From e61552e90665a44fafe589bdafa8e0706ee9996e Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Fri, 23 Feb 2018 03:48:50 -0500 Subject: [PATCH 001/118] [Spell] Non-modifying spells no longer mark file as modified --- src/spellbook.cpp | 7 +++++++ src/spellbook.h | 2 ++ src/spells/blocks.cpp | 1 + src/spells/blocks.h | 1 + src/spells/flags.cpp | 1 + src/spells/headerstring.cpp | 4 +++- src/spells/misc.cpp | 3 +++ src/spells/stringpalette.cpp | 1 + src/spells/texture.cpp | 4 ++++ src/ui/widgets/nifview.cpp | 6 ++++++ 10 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/spellbook.cpp b/src/spellbook.cpp index 3ec4c3b33..9a02b9e14 100644 --- a/src/spellbook.cpp +++ b/src/spellbook.cpp @@ -110,6 +110,13 @@ void SpellBook::cast( NifModel * nif, const QModelIndex & index, SpellPtr spell QDialogButtonBox::StandardButton response = QDialogButtonBox::Yes; + // Cast non-modifying spells + if ( spell && spell->isApplicable( nif, index ) && spell->constant() ) { + auto idx = spell->cast( nif, index ); + emit sigIndex( idx ); + return; + } + if ( !suppressConfirm && spell->page() != "Array" ) { response = CheckableMessageBox::question( this, "Confirmation", "This action cannot currently be undone. Do you want to continue?", "Do not ask me again", &accepted ); diff --git a/src/spellbook.h b/src/spellbook.h index 84d888db8..12a535beb 100644 --- a/src/spellbook.h +++ b/src/spellbook.h @@ -73,6 +73,8 @@ class Spell virtual QString hint() const { return QString(); } //! Icon displayed in block view virtual QIcon icon() const { return QIcon(); } + //! Whether the spell does not modify the file + virtual bool constant() const { return false; } //! Whether the spell shows up in block list instead of a context menu virtual bool instant() const { return false; } //! Whether the spell performs a sanitizing function diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 4872eda30..29384ccf9 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -1047,6 +1047,7 @@ class spCopyBlock final : public Spell public: QString name() const override final { return Spell::tr( "Copy" ); } QString page() const override final { return Spell::tr( "Block" ); } + bool constant() const override final { return true; } QKeySequence hotkey() const override final { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_C }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final diff --git a/src/spells/blocks.h b/src/spells/blocks.h index f8e75843c..c57da8eeb 100644 --- a/src/spells/blocks.h +++ b/src/spells/blocks.h @@ -28,6 +28,7 @@ class spCopyBranch final : public Spell public: QString name() const override final { return Spell::tr( "Copy Branch" ); } QString page() const override final { return Spell::tr( "Block" ); } + bool constant() const override final { return true; } QKeySequence hotkey() const override final { return QKeySequence( QKeySequence::Copy ); } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final; diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index 36df2fd5c..5c1c7ca41 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -21,6 +21,7 @@ class spEditFlags : public Spell { public: QString name() const override { return Spell::tr( "Flags" ); } + bool constant() const override { return true; } bool instant() const override { return true; } QIcon icon() const override { return QIcon( ":/img/flag" ); } diff --git a/src/spells/headerstring.cpp b/src/spells/headerstring.cpp index 9e0a36638..0a2519100 100644 --- a/src/spells/headerstring.cpp +++ b/src/spells/headerstring.cpp @@ -75,6 +75,7 @@ class spEditStringIndex final : public Spell return *txt_xpm_icon; } + bool constant() const override final { return true; } bool instant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final @@ -139,7 +140,8 @@ class spEditStringIndex final : public Spell if ( dlg.exec() != QDialog::Accepted ) return index; - nif->set( index, le->text() ); + if ( le->text() != string ) + nif->set( index, le->text() ); return index; } diff --git a/src/spells/misc.cpp b/src/spells/misc.cpp index 9ed5dd274..151bb579e 100644 --- a/src/spells/misc.cpp +++ b/src/spells/misc.cpp @@ -105,6 +105,7 @@ class spFollowLink final : public Spell { public: QString name() const override final { return Spell::tr( "Follow Link" ); } + bool constant() const override final { return true; } bool instant() const override final { return true; } QIcon icon() const override final { return QIcon( ":/img/link" ); } @@ -131,6 +132,7 @@ class spFileOffset final : public Spell { public: QString name() const override final { return Spell::tr( "File Offset" ); } + bool constant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -155,6 +157,7 @@ class spExportBinary final : public Spell { public: QString name() const override final { return Spell::tr( "Export Binary" ); } + bool constant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { diff --git a/src/spells/stringpalette.cpp b/src/spells/stringpalette.cpp index 29d330d11..49d2f0cde 100644 --- a/src/spells/stringpalette.cpp +++ b/src/spells/stringpalette.cpp @@ -73,6 +73,7 @@ class spEditStringOffset final : public Spell public: QString name() const override final { return Spell::tr( "Edit String Offset" ); } QString page() const override final { return Spell::tr( "" ); } + bool constant() const override final { return true; } QIcon icon() const override final { if ( !txt_xpm_icon ) diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index 388331eeb..bd884ed0b 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -159,6 +159,7 @@ class spChooseTexture final : public Spell public: QString name() const override final { return Spell::tr( "Choose" ); } QString page() const override final { return Spell::tr( "Texture" ); } + bool constant() const override final { return true; } bool instant() const override final { return true; } QIcon icon() const override final { @@ -259,6 +260,7 @@ class spEditTexCoords final : public Spell public: QString name() const override final { return Spell::tr( "Edit UV" ); } QString page() const override final { return Spell::tr( "Texture" ); } + bool constant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -524,6 +526,7 @@ class spTextureTemplate final : public Spell { QString name() const override final { return Spell::tr( "Export Template" ); } QString page() const override final { return Spell::tr( "Texture" ); } + bool constant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -880,6 +883,7 @@ class spExportTexture final : public Spell public: QString name() const override final { return Spell::tr( "Export" ); } QString page() const override final { return Spell::tr( "Texture" ); } + bool constant() const override final { return true; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index fc2e3bfc0..48fb2bf7a 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -426,6 +426,12 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) oldidx = proxy->mapTo( currentIndex() ); } + // Cast non-modifying spells + if ( spell->constant() && spell->isApplicable( nif, oldidx ) ) { + spell->cast( nif, oldidx ); + return; + } + if ( nif && spell->isApplicable( nif, oldidx ) ) { selectionModel()->setCurrentIndex( QModelIndex(), QItemSelectionModel::Clear | QItemSelectionModel::Rows ); From e2c61c2e7eb90fc8792cef77397d9cb439656819 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 10 Jun 2018 10:14:26 -0400 Subject: [PATCH 002/118] [UI] Message box no longer focus stealing This was necessary for a new action which uses a message box to display information but it's meant to stay open while you interact with the rest of the UI. --- src/message.cpp | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/message.cpp b/src/message.cpp index 5d588dda1..5e8c96fed 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -26,17 +26,15 @@ Message::~Message() void Message::message( QWidget * parent, const QString & str, QMessageBox::Icon icon ) { auto msgBox = new QMessageBox( parent ); - - // Keep message box on top if it does not have a parent - //if ( !parent ) - msgBox->setWindowFlags( msgBox->windowFlags() | Qt::WindowStaysOnTopHint | Qt::Tool ); + msgBox->setWindowFlags( msgBox->windowFlags() | Qt::Tool ); + msgBox->setAttribute( Qt::WA_DeleteOnClose ); + msgBox->setWindowModality( Qt::NonModal ); msgBox->setText( str ); msgBox->setIcon( icon ); - msgBox->open(); + msgBox->show(); - //if ( !parent ) msgBox->activateWindow(); } @@ -47,18 +45,16 @@ void Message::message( QWidget * parent, const QString & str, const QString & er parent = qApp->activeWindow(); auto msgBox = new QMessageBox( parent ); - - // Keep message box on top if it does not have a parent - //if ( !parent ) - msgBox->setWindowFlags( msgBox->windowFlags() | Qt::WindowStaysOnTopHint | Qt::Tool ); + msgBox->setAttribute( Qt::WA_DeleteOnClose ); + msgBox->setWindowModality( Qt::NonModal ); + msgBox->setWindowFlags( msgBox->windowFlags() | Qt::Tool ); msgBox->setText( str ); msgBox->setIcon( icon ); msgBox->setDetailedText( err ); - msgBox->open(); + msgBox->show(); - //if ( !parent ) msgBox->activateWindow(); } @@ -152,25 +148,24 @@ void Message::append( QWidget * parent, const QString & str, const QString & err } else { // Create new message box auto msgBox = new QMessageBox( parent ); - - // Keep message box on top if it does not have a parent - //if ( !parent ) - msgBox->setWindowFlags( msgBox->windowFlags() | Qt::WindowStaysOnTopHint | Qt::Tool ); + msgBox->setAttribute( Qt::WA_DeleteOnClose ); + msgBox->setWindowModality( Qt::NonModal ); + msgBox->setWindowFlags( msgBox->windowFlags() | Qt::Tool ); msgBox->setText( str ); msgBox->setIcon( icon ); msgBox->setDetailedText( err + "\n" ); - msgBox->open(); + msgBox->show(); - //if ( !parent ) msgBox->activateWindow(); messageBoxes[str] = msgBox; // Clear Detailed Text with each confirmation - connect( msgBox, &QMessageBox::buttonClicked, [msgBox]( QAbstractButton * button ) { + connect( msgBox, &QMessageBox::buttonClicked, [msgBox, str]( QAbstractButton * button ) { Q_UNUSED( button ); - msgBox->setDetailedText( "" ); + if ( messageBoxes.contains( str ) ) + messageBoxes.remove( str ); } ); } } From b9ca3a3a3b8f37a5e3cbcce0745c50c0feeafe1c Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 10 Jun 2018 10:18:02 -0400 Subject: [PATCH 003/118] [GL] More specific condition for samplers The intent of `mesh->bslsp` was really `!mesh->bsesp` and to ever support shaders on Oblivion/FO3 this change is needed as those won't be BSLSP either. --- src/gl/renderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 9a73812ae..f2a670c65 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -649,7 +649,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } } - if ( bsprop && mesh->bslsp ) { + if ( bsprop && !mesh->bsesp ) { QString forced; if ( !(opts & Scene::DoLighting) ) forced = default_n; @@ -679,7 +679,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } } - if ( bsprop && mesh->bslsp ) { + if ( bsprop && !mesh->bsesp ) { prog->uniSampler( bsprop, SAMP_GLOW, 2, texunit, black, clamp ); } else if ( !bsprop ) { GLint uniGlowMap = prog->uniformLocations[SAMP_GLOW]; From 6d0bfda02e4b2ce8232125f892d1770a8db4e945 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 10 Jun 2018 10:23:17 -0400 Subject: [PATCH 004/118] [Spell] Referenced By Show which blocks reference this one --- src/spells/blocks.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 29384ccf9..6347f1ea1 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -22,6 +22,7 @@ */ const char * B_ERR = QT_TR_NOOP( "%1 failed with errors." ); +const char * REF_MSG = QT_TR_NOOP( "Found %1 References" ); // Use Unicode symbols for separators // to lessen chance of splitting incorrectly. @@ -2105,3 +2106,53 @@ class spAttachParentNode final : public Spell REGISTER_SPELL( spAttachParentNode ) +//! List all blocks that reference this block +class spReferencedBy final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Referenced By" ); } + QString page() const override final { return Spell::tr( "" ); } + bool constant() const override final { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + return nif->isNiBlock( index ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + int blockNum = nif->getBlockNumber( index ); + + QVector parents; + QVector children; + for ( int i = 0; i < nif->getBlockCount(); i++ ) { + if ( i == blockNum ) + continue; + + auto parentLinks = nif->getParentLinks( i ); + if ( parentLinks.contains( blockNum ) ) + children << i; + + auto childLinks = nif->getChildLinks( i ); + if ( childLinks.contains( blockNum ) ) + parents << i; + } + + int refCount = parents.count() + children.count(); + + for ( const int p : parents ) { + Message::append( tr( REF_MSG ).arg( refCount ), tr( "Parent: %1" ).arg( p ), + QMessageBox::Information + ); + } + + for ( const int c : children ) { + Message::append( tr( REF_MSG ).arg( refCount ), tr( "Child: %1" ).arg( c ), + QMessageBox::Information + ); + } + return index; + } +}; + +REGISTER_SPELL( spReferencedBy ) From b4c747067ee7b4a0f118ebd52dde3ec0bf120c20 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 10 Jun 2018 10:36:26 -0400 Subject: [PATCH 005/118] [Spell] Convert List Shape For FNV and later, convert CLS to LS, and for FO3 and earlier, convert LS to CLS. --- src/spells/havok.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index 5691ed6e8..5547a314d 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -493,3 +493,78 @@ class spPackHavokStrips final : public Spell REGISTER_SPELL( spPackHavokStrips ) +//! Converts bhkListShape to bhkConvexListShape for FO3 +class spConvertListShape final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Convert to bhkConvexListShape" ); } + QString page() const override final { return Spell::tr( "Havok" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & idx ) override final + { + return nif->isNiBlock( idx, "bhkListShape" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) override final + { + QPersistentModelIndex iShape( iBlock ); + QPersistentModelIndex iRigidBody = nif->getBlock( nif->getParent( iShape ) ); + if ( !iRigidBody.isValid() ) + return {}; + + auto iCLS = nif->insertNiBlock( "bhkConvexListShape" ); + + nif->set( iCLS, "Num Sub Shapes", nif->get( iShape, "Num Sub Shapes" ) ); + nif->set( iCLS, "Material", nif->get( iShape, "Material" ) ); + nif->updateArray( iCLS, "Sub Shapes" ); + + nif->setLinkArray( iCLS, "Sub Shapes", nif->getLinkArray( iShape, "Sub Shapes" ) ); + nif->setLinkArray( iShape, "Sub Shapes", {} ); + nif->removeNiBlock( nif->getBlockNumber( iShape ) ); + + nif->setLink( iRigidBody, "Shape", nif->getBlockNumber( iCLS ) ); + + return iCLS; + } +}; + +REGISTER_SPELL( spConvertListShape ) + +//! Converts bhkConvexListShape to bhkListShape for FNV +class spConvertConvexListShape final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Convert to bhkListShape" ); } + QString page() const override final { return Spell::tr( "Havok" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & idx ) override final + { + return nif->isNiBlock( idx, "bhkConvexListShape" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) override final + { + QPersistentModelIndex iShape( iBlock ); + QPersistentModelIndex iRigidBody = nif->getBlock( nif->getParent( iShape ) ); + if ( !iRigidBody.isValid() ) + return {}; + + auto iLS = nif->insertNiBlock( "bhkListShape" ); + + nif->set( iLS, "Num Sub Shapes", nif->get( iShape, "Num Sub Shapes" ) ); + nif->set( iLS, "Num Unknown Ints", nif->get( iShape, "Num Sub Shapes" ) ); + nif->set( iLS, "Material", nif->get( iShape, "Material" ) ); + nif->updateArray( iLS, "Sub Shapes" ); + nif->updateArray( iLS, "Unknown Ints" ); + + nif->setLinkArray( iLS, "Sub Shapes", nif->getLinkArray( iShape, "Sub Shapes" ) ); + nif->setLinkArray( iShape, "Sub Shapes", {} ); + nif->removeNiBlock( nif->getBlockNumber( iShape ) ); + + nif->setLink( iRigidBody, "Shape", nif->getBlockNumber( iLS ) ); + + return iLS; + } +}; + +REGISTER_SPELL( spConvertConvexListShape ) From 0d7396b17a8f9ece1575073b18d63492ed8cca06 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 10 Jun 2018 10:46:22 -0400 Subject: [PATCH 006/118] [Spell] Create Convex Shape improvements Merge multiple CVS into a list shape. Take the entire transform into account for CVS. Support BSTriShape. Also synced to XML. --- src/spells/havok.cpp | 133 ++++++++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 39 deletions(-) diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index 5547a314d..24bf3537f 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -35,16 +35,22 @@ class spCreateCVS final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - if ( !nif->inherits( index, "NiTriBasedGeom" ) || !nif->checkVersion( 0x0A000100, 0 ) ) + if ( !(nif->inherits( index, "NiTriBasedGeom" ) || nif->inherits( index, "BSTriShape" )) + || !nif->getUserVersion2() ) return false; QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); - return iData.isValid(); + if ( !iData.isValid() && nif->getIndex( index, "Vertex Data" ).isValid() ) + iData = index; + + return iData.isValid() && nif->get( iData, "Num Vertices" ) > 0; } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + if ( !iData.isValid() ) + iData = nif->getIndex( index, "Vertex Data" ); if ( !iData.isValid() ) return index; @@ -60,10 +66,26 @@ class spCreateCVS final : public Spell QVector verts = nif->getArray( iData, "Vertices" ); QVector vertsTrans; + if ( nif->getUserVersion2() < 100 ) { + verts = nif->getArray( iData, "Vertices" ); + } else { + int numVerts = nif->get( index, "Num Vertices" ); + verts.reserve( numVerts ); + for ( int i = 0; i < numVerts; i++ ) + verts += nif->get( nif->index( i, 0, iData ), "Vertex" ); + } + // Offset by translation of NiTriShape Vector3 trans = nif->get( index, "Translation" ); + Matrix rot = nif->get( index, "Rotation" ); + float scale = nif->get( index, "Scale" ); + Transform transform; + transform.translation = trans; + transform.rotation = rot; + transform.scale = scale; + for ( auto v : verts ) { - vertsTrans.append( v + trans ); + vertsTrans.append( transform * v ); } // to store results @@ -165,10 +187,6 @@ class spCreateCVS final : public Spell // TODO: Figure out if radius is not arbitrarily set in vanilla NIFs nif->set( iCVS, "Radius", spnRadius->value() ); - // for arrow detection: [0, 0, -0, 0, 0, -0] - nif->set( nif->getIndex( iCVS, "Unknown 6 Floats" ).child( 2, 0 ), -0.0 ); - nif->set( nif->getIndex( iCVS, "Unknown 6 Floats" ).child( 5, 0 ), -0.0 ); - QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ) ); QModelIndex collisionLink = nif->getIndex( iParent, "Collision Object" ); QModelIndex collisionObject = nif->getBlock( nif->getLink( collisionLink ) ); @@ -191,21 +209,58 @@ class spCreateCVS final : public Spell nif->setLink( rigidBodyLink, nif->getBlockNumber( rigidBody ) ); } - QModelIndex shapeLink = nif->getIndex( rigidBody, "Shape" ); - QModelIndex shape = nif->getBlock( nif->getLink( shapeLink ) ); - - // set link and delete old one - nif->setLink( shapeLink, nif->getBlockNumber( iCVS ) ); + QPersistentModelIndex shapeLink = nif->getIndex( rigidBody, "Shape" ); + QPersistentModelIndex shape = nif->getBlock( nif->getLink( shapeLink ) ); + QVector shapeLinks; + bool replace = true; if ( shape.isValid() ) { - // cheaper than calling spRemoveBranch - nif->removeNiBlock( nif->getBlockNumber( shape ) ); + shapeLinks = { nif->getBlockNumber( shape ) }; + + QString questionTitle = tr( "Create List Shape" ); + QString questionBody = tr( "This collision object already has a shape. Combine into a list shape? 'No' will replace the shape." ); + + bool isListShape = false; + if ( nif->inherits( shape, "bhkListShape" ) ) { + isListShape = true; + questionTitle = tr( "Add to List Shape" ); + questionBody = tr( "This collision object already has a list shape. Add to list shape? 'No' will replace the list shape." ); + shapeLinks = nif->getLinkArray( shape, "Sub Shapes" ); + } + + int response = QMessageBox::question( nullptr, questionTitle, questionBody, QMessageBox::Yes, QMessageBox::No ); + if ( response == QMessageBox::Yes ) { + QModelIndex iListShape = shape; + if ( !isListShape ) { + iListShape = nif->insertNiBlock( "bhkListShape" ); + nif->setLink( shapeLink, nif->getBlockNumber( iListShape ) ); + } + + shapeLinks << nif->getBlockNumber( iCVS ); + nif->set( iListShape, "Num Sub Shapes", shapeLinks.size() ); + nif->updateArray( iListShape, "Sub Shapes" ); + nif->setLinkArray( iListShape, "Sub Shapes", shapeLinks ); + nif->set( iListShape, "Num Unknown Ints", shapeLinks.size() ); + nif->updateArray( iListShape, "Unknown Ints" ); + replace = false; + } + } + + if ( replace ) { + // Replace link + nif->setLink( shapeLink, nif->getBlockNumber( iCVS ) ); + // Remove all old shapes + spRemoveBranch rm; + rm.castIfApplicable( nif, shape ); } - Message::info( nullptr, Spell::tr( "Created hull with %1 vertices, %2 normals" ).arg( convex_verts.count() ).arg( convex_norms.count() ) ); + Message::info( nullptr, + Spell::tr( "Created hull with %1 vertices, %2 normals" ) + .arg( convex_verts.count() ) + .arg( convex_norms.count() ) + ); - // returning iCVS here can crash NifSkope if a child array is selected - return index; + return (iCVS.isValid()) ? iCVS : rigidBody; } }; @@ -280,39 +335,39 @@ class spConstraintHelper final : public Spell pivot = transB.rotation.inverted() * ( pivot - transB.translation ) / transB.scale / havokConst; nif->set( iConstraintData, "Pivot B", { pivot[0], pivot[1], pivot[2], 0 } ); - QString axleA, axleB, twistA, twistB, twistA2, twistB2; + QString axisA, axisB, twistA, twistB, twistA2, twistB2; if ( name.endsWith( "HingeConstraint" ) ) { - axleA = "Axle A"; - axleB = "Axle B"; - twistA = "Perp2 Axle In A1"; - twistB = "Perp2 Axle In B1"; - twistA2 = "Perp2 Axle In A2"; - twistB2 = "Perp2 Axle In B2"; + axisA = "Axis A"; + axisB = "Axis B"; + twistA = "Perp Axis In A1"; + twistB = "Perp Axis In B1"; + twistA2 = "Perp Axis In A2"; + twistB2 = "Perp Axis In B2"; } else if ( name == "bhkRagdollConstraint" ) { - axleA = "Plane A"; - axleB = "Plane B"; + axisA = "Plane A"; + axisB = "Plane B"; twistA = "Twist A"; twistB = "Twist B"; } - if ( axleA.isEmpty() || axleB.isEmpty() || twistA.isEmpty() || twistB.isEmpty() ) + if ( axisA.isEmpty() || axisB.isEmpty() || twistA.isEmpty() || twistB.isEmpty() ) return index; - Vector3 axle = Vector3( nif->get( iConstraintData, axleA ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraintData, axleB, { axle[0], axle[1], axle[2], 0 } ); + Vector3 axis = Vector3( nif->get( iConstraintData, axisA ) ); + axis = transA.rotation * axis; + axis = transB.rotation.inverted() * axis; + nif->set( iConstraintData, axisB, { axis[0], axis[1], axis[2], 0 } ); - axle = Vector3( nif->get( iConstraintData, twistA ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraintData, twistB, { axle[0], axle[1], axle[2], 0 } ); + axis = Vector3( nif->get( iConstraintData, twistA ) ); + axis = transA.rotation * axis; + axis = transB.rotation.inverted() * axis; + nif->set( iConstraintData, twistB, { axis[0], axis[1], axis[2], 0 } ); if ( !twistA2.isEmpty() && !twistB2.isEmpty() ) { - axle = Vector3( nif->get( iConstraintData, twistA2 ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraintData, twistB2, { axle[0], axle[1], axle[2], 0 } ); + axis = Vector3( nif->get( iConstraintData, twistA2 ) ); + axis = transA.rotation * axis; + axis = transB.rotation.inverted() * axis; + nif->set( iConstraintData, twistB2, { axis[0], axis[1], axis[2], 0 } ); } return index; From 4fd3ada0b3148d020ac1f2d91c918f6a3bfc6052 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 10 Jun 2018 15:17:58 -0400 Subject: [PATCH 007/118] [XML] Initial nifxml 1.0 sync Added token replacement, support for `onlyT`/`excludeT` by simply aliasing back to the `cond` version for now. At least acknowledge bitfield tags for now, but do not do anything with them other than alias them to their storage type. Support for the BSStreamHeader reorganization in Header. Renamed TEMPLATE and ARG tokens. Removed userver and userver2 Do *NOT* support int64/uint64 yet other than the type map so that nifxml won't error. --- NifSkope.pro | 1 + res/shaders/fo4_default.prog | 2 +- res/shaders/fo4_effectshader.prog | 2 +- res/shaders/sk_default.prog | 4 +- res/shaders/sk_effectshader.prog | 4 +- res/shaders/sk_msn.prog | 4 +- res/shaders/sk_multilayer.prog | 4 +- src/data/nifvalue.cpp | 2 + src/data/nifvalue.h | 68 ++++++++------- src/gl/glnode.cpp | 72 ++++++++-------- src/gl/renderer.cpp | 10 ++- src/model/basemodel.cpp | 3 +- src/model/kfmmodel.cpp | 17 ++-- src/model/nifmodel.cpp | 16 ++-- src/model/nifmodel.h | 3 +- src/xml/kfmxml.cpp | 7 +- src/xml/nifxml.cpp | 139 +++++++++++++++++++++++------- src/xml/xmlconfig.h | 9 ++ 18 files changed, 233 insertions(+), 134 deletions(-) create mode 100644 src/xml/xmlconfig.h diff --git a/NifSkope.pro b/NifSkope.pro index 1c0bc5a69..ecbf0fe49 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -195,6 +195,7 @@ HEADERS += \ src/ui/settingsdialog.h \ src/ui/settingspane.h \ src/xml/nifexpr.h \ + src/xml/xmlconfig.h \ src/glview.h \ src/message.h \ src/nifskope.h \ diff --git a/res/shaders/fo4_default.prog b/res/shaders/fo4_default.prog index 69dfa7618..246d69bbf 100644 --- a/res/shaders/fo4_default.prog +++ b/res/shaders/fo4_default.prog @@ -2,7 +2,7 @@ checkgroup begin and # Fallout 4 - check HEADER/User Version 2 >= 130 + check HEADER/BS Header/BS Version >= 130 check BSLightingShaderProperty #check BSLightingShaderProperty/Skyrim Shader Type != 1 #check BSLightingShaderProperty/Skyrim Shader Type != 16 diff --git a/res/shaders/fo4_effectshader.prog b/res/shaders/fo4_effectshader.prog index f8ed3d5ba..b56583d0d 100644 --- a/res/shaders/fo4_effectshader.prog +++ b/res/shaders/fo4_effectshader.prog @@ -2,7 +2,7 @@ checkgroup begin and # Fallout 4 - check HEADER/User Version 2 >= 130 + check HEADER/BS Header/BS Version >= 130 check BSEffectShaderProperty checkgroup end diff --git a/res/shaders/sk_default.prog b/res/shaders/sk_default.prog index ae706b875..0535dcd2e 100644 --- a/res/shaders/sk_default.prog +++ b/res/shaders/sk_default.prog @@ -2,8 +2,8 @@ checkgroup begin and # Skyrim - check HEADER/User Version 2 >= 83 - check HEADER/User Version 2 <= 100 + check HEADER/BS Header/BS Version >= 83 + check HEADER/BS Header/BS Version <= 100 check BSLightingShaderProperty check BSLightingShaderProperty/Skyrim Shader Type != 11 checkgroup begin or diff --git a/res/shaders/sk_effectshader.prog b/res/shaders/sk_effectshader.prog index 389b5422f..6905e34a1 100644 --- a/res/shaders/sk_effectshader.prog +++ b/res/shaders/sk_effectshader.prog @@ -2,8 +2,8 @@ checkgroup begin and # Skyrim - check HEADER/User Version 2 >= 83 - check HEADER/User Version 2 <= 100 + check HEADER/BS Header/BS Version >= 83 + check HEADER/BS Header/BS Version <= 100 check BSEffectShaderProperty checkgroup end diff --git a/res/shaders/sk_msn.prog b/res/shaders/sk_msn.prog index b1fd18097..c0bb38cc9 100644 --- a/res/shaders/sk_msn.prog +++ b/res/shaders/sk_msn.prog @@ -3,8 +3,8 @@ checkgroup begin or checkgroup begin and # Skyrim - check HEADER/User Version 2 >= 83 - check HEADER/User Version 2 <= 100 + check HEADER/BS Header/BS Version >= 83 + check HEADER/BS Header/BS Version <= 130 checkgroup end checkgroup end diff --git a/res/shaders/sk_multilayer.prog b/res/shaders/sk_multilayer.prog index 830a22a35..2631200a4 100644 --- a/res/shaders/sk_multilayer.prog +++ b/res/shaders/sk_multilayer.prog @@ -2,8 +2,8 @@ checkgroup begin and # Skyrim - check HEADER/User Version 2 >= 83 - check HEADER/User Version 2 <= 100 + check HEADER/BS Header/BS Version >= 83 + check HEADER/BS Header/BS Version <= 100 check BSLightingShaderProperty check BSLightingShaderProperty/Skyrim Shader Type == 11 checkgroup begin or diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index d1d8feb14..07ebece4e 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -82,6 +82,8 @@ void NifValue::initialize() typeMap.insert( "ushort", NifValue::tWord ); typeMap.insert( "uint", NifValue::tUInt ); typeMap.insert( "ulittle32", NifValue::tULittle32 ); + typeMap.insert( "int64", NifValue::tInt64 ); + typeMap.insert( "uint64", NifValue::tUInt64 ); typeMap.insert( "Ref", NifValue::tLink ); typeMap.insert( "Ptr", NifValue::tUpLink ); typeMap.insert( "float", NifValue::tFloat ); diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 15428bdc7..7ef692eb8 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -90,42 +90,44 @@ class NifValue tInt = 7, tShort = 8, tULittle32 = 9, - tUInt = 10, + tInt64 = 10, + tUInt64 = 11, + tUInt = 12, // - tLink = 11, - tUpLink = 12, - tFloat = 13, + tLink = 13, + tUpLink = 14, + tFloat = 15, // all string types should come between tSizedString and tChar8String - tSizedString = 14, - tText = 15, - tShortString = 16, - tHeaderString = 18, - tLineString = 19, - tChar8String = 20, + tSizedString = 16, + tText = 17, + tShortString = 18, + tHeaderString = 19, + tLineString = 20, + tChar8String = 21, // - tColor3 = 21, - tColor4 = 22, - tVector3 = 23, - tQuat = 24, - tQuatXYZW = 25, - tMatrix = 26, - tMatrix4 = 27, - tVector2 = 28, - tVector4 = 29, - tTriangle = 30, - tFileVersion = 31, - tByteArray = 32, - tStringPalette = 33, - tString = 34, //!< not a regular string: an integer for nif versions 20.1.0.3 and up - tFilePath = 35, //!< not a string: requires special handling for slash/backslash etc. - tByteMatrix = 36, - tBlob = 37, - tHfloat = 38, - tHalfVector3 = 39, - tByteVector3 = 40, - tHalfVector2 = 41, - tByteColor4 = 42, - tBSVertexDesc = 43, + tColor3 = 22, + tColor4 = 23, + tVector3 = 24, + tQuat = 25, + tQuatXYZW = 26, + tMatrix = 27, + tMatrix4 = 28, + tVector2 = 29, + tVector4 = 30, + tTriangle = 31, + tFileVersion = 32, + tByteArray = 33, + tStringPalette = 34, + tString = 35, //!< not a regular string: an integer for nif versions 20.1.0.3 and up + tFilePath = 36, //!< not a string: requires special handling for slash/backslash etc. + tByteMatrix = 37, + tBlob = 38, + tHfloat = 39, + tHalfVector3 = 40, + tByteVector3 = 41, + tHalfVector2 = 42, + tByteColor4 = 43, + tBSVertexDesc = 44, tNone = 0xff }; diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index d9cbb9da3..31f03bbed 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -1145,12 +1145,12 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); - const Vector3 axleA( nif->get( iHinge, "Axle A" ) ); - const Vector3 axleA1( nif->get( iHinge, "Perp2 Axle In A1" ) ); - const Vector3 axleA2( nif->get( iHinge, "Perp2 Axle In A2" ) ); + const Vector3 axisA( nif->get( iHinge, "Axis A" ) ); + const Vector3 axisA1( nif->get( iHinge, "Perp Axis In A1" ) ); + const Vector3 axisA2( nif->get( iHinge, "Perp Axis In A2" ) ); - const Vector3 axleB( nif->get( iHinge, "Axle B" ) ); - const Vector3 axleB2( nif->get( iHinge, "Perp2 Axle In B2" ) ); + const Vector3 axisB( nif->get( iHinge, "Axis B" ) ); + const Vector3 axisB2( nif->get( iHinge, "Perp Axis In B2" ) ); const float minAngle = nif->get( iHinge, "Min Angle" ); const float maxAngle = nif->get( iHinge, "Max Angle" ); @@ -1162,11 +1162,11 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glColor( color_a ); glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + axleA ); glEnd(); - drawDashLine( pivotA, pivotA + axleA1, 14 ); - drawDashLine( pivotA, pivotA + axleA2, 14 ); - drawCircle( pivotA, axleA, 1.0f ); - drawSolidArc( pivotA, axleA / 5, axleA2, axleA1, minAngle, maxAngle, 1.0f ); + glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + axisA ); glEnd(); + drawDashLine( pivotA, pivotA + axisA1, 14 ); + drawDashLine( pivotA, pivotA + axisA2, 14 ); + drawCircle( pivotA, axisA, 1.0f ); + drawSolidArc( pivotA, axisA / 5, axisA2, axisA1, minAngle, maxAngle, 1.0f ); glPopMatrix(); glPushMatrix(); @@ -1176,22 +1176,22 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glColor( color_b ); glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axleB ); glEnd(); - drawDashLine( pivotB + axleB2, pivotB, 14 ); - drawDashLine( pivotB + Vector3::crossproduct( axleB2, axleB ), pivotB, 14 ); - drawCircle( pivotB, axleB, 1.01f ); - drawSolidArc( pivotB, axleB / 7, axleB2, Vector3::crossproduct( axleB2, axleB ), minAngle, maxAngle, 1.01f ); + glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axisB ); glEnd(); + drawDashLine( pivotB + axisB2, pivotB, 14 ); + drawDashLine( pivotB + Vector3::crossproduct( axisB2, axisB ), pivotB, 14 ); + drawCircle( pivotB, axisB, 1.01f ); + drawSolidArc( pivotB, axisB / 7, axisB2, Vector3::crossproduct( axisB2, axisB ), minAngle, maxAngle, 1.01f ); glPopMatrix(); glMultMatrix( tBodies.value( 0 ) ); - float angle = Vector3::angle( tBodies.value( 0 ).rotation * axleA2, tBodies.value( 1 ).rotation * axleB2 ); + float angle = Vector3::angle( tBodies.value( 0 ).rotation * axisA2, tBodies.value( 1 ).rotation * axisB2 ); if ( !Node::SELECTING ) glColor( color_a ); glBegin( GL_LINES ); glVertex( pivotA ); - glVertex( pivotA + axleA1 * cosf( angle ) + axleA2 * sinf( angle ) ); + glVertex( pivotA + axisA1 * cosf( angle ) + axisA2 * sinf( angle ) ); glEnd(); } else if ( name == "bhkHingeConstraint" ) { QModelIndex iHinge = nif->getIndex( iConstraint, "Hinge" ); @@ -1201,34 +1201,34 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); - const Vector3 axleA1( nif->get( iHinge, "Perp2 Axle In A1" ) ); - const Vector3 axleA2( nif->get( iHinge, "Perp2 Axle In A2" ) ); - const Vector3 axleA( Vector3::crossproduct( axleA1, axleA2 ) ); + const Vector3 axisA1( nif->get( iHinge, "Perp Axis In A1" ) ); + const Vector3 axisA2( nif->get( iHinge, "Perp Axis In A2" ) ); + const Vector3 axisA( Vector3::crossproduct( axisA1, axisA2 ) ); - const Vector3 axleB( nif->get( iHinge, "Axle B" ) ); + const Vector3 axisB( nif->get( iHinge, "Axis B" ) ); - const Vector3 axleB1( axleB[1], axleB[2], axleB[0] ); - const Vector3 axleB2( Vector3::crossproduct( axleB, axleB1 ) ); + const Vector3 axisB1( axisB[1], axisB[2], axisB[0] ); + const Vector3 axisB2( Vector3::crossproduct( axisB, axisB1 ) ); /* * This should be correct but is visually strange... * - Vector3 axleB1temp; - Vector3 axleB2temp; + Vector3 axisB1temp; + Vector3 axisB2temp; if ( nif->checkVersion( 0, 0x14000002 ) ) { - Vector3 axleB1temp( axleB[1], axleB[2], axleB[0] ); - Vector3 axleB2temp( Vector3::crossproduct( axleB, axleB1temp ) ); + Vector3 axisB1temp( axisB[1], axisB[2], axisB[0] ); + Vector3 axisB2temp( Vector3::crossproduct( axisB, axisB1temp ) ); } 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 axisB1temp( nif->get( iHinge, "Perp Axis In B1" ) ); + Vector3 axisB2temp( nif->get( iHinge, "Perp Axis In B2" ) ); } - const Vector3 axleB1( axleB1temp ); - const Vector3 axleB2( axleB2temp ); + const Vector3 axisB1( axisB1temp ); + const Vector3 axisB2( axisB2temp ); */ const float minAngle = (float)-PI; @@ -1241,9 +1241,9 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glColor( color_a ); glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); - drawDashLine( pivotA, pivotA + axleA1 ); - drawDashLine( pivotA, pivotA + axleA2 ); - drawSolidArc( pivotA, axleA / 5, axleA2, axleA1, minAngle, maxAngle, 1.0f, 16 ); + drawDashLine( pivotA, pivotA + axisA1 ); + drawDashLine( pivotA, pivotA + axisA2 ); + drawSolidArc( pivotA, axisA / 5, axisA2, axisA1, minAngle, maxAngle, 1.0f, 16 ); glPopMatrix(); glMultMatrix( tBodies.value( 1 ) ); @@ -1252,8 +1252,8 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glColor( color_b ); glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axleB ); glEnd(); - drawSolidArc( pivotB, axleB / 7, axleB2, axleB1, minAngle, maxAngle, 1.01f, 16 ); + glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axisB ); glEnd(); + drawSolidArc( pivotB, axisB / 7, axisB2, axisB1, minAngle, maxAngle, 1.01f, 16 ); } else if ( name == "bhkStiffSpringConstraint" ) { QModelIndex iSpring = nif->getIndex( iConstraint, "Stiff Spring" ); if ( !iSpring.isValid() ) diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index f2a670c65..9bf1a5302 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -123,8 +123,14 @@ QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QVe { QString childid; - if ( blkid.startsWith( "HEADER/" ) ) - return nif->getIndex( nif->getHeader(), blkid.remove( "HEADER/" ) ); + if ( blkid.startsWith( "HEADER/" ) ) { + auto blk = blkid.remove( "HEADER/" ); + if ( blk.contains("/") ) { + auto blks = blk.split( "/" ); + return nif->getIndex( nif->getIndex( nif->getHeader(), blks.at(0) ), blks.at(1) ); + } + return nif->getIndex( nif->getHeader(), blk ); + } int pos = blkid.indexOf( "/" ); diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 230c6dbe3..c7e7eb1a2 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -32,6 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "basemodel.h" +#include "xml/xmlconfig.h" #include "message.h" #include @@ -828,7 +829,7 @@ QVariant BaseModelEval::operator()(const QVariant & v) const const NifItem * i = item; // resolve "ARG" - while ( left == "ARG" ) { + while ( left == XMLARG ) { if ( !i->parent() ) return false; diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index f4bf1f6c5..3ee4fae67 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -32,6 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "kfmmodel.h" +#include "xml/xmlconfig.h" #include "message.h" #include "io/nifstream.h" @@ -225,27 +226,27 @@ void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) QString arg = parentPrefix( data.arg() ); QString tmp = data.temp(); - if ( tmp == "TEMPLATE" ) { + if ( tmp == XMLTMPL ) { NifItem * tItem = branch; - while ( tmp == "TEMPLATE" && tItem->parent() ) { + while ( tmp == XMLTMPL && tItem->parent() ) { tItem = tItem->parent(); tmp = tItem->temp(); } } for ( NifData & d : compound->types ) { - if ( d.type() == "TEMPLATE" ) { + if ( d.type() == XMLTMPL ) { d.setType( tmp ); d.value.changeType( NifValue::type( tmp ) ); } - if ( d.arg() == "ARG" ) d.setArg( data.arg() ); - if ( d.arr1() == "ARG" ) d.setArr1( arg ); - if ( d.arr2() == "ARG" ) d.setArr2( arg ); + if ( d.arg() == XMLARG ) d.setArg( data.arg() ); + if ( d.arr1() == XMLARG ) d.setArr1( arg ); + if ( d.arr2() == XMLARG ) d.setArr2( arg ); - if ( d.cond().contains( "ARG" ) ) { - QString x = d.cond(); x.replace( x.indexOf( "ARG" ), 5, arg ); d.setCond( x ); + if ( d.cond().contains( XMLARG ) ) { + QString x = d.cond(); x.replace( x.indexOf( XMLARG ), 5, arg ); d.setCond( x ); } insertType( branch, d ); diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 8104a1b86..d62f71e1d 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -32,6 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifmodel.h" +#include "xml/xmlconfig.h" #include "message.h" #include "spellbook.h" #include "data/niftypes.h" @@ -435,7 +436,7 @@ NifItem * NifModel::getItem( NifItem * item, const QString & name ) const if ( !item || item == root ) return nullptr; - if ( item->isArray() || item->parent()->isArray() ) { + //if ( item->isArray() || item->parent()->isArray() ) { int slash = name.indexOf( QLatin1String("\\") ); if ( slash > 0 ) { QString left = name.left( slash ); @@ -447,7 +448,7 @@ NifItem * NifModel::getItem( NifItem * item, const QString & name ) const return getItem( getItem( item, left ), right ); } - } + //} for ( auto child : item->children() ) { if ( child && child->name() == name && evalCondition( child ) ) @@ -1103,25 +1104,24 @@ void NifModel::insertType( NifItem * parent, const NifData & data, int at ) insertType( parent, d ); } } else if ( data.isTemplated() ) { - QLatin1String tmpl( "TEMPLATE" ); QString tmp = parent->temp(); NifItem * tItem = parent; - while ( tmp == tmpl && tItem->parent() ) { + while ( tmp == XMLTMPL && tItem->parent() ) { tItem = tItem->parent(); tmp = tItem->temp(); } NifData d( data ); - if ( d.type() == tmpl ) { + if ( d.type() == XMLTMPL ) { d.value.changeType( NifValue::type( tmp ) ); d.setType( tmp ); // The templates are now filled d.setTemplated( false ); } - if ( d.temp() == tmpl ) + if ( d.temp() == XMLTMPL ) d.setTemp( tmp ); insertType( parent, d, at ); @@ -1208,7 +1208,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const if ( !item->temp().isEmpty() ) { NifItem * i = item; - while ( i && i->temp() == "TEMPLATE" ) + while ( i && i->temp() == XMLTMPL ) i = i->parent(); return QString( "%1<%2>" ).arg( item->type(), i ? i->temp() : QString() ); @@ -2316,7 +2316,7 @@ bool NifModel::loadHeader( NifItem * header, NifIStream & stream ) return false; set( header, "User Version", 0 ); - set( header, "User Version 2", 0 ); + set( getItem(header, "BS Header"), "BS Version", 0 ); invalidateConditions( header, false ); diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 41d777f4b..0c6fdb603 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -42,7 +42,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include - class SpellBook; class QUndoStack; @@ -284,7 +283,7 @@ class NifModel final : public BaseModel bool checkVersion( quint32 since, quint32 until ) const; quint32 getUserVersion() const { return get( getHeader(), "User Version" ); } - quint32 getUserVersion2() const { return get( getHeader(), "User Version 2" ); } + quint32 getUserVersion2() const { return get( getItem( getHeaderItem(), "BS Header" ), "BS Version" ); } QString string( const QModelIndex & index, bool extraInfo = false ) const; QString string( const QModelIndex & index, const QString & name, bool extraInfo = false ) const; diff --git a/src/xml/kfmxml.cpp b/src/xml/kfmxml.cpp index 878fd0e0e..decd454a0 100644 --- a/src/xml/kfmxml.cpp +++ b/src/xml/kfmxml.cpp @@ -30,6 +30,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ +#include "xml/xmlconfig.h" #include "message.h" #include "model/kfmmodel.h" @@ -152,7 +153,7 @@ class KfmXmlHandler final : public QXmlDefaultHandler KfmModel::version2number( ver2 ) ); - bool isTemplated = (type == "TEMPLATE" || tmpl == "TEMPLATE"); + bool isTemplated = (type == XMLTMPL || tmpl == XMLTMPL); bool isCompound = KfmModel::compounds.contains( type ); bool isArray = !arr1.isEmpty(); bool isMultiArray = !arr2.isEmpty(); @@ -217,12 +218,12 @@ class KfmXmlHandler final : public QXmlDefaultHandler bool checkType( const NifData & data ) { - return KfmModel::compounds.contains( data.type() ) || NifValue::type( data.type() ) != NifValue::tNone || data.type() == "TEMPLATE"; + return KfmModel::compounds.contains( data.type() ) || NifValue::type( data.type() ) != NifValue::tNone || data.type() == XMLTMPL; } bool checkTemp( const NifData & data ) { - return data.temp().isEmpty() || NifValue::type( data.temp() ) != NifValue::tNone || data.temp() == "TEMPLATE"; + return data.temp().isEmpty() || NifValue::type( data.temp() ) != NifValue::tNone || data.temp() == XMLTMPL; } bool endDocument() override final diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 182e0629c..7675ecde9 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -30,6 +30,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ +#include "xmlconfig.h" #include "message.h" #include "data/niftypes.h" #include "model/nifmodel.h" @@ -51,6 +52,12 @@ QHash NifModel::fixedCompounds; QHash NifModel::blocks; QMap NifModel::blockHashes; + +// Current token attribute list +QString attrlist; +// Token storage +QMap>> tokens; + //! Parses nif.xml class NifXmlHandler final : public QXmlDefaultHandler { @@ -69,7 +76,11 @@ class NifXmlHandler final : public QXmlDefaultHandler tagBasic, tagEnum, tagOption, - tagBitFlag + tagBitFlag, + tagBitfield, + tagMember, + tagToken, + tagTokenTag }; //! i18n wrapper for various strings @@ -92,6 +103,11 @@ class NifXmlHandler final : public QXmlDefaultHandler tags.insert( "enum", tagEnum ); tags.insert( "option", tagOption ); tags.insert( "bitflags", tagBitFlag ); + tags.insert( "token", tagToken ); + tags.insert( "bitfield", tagBitfield ); + tags.insert( "member", tagMember ); + + tokens.clear(); } //! Current position on stack @@ -136,6 +152,16 @@ class NifXmlHandler final : public QXmlDefaultHandler return stack[--depth]; } + QString token_replace( const QXmlAttributes & list, const QString & attr ) + { + QString str = list.value(attr); + if ( tokens.contains( attr ) ) { + for ( const auto & p : tokens[attr] ) + str.replace( p.first, p.second ); + } + return str; + } + //! Reimplemented from QXmlContentHandler /*! * \param Namespace (unused) @@ -150,8 +176,18 @@ class NifXmlHandler final : public QXmlDefaultHandler Tag x = tags.value( tagid ); + // Token Replace lambda, use get(x) instead of list.value(x) + auto get = [this, &list]( const QString & attr ) { + return token_replace( list, attr ); + }; + + if ( x == tagToken ) + tags.insert( list.value( "name" ), tagTokenTag ); + if ( x == tagNone ) - err( tr( "error unknown element '%1'" ).arg( tagid ) ); + x = tags.value( tagid ); + if ( x == tagNone ) + err( tr( "error unknown element '%1'" ).arg( tagid ) ); if ( depth == 0 ) { if ( x != tagFile ) @@ -237,6 +273,19 @@ class NifXmlHandler final : public QXmlDefaultHandler NifValue::registerEnumType( typId, flags ); } break; + case tagBitfield: + { + typId = list.value( "name" ); + typTxt = QString(); + QString storage = list.value( "storage" ); + + if ( typId.isEmpty() || storage.isEmpty() ) + err( tr( "bitfield definition must have a name and a known storage type" ) ); + + if ( !NifValue::registerAlias( typId, storage ) ) + err( tr( "failed to register alias %1 for enum type %2" ).arg( storage, typId ) ); + } + break; case tagVersion: { int v = NifModel::version2number( list.value( "num" ).trimmed() ); @@ -247,6 +296,11 @@ class NifXmlHandler final : public QXmlDefaultHandler err( tr( "invalid version tag" ) ); } break; + case tagToken: + { + attrlist = list.value( "attrs" ); + } + break; default: err( tr( "expected basic, enum, compound, niobject or version got %1 instead" ).arg( tagid ) ); } @@ -268,19 +322,30 @@ class NifXmlHandler final : public QXmlDefaultHandler { QString type = list.value( "type" ); QString tmpl = list.value( "template" ); - QString arg = list.value( "arg" ); - QString arr1 = list.value( "arr1" ); - QString arr2 = list.value( "arr2" ); - QString cond = list.value( "cond" ); + QString arg = get( "arg" ); + QString arr1 = get( "arr1" ); + QString arr2 = get( "arr2" ); + QString cond = get( "cond" ); QString ver1 = list.value( "ver1" ); QString ver2 = list.value( "ver2" ); QString abs = list.value( "abstract" ); QString bin = list.value( "binary" ); - QString vercond = list.value( "vercond" ); - QString userver = list.value( "userver" ); - QString userver2 = list.value( "userver2" ); + QString vercond = get( "vercond" ); + QString defval = get( "default" ); + + QString onlyT = list.value( "onlyT" ); + QString excludeT = list.value( "excludeT" ); + if ( !onlyT.isEmpty() || !excludeT.isEmpty() ) { + Q_ASSERT( cond.isEmpty() ); + Q_ASSERT( onlyT.isEmpty() != excludeT.isEmpty() ); + if ( !onlyT.isEmpty() ) + cond = onlyT; + else + cond = QString( "!%1" ).arg( excludeT ); + } - bool isTemplated = (type == "TEMPLATE" || tmpl == "TEMPLATE"); + + bool isTemplated = (type == XMLTMPL || tmpl == XMLTMPL); bool isCompound = NifModel::compounds.contains( type ); bool isArray = !arr1.isEmpty(); bool isMultiArray = !arr2.isEmpty(); @@ -305,8 +370,7 @@ class NifXmlHandler final : public QXmlDefaultHandler isMixin = mixinTypes.contains( type ); // The must not use any attributes other than name/type isMixin = isMixin && !isTemplated && !isArray; - isMixin = isMixin && cond.isEmpty() && ver1.isEmpty() && ver2.isEmpty() - && vercond.isEmpty() && userver.isEmpty() && userver2.isEmpty(); + isMixin = isMixin && cond.isEmpty() && ver1.isEmpty() && ver2.isEmpty() && vercond.isEmpty(); isCompound = !isMixin; } @@ -331,15 +395,14 @@ class NifXmlHandler final : public QXmlDefaultHandler ); // Set data flags - data.setAbstract( abs == "1" ); - data.setBinary( bin == "1" ); + data.setAbstract( abs == "1" || abs == "true" ); + data.setBinary( bin == "1" || bin == "true" ); data.setTemplated( isTemplated ); data.setIsCompound( isCompound ); data.setIsArray( isArray ); data.setIsMultiArray( isMultiArray ); data.setIsMixin( isMixin ); - QString defval = list.value( "default" ); if ( !defval.isEmpty() ) { bool ok; @@ -352,20 +415,6 @@ class NifXmlHandler final : public QXmlDefaultHandler } } - if ( !userver.isEmpty() ) { - if ( !vercond.isEmpty() ) - vercond += " && "; - - vercond += QString( "(User Version == %1)" ).arg( userver ); - } - - if ( !userver2.isEmpty() ) { - if ( !vercond.isEmpty() ) - vercond += " && "; - - vercond += QString( "(User Version 2 == %1)" ).arg( userver2 ); - } - if ( !vercond.isEmpty() ) { data.setVerCond( vercond ); } @@ -407,6 +456,34 @@ class NifXmlHandler final : public QXmlDefaultHandler err( tr( "only option tags allowed in enum declaration" ) ); } + break; + case tagBitfield: + push( x ); + switch ( x ) { + case tagMember: + break; + default: + err( tr( "only member tags allowed in bitfield declaration" ) ); + } + break; + case tagToken: + push( x ); + switch ( x ) { + case tagTokenTag: + { + auto tok = list.value( "token" ); + auto str = list.value( "string" ); + for ( const auto & attr : attrlist.split( " " ) ) { + if ( !tokens.contains(attr) ) + tokens[attr] = {}; + + tokens[attr].append( {tok, str} ); + } + } + break; + default: + err( tr( "only token tags allowed in token declaration" ) );; + } break; default: err( tr( "error unhandled tag %1" ).arg( tagid ) ); @@ -527,7 +604,7 @@ class NifXmlHandler final : public QXmlDefaultHandler { return ( NifModel::compounds.contains( d.type() ) || NifValue::type( d.type() ) != NifValue::tNone - || d.type() == "TEMPLATE" + || d.type() == XMLTMPL ); } @@ -536,7 +613,7 @@ class NifXmlHandler final : public QXmlDefaultHandler { return ( d.temp().isEmpty() || NifValue::type( d.temp() ) != NifValue::tNone - || d.temp() == "TEMPLATE" + || d.temp() == XMLTMPL || NifModel::blocks.contains( d.temp() ) || NifModel::compounds.contains( d.temp() ) ); diff --git a/src/xml/xmlconfig.h b/src/xml/xmlconfig.h new file mode 100644 index 000000000..a4a9d8487 --- /dev/null +++ b/src/xml/xmlconfig.h @@ -0,0 +1,9 @@ +#ifndef XMLCONFIG_H +#define XMLCONFIG_H + +#include + +#define XMLTMPL QLatin1String("#T#") +#define XMLARG QLatin1String("#ARG#") + +#endif // XMLCONFIG_H From bb8a755138c342c8ce5ecb6e8aaa7cdd1f180692 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 10 Jun 2018 15:20:02 -0400 Subject: [PATCH 008/118] [GL] Fallback to msn shader for FO4 BTR files --- res/shaders/fo4_default.prog | 1 + 1 file changed, 1 insertion(+) diff --git a/res/shaders/fo4_default.prog b/res/shaders/fo4_default.prog index 246d69bbf..22a60e3ac 100644 --- a/res/shaders/fo4_default.prog +++ b/res/shaders/fo4_default.prog @@ -6,6 +6,7 @@ checkgroup begin and check BSLightingShaderProperty #check BSLightingShaderProperty/Skyrim Shader Type != 1 #check BSLightingShaderProperty/Skyrim Shader Type != 16 + check BSLightingShaderProperty/Skyrim Shader Type != 18 checkgroup begin or check BSTriShape check BSSubIndexTriShape From 2cfb0dadff4a101e59236f93badfff7b31d1254f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 11 Nov 2018 00:57:50 -0500 Subject: [PATCH 009/118] [XML] nifxml 1.0 sync 2 --- src/data/nifitem.h | 14 ++- src/data/niftypes.h | 10 ++ src/data/nifvalue.cpp | 40 ++++++-- src/data/nifvalue.h | 122 +++++++++++----------- src/gl/bsshape.cpp | 14 +-- src/gl/bsshape.h | 3 +- src/gl/controllers.cpp | 38 +++---- src/gl/glmesh.cpp | 10 +- src/gl/glnode.cpp | 195 +++++++++++++++--------------------- src/gl/gltools.cpp | 100 +++++++++++++++--- src/gl/gltools.h | 13 +++ src/gl/renderer.cpp | 2 +- src/io/nifstream.cpp | 21 +++- src/lib/importex/3ds.cpp | 8 +- src/lib/importex/col.cpp | 2 +- src/lib/importex/obj.cpp | 12 +-- src/model/basemodel.cpp | 12 ++- src/spells/flags.cpp | 4 +- src/spells/havok.cpp | 48 +++------ src/spells/mesh.cpp | 11 +- src/spells/mesh.h | 2 +- src/spells/moppcode.cpp | 10 +- src/spells/normals.cpp | 4 +- src/spells/skeleton.cpp | 19 ++-- src/spells/strippify.cpp | 44 ++------ src/spells/tangentspace.cpp | 19 +--- src/spells/texture.cpp | 4 +- src/spells/transform.cpp | 26 ++--- src/ui/widgets/uvedit.cpp | 14 ++- src/xml/nifexpr.cpp | 10 +- src/xml/nifexpr.h | 17 +++- src/xml/nifxml.cpp | 34 +++++-- 32 files changed, 478 insertions(+), 404 deletions(-) diff --git a/src/data/nifitem.h b/src/data/nifitem.h index 7b7d9f868..c5c03aeda 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -72,7 +72,7 @@ class NifSharedData final : public QSharedData NifSharedData( const QString & n, const QString & t, const QString & tt, const QString & a, const QString & a1, const QString & a2, const QString & c, quint32 v1, quint32 v2, NifSharedData::DataFlags f ) - : QSharedData(), name( n ), type( t ), temp( tt ), arg( a ), arr1( a1 ), arr2( a2 ), + : QSharedData(), name( n ), type( t ), temp( tt ), arg( a ), argexpr( a ), arr1( a1 ), arr2( a2 ), cond( c ), ver1( v1 ), ver2( v2 ), condexpr( c ), arr1expr( a1 ), flags( f ) { } @@ -94,6 +94,8 @@ class NifSharedData final : public QSharedData QString temp; //! Argument. QString arg; + //! Arg as an expression. + NifExpr argexpr; //! First array length. QString arr1; //! Second array length. @@ -156,6 +158,8 @@ class NifData inline quint32 ver2() const { return d->ver2; } //! Get the text description of the data. inline const QString & text() const { return d->text; } + //! Get the arg attribute of the data, as an expression. + inline const NifExpr & argexpr() const { return d->argexpr; } //! Get the condition attribute of the data, as an expression. inline const NifExpr & condexpr() const { return d->condexpr; } //! Get the first array length of the data, as an expression. @@ -188,7 +192,11 @@ class NifData //! Sets the template type of the data. void setTemp( const QString & temp ) { d->temp = temp; } //! Sets the argument of the data. - void setArg( const QString & arg ) { d->arg = arg; } + void setArg( const QString & arg ) + { + d->arg = arg; + d->argexpr = NifExpr( arg ); + } //! Sets the first array length of the data. void setArr1( const QString & arr1 ) { @@ -617,6 +625,8 @@ class NifItem //! Return the description text of the data inline QString text() const { return itemData.text(); } + //! Return the condition attribute of the data, as an expression + inline const NifExpr & argexpr() const { return itemData.argexpr(); } //! Return the condition attribute of the data, as an expression inline const NifExpr & condexpr() const { return itemData.condexpr(); } //! Return the arr1 attribute of the data, as an expression diff --git a/src/data/niftypes.h b/src/data/niftypes.h index eab32f167..3b1d8d005 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -1682,6 +1682,16 @@ class BSVertexDesc { } + BSVertexDesc( quint64 val ) + { + desc = val; + } + + quint64 Value() const + { + return desc; + } + // Sets a specific flag void SetFlag( VertexFlags flag ) { diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index 07ebece4e..1e9e5a7f6 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -89,7 +89,7 @@ void NifValue::initialize() typeMap.insert( "float", NifValue::tFloat ); typeMap.insert( "SizedString", NifValue::tSizedString ); typeMap.insert( "Text", NifValue::tText ); - typeMap.insert( "ShortString", NifValue::tShortString ); + typeMap.insert( "ExportString", NifValue::tShortString ); typeMap.insert( "Color3", NifValue::tColor3 ); typeMap.insert( "Color4", NifValue::tColor4 ); typeMap.insert( "Vector4", NifValue::tVector4 ); @@ -111,7 +111,7 @@ void NifValue::initialize() typeMap.insert( "LineString", NifValue::tLineString ); typeMap.insert( "StringPalette", NifValue::tStringPalette ); typeMap.insert( "StringOffset", NifValue::tStringOffset ); - typeMap.insert( "StringIndex", NifValue::tStringIndex ); + typeMap.insert( "NiFixedString", NifValue::tStringIndex ); typeMap.insert( "BlockTypeIndex", NifValue::tBlockTypeIndex ); typeMap.insert( "char8string", NifValue::tChar8String ); typeMap.insert( "string", NifValue::tString ); @@ -123,7 +123,7 @@ void NifValue::initialize() typeMap.insert( "HalfVector2", NifValue::tHalfVector2 ); typeMap.insert( "HalfTexCoord", NifValue::tHalfVector2 ); typeMap.insert( "ByteColor4", NifValue::tByteColor4 ); - typeMap.insert( "BSVertexDesc", NifValue::tBSVertexDesc ); + //typeMap.insert( "BSVertexDesc", NifValue::tBSVertexDesc ); enumMap.clear(); } @@ -415,7 +415,7 @@ void NifValue::clear() } typ = tNone; - val.u32 = 0; + val.u64 = 0; } void NifValue::changeType( Type t ) @@ -490,7 +490,7 @@ void NifValue::changeType( Type t ) val.data = new QByteArray(); return; default: - val.u32 = 0; + val.u64 = 0; return; } } @@ -586,6 +586,11 @@ bool NifValue::operator==( const NifValue & other ) const case tUpLink: return val.i32 == other.val.i32; + case tInt64: + return val.i64 == other.val.i64; + case tUInt64: + return val.u64 == other.val.u64; + case tFloat: case tHfloat: return val.f32 == other.val.f32; @@ -773,6 +778,7 @@ bool NifValue::setFromString( const QString & s ) switch ( typ ) { case tBool: + val.u64 = 0; if ( s == "yes" || s == "true" ) { val.u32 = 1; @@ -786,7 +792,7 @@ bool NifValue::setFromString( const QString & s ) } case tByte: - val.u32 = 0; + val.u64 = 0; val.u08 = s.toUInt( &ok, 0 ); return ok; case tWord: @@ -794,7 +800,7 @@ bool NifValue::setFromString( const QString & s ) case tStringOffset: case tBlockTypeIndex: case tShort: - val.u32 = 0; + val.u64 = 0; val.u16 = s.toShort( &ok, 0 ); return ok; case tInt: @@ -802,19 +808,30 @@ bool NifValue::setFromString( const QString & s ) return ok; case tUInt: case tULittle32: + val.u64 = 0; val.u32 = s.toUInt( &ok, 0 ); return ok; + case tInt64: + val.i64 = s.toLongLong( &ok, 0 ); + return ok; + case tUInt64: + val.u64 = s.toULongLong( &ok, 0 ); + return ok; case tStringIndex: + val.u64 = 0; val.u32 = s.toUInt( &ok ); return ok; case tLink: case tUpLink: + val.u64 = 0; val.i32 = s.toInt( &ok ); return ok; case tFloat: + val.u64 = 0; val.f32 = s.toDouble( &ok ); return ok; case tHfloat: + val.u64 = 0; val.f32 = s.toDouble( &ok ); return ok; case tString: @@ -834,6 +851,7 @@ bool NifValue::setFromString( const QString & s ) static_cast( val.data )->fromQColor( QColor( s ) ); return true; case tFileVersion: + val.u64 = 0; val.u32 = NifModel::version2number( s ); return val.u32 != 0; case tVector2: @@ -880,6 +898,10 @@ QString NifValue::toString() const return QString::number( val.u32 ); case tStringIndex: return QString::number( val.u32 ); + case tInt64: + return QString::number( val.i64 ); + case tUInt64: + return QString::number( val.u64 ); case tShort: return QString::number( (short)val.u16 ); case tInt: @@ -888,7 +910,9 @@ QString NifValue::toString() const case tUpLink: return QString::number( val.i32 ); case tFloat: - return NumOrMinMax( val.f32, 'f', 6 ); + if ( val.f32 == 0.0 ) + return QString("0.0"); + return NumOrMinMax( val.f32, 'G', 6 ); case tHfloat: return QString::number( val.f32, 'f', 4 ); case tString: diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 7ef692eb8..45ede4a74 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -80,55 +80,55 @@ class NifValue enum Type { // all count types should come between tBool and tUInt - tBool = 0, - tByte = 1, - tWord = 2, - tFlags = 3, - tStringOffset = 4, - tStringIndex = 5, - tBlockTypeIndex = 6, - tInt = 7, - tShort = 8, - tULittle32 = 9, - tInt64 = 10, - tUInt64 = 11, - tUInt = 12, - // - tLink = 13, - tUpLink = 14, - tFloat = 15, + tBool = 0, + tByte, + tWord, + tFlags, + tStringOffset, + tStringIndex, + tBlockTypeIndex, + tInt, + tShort, + tULittle32, + tInt64, + tUInt64, + tUInt, + // all count types should come between tBool and tUInt + tLink, + tUpLink, + tFloat, + // all string types should come between tSizedString and tChar8String + tSizedString, + tText, + tShortString, + tHeaderString, + tLineString, + tChar8String, // all string types should come between tSizedString and tChar8String - tSizedString = 16, - tText = 17, - tShortString = 18, - tHeaderString = 19, - tLineString = 20, - tChar8String = 21, - // - tColor3 = 22, - tColor4 = 23, - tVector3 = 24, - tQuat = 25, - tQuatXYZW = 26, - tMatrix = 27, - tMatrix4 = 28, - tVector2 = 29, - tVector4 = 30, - tTriangle = 31, - tFileVersion = 32, - tByteArray = 33, - tStringPalette = 34, - tString = 35, //!< not a regular string: an integer for nif versions 20.1.0.3 and up - tFilePath = 36, //!< not a string: requires special handling for slash/backslash etc. - tByteMatrix = 37, - tBlob = 38, - tHfloat = 39, - tHalfVector3 = 40, - tByteVector3 = 41, - tHalfVector2 = 42, - tByteColor4 = 43, - tBSVertexDesc = 44, - tNone = 0xff + tColor3, + tColor4, + tVector3, + tQuat, + tQuatXYZW, + tMatrix, + tMatrix4, + tVector2, + tVector4, + tTriangle, + tFileVersion, + tByteArray, + tStringPalette, + tString, //!< not a regular string: an integer for nif versions 20.1.0.3 and up + tFilePath, //!< not a string: requires special handling for slash/backslash etc. + tByteMatrix, + tBlob, + tHfloat, + tHalfVector3, + tByteVector3, + tHalfVector2, + tByteColor4, + tBSVertexDesc, + tNone= 0xff }; enum EnumType @@ -286,7 +286,7 @@ class NifValue //! Return the value of the data as a QColor, if applicable. QColor toColor() const; //! Return the value of the data as a count. - quint32 toCount() const; + quint64 toCount() const; //! Return the value of the data as a float. float toFloat() const; //! Return the value of the data as a link, if applicable. @@ -303,7 +303,7 @@ class NifValue * * @return True if applicable, false otherwise */ - bool setCount( quint32 ); + bool setCount( quint64 ); /*! Set this value to a float. * @@ -353,6 +353,8 @@ class NifValue quint16 u16; quint32 u32; qint32 i32; + quint64 u64; + qint64 i64; float f32; void * data; }; @@ -406,10 +408,10 @@ Q_DECLARE_METATYPE( NifValue ) // documented above; should this really be inlined? // GCC only allows type punning via union (http://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Optimize-Options.html#index-fstrict_002daliasing-550) // This also works on GCC 3.4.5 -inline quint32 NifValue::toCount() const +inline quint64 NifValue::toCount() const { if ( isCount() || isFloat() ) - return val.u32; + return val.u64; return 0; } @@ -438,10 +440,10 @@ inline quint32 NifValue::toFileVersion() const return 0; } -inline bool NifValue::setCount( quint32 c ) +inline bool NifValue::setCount( quint64 c ) { if ( isCount() ) { - val.u32 = c; return true; + val.u64 = c; return true; } return false; @@ -499,6 +501,14 @@ template <> inline bool NifValue::get() const { return toCount(); } +template <> inline qint64 NifValue::get() const +{ + return toCount(); +} +template <> inline quint64 NifValue::get() const +{ + return toCount(); +} template <> inline qint32 NifValue::get() const { return toCount(); @@ -623,7 +633,7 @@ template <> inline ByteMatrix * NifValue::get() const } template <> inline BSVertexDesc NifValue::get() const { - return getType( tBSVertexDesc ); + return BSVertexDesc(toCount()); } //! Set the data from a boolean. Return true if successful. @@ -758,7 +768,7 @@ template <> inline bool NifValue::set( const Quat & x ) //! Set the data from a BSVertexDesc. Return true if successful. template <> inline bool NifValue::set( const BSVertexDesc & x ) { - return setType( tBSVertexDesc, x ); + return setCount(x.Value()); } diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 7a1a43943..f9cb6d505 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -89,6 +89,9 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) updateSkin = isSkinned; transformRigid = !isSkinned; + if ( updateBounds ) + dataBound = BoundSphere( nif, iBlock ); + int dataSize = 0; if ( !isDataOnSkin ) { iVertData = nif->getIndex( iBlock, "Vertex Data" ); @@ -121,13 +124,6 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) numVerts = dataSize / vertexSize; } - - auto bsphere = nif->getIndex( iBlock, "Bounding Sphere" ); - if ( bsphere.isValid() ) { - bsphereCenter = nif->get( bsphere, "Center" ); - bsphereRadius = nif->get( bsphere, "Radius" ); - } - if ( iBlock == index && dataSize > 0 ) { verts.clear(); norms.clear(); @@ -179,7 +175,7 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) triangles = nif->getArray( iTriData ); triangles = triangles.mid( 0, numTris ); } else { - auto partIdx = nif->getIndex( iSkinPart, "Partition" ); + auto partIdx = nif->getIndex( iSkinPart, "Partitions" ); for ( int i = 0; i < nif->rowCount( partIdx ); i++ ) triangles << nif->getArray( nif->index( i, 0, partIdx ), "Triangles" ); } @@ -904,7 +900,7 @@ BoundSphere BSShape::bounds() const if ( verts.count() ) { boundSphere = BoundSphere( verts ); } else { - boundSphere = BoundSphere( bsphereCenter, bsphereRadius ); + boundSphere = dataBound; } } diff --git a/src/gl/bsshape.h b/src/gl/bsshape.h index 1b2f9d19a..51922f764 100644 --- a/src/gl/bsshape.h +++ b/src/gl/bsshape.h @@ -53,8 +53,7 @@ class BSShape : public Shape int numVerts = 0; int numTris = 0; - Vector3 bsphereCenter; - float bsphereRadius = 0.0; + BoundSphere dataBound; }; #endif // BSSHAPE_H diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 5ffb58b7a..5435a468f 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -564,24 +564,24 @@ bool ParticleController::update( const NifModel * nif, const QModelIndex & index emitNode = target->scene->getNode( nif, nif->getBlock( nif->getLink( iBlock, "Emitter" ) ) ); emitStart = nif->get( iBlock, "Emit Start Time" ); emitStop = nif->get( iBlock, "Emit Stop Time" ); - emitRate = nif->get( iBlock, "Emit Rate" ); - emitRadius = nif->get( iBlock, "Start Random" ); + emitRate = nif->get( iBlock, "Birth Rate" ); + emitRadius = nif->get( iBlock, "Emitter Dimensions" ); emitAccu = 0; emitLast = emitStart; spd = nif->get( iBlock, "Speed" ); - spdRnd = nif->get( iBlock, "Speed Random" ); + spdRnd = nif->get( iBlock, "Speed Variation" ); ttl = nif->get( iBlock, "Lifetime" ); - ttlRnd = nif->get( iBlock, "Lifetime Random" ); + ttlRnd = nif->get( iBlock, "Lifetime Variation" ); - inc = nif->get( iBlock, "Vertical Direction" ); - incRnd = nif->get( iBlock, "Vertical Angle" ); + inc = nif->get( iBlock, "Declination" ); + incRnd = nif->get( iBlock, "Declination Variation" ); - dec = nif->get( iBlock, "Horizontal Direction" ); - decRnd = nif->get( iBlock, "Horizontal Angle" ); + dec = nif->get( iBlock, "Planar Angle" ); + decRnd = nif->get( iBlock, "Planar Angle Variation" ); - size = nif->get( iBlock, "Size" ); + size = nif->get( iBlock, "Initial Size" ); grow = 0.0; fade = 0.0; @@ -599,10 +599,10 @@ bool ParticleController::update( const NifModel * nif, const QModelIndex & index for ( int p = 0; p < numValid && p < nif->rowCount( iParticles ); p++ ) { Particle particle; particle.velocity = nif->get( iParticles.child( p, 0 ), "Velocity" ); - particle.lifetime = nif->get( iParticles.child( p, 0 ), "Lifetime" ); - particle.lifespan = nif->get( iParticles.child( p, 0 ), "Lifespan" ); - particle.lasttime = nif->get( iParticles.child( p, 0 ), "Timestamp" ); - particle.vertex = nif->get( iParticles.child( p, 0 ), "Vertex ID" ); + particle.lifetime = nif->get( iParticles.child( p, 0 ), "Age" ); + particle.lifespan = nif->get( iParticles.child( p, 0 ), "Life Span" ); + particle.lasttime = nif->get( iParticles.child( p, 0 ), "Last Update" ); + particle.vertex = nif->get( iParticles.child( p, 0 ), "Code" ); // Display saved particle start on initial load list.append( particle ); } @@ -610,14 +610,14 @@ bool ParticleController::update( const NifModel * nif, const QModelIndex & index //} } - if ( (nif->get( iBlock, "Emit Flags" ) & 1) == 0 ) { + if ( nif->get( iBlock, "Use Birth Rate" ) == 0 ) { emitRate = emitMax / (ttl + ttlRnd / 2); } iExtras.clear(); grav.clear(); iColorKeys = QModelIndex(); - QModelIndex iExtra = nif->getBlock( nif->getLink( iBlock, "Particle Extra" ) ); + QModelIndex iExtra = nif->getBlock( nif->getLink( iBlock, "Particle Modifier" ) ); while ( iExtra.isValid() ) { iExtras.append( iExtra ); @@ -1027,7 +1027,7 @@ void EffectFloatController::updateTime( float time ) bool EffectFloatController::update( const NifModel * nif, const QModelIndex & index ) { if ( Controller::update( nif, index ) ) { - variable = EffectFloat::Variable( nif->get( iBlock, "Type of Controlled Variable" ) ); + variable = EffectFloat::Variable( nif->get( iBlock, "Controlled Variable" ) ); return true; } @@ -1068,7 +1068,7 @@ void EffectColorController::updateTime( float time ) bool EffectColorController::update( const NifModel * nif, const QModelIndex & index ) { if ( Controller::update( nif, index ) ) { - variable = nif->get( iBlock, "Type of Controlled Color" ); + variable = nif->get( iBlock, "Controlled Color" ); return true; } @@ -1130,7 +1130,7 @@ void LightingFloatController::updateTime( float time ) bool LightingFloatController::update( const NifModel * nif, const QModelIndex & index ) { if ( Controller::update( nif, index ) ) { - variable = LightingFloat::Variable(nif->get( iBlock, "Type of Controlled Variable" )); + variable = LightingFloat::Variable(nif->get( iBlock, "Controlled Variable" )); return true; } @@ -1169,7 +1169,7 @@ void LightingColorController::updateTime( float time ) bool LightingColorController::update( const NifModel * nif, const QModelIndex & index ) { if ( Controller::update( nif, index ) ) { - variable = nif->get( iBlock, "Type of Controlled Color" ); + variable = nif->get( iBlock, "Controlled Color" ); return true; } diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 794cb7359..32d0490cd 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -185,7 +185,7 @@ void Shape::boneSphere( const NifModel * nif, const QModelIndex & index ) const Transform t = (scene->options & Scene::DoSkinning) ? viewTrans() : Transform(); t = t * skeletonTrans * bone->localTrans( 0 ) * boneT; - auto bSphere = BoundSphere( nif, nif->getIndex( index, "Bounding Sphere" ) ); + auto bSphere = BoundSphere( nif, index ); if ( bSphere.radius > 0.0 ) { glColor4f( 1, 1, 1, 0.33 ); auto pos = boneT.rotation.inverted() * (bSphere.center - boneT.translation); @@ -680,10 +680,6 @@ void Mesh::transform() coords.clear(); QModelIndex uvcoord = nif->getIndex( iData, "UV Sets" ); - - if ( !uvcoord.isValid() ) - uvcoord = nif->getIndex( iData, "UV Sets 2" ); - if ( uvcoord.isValid() ) { for ( int r = 0; r < nif->rowCount( uvcoord ); r++ ) { TexCoords tc = nif->getArray( uvcoord.child( r, 0 ) ); @@ -806,7 +802,7 @@ void Mesh::transform() } if ( iSkinPart.isValid() && doSkinning ) { - QModelIndex idx = nif->getIndex( iSkinPart, "Skin Partition Blocks" ); + QModelIndex idx = nif->getIndex( iSkinPart, "Partitions" ); uint numTris = 0; uint numStrips = 0; @@ -1457,7 +1453,7 @@ void Mesh::drawSelection() const } } - if ( n == "Skin Partition Blocks" ) { + if ( n == "Partitions" ) { for ( int c = 0; c < partitions.count(); c++ ) { if ( c == i ) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 31f03bbed..e0aedac5d 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -492,20 +492,18 @@ void Node::transform() const NifModel * nif = static_cast( iBlock.model() ); if ( iBlock.isValid() && nif ) { - // Scale up for Skyrim - float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; - QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); if ( nif->getUserVersion2() > 0 && iObject.isValid() ) { QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); if ( iBody.isValid() ) { Transform t; - t.scale = 7.0f; + t.scale = bhkScale( nif ); if ( nif->isNiBlock( iBody, "bhkRigidBodyT" ) ) { - t.rotation.fromQuat( nif->get( iBody, "Rotation" ) ); - t.translation = Vector3( nif->get( iBody, "Translation" ) * 7.0f * havokScale ); + auto cinfo = nif->getIndex( iBody, "Rigid Body Info" ); + t.rotation.fromQuat( nif->get( cinfo, "Rotation" ) ); + t.translation = Vector3( nif->get( cinfo, "Translation" ) * bhkScale( nif ) ); } scene->bhkBodyTrans.insert( nif->getBlockNumber( iBody ), worldTrans() * t ); @@ -781,9 +779,6 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackcheckVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; - //qDebug() << "draw shape" << nif->getBlockNumber( iShape ) << nif->itemName( iShape ); if ( name.endsWith( "ListShape" ) ) { @@ -815,7 +810,6 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlock( nif->getLink( iShape, "Shape" ) ), stack, scene, origin_color3fv ); @@ -826,7 +820,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackget( iShape, "Radius" ) * havokScale ); + drawSphere( Vector3(), nif->get( iShape, "Radius" ) ); } else if ( name == "bhkMultiSphereShape" ) { if ( Node::SELECTING ) { int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iShape ) ); @@ -845,7 +839,6 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackget( iShape, "Dimensions" ); - v *= havokScale; drawBox( v, -v ); } else if ( name == "bhkCapsuleShape" ) { if ( Node::SELECTING ) { @@ -853,10 +846,10 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackget( iShape, "First Point" ) * havokScale, nif->get( iShape, "Second Point" ) * havokScale, nif->get( iShape, "Radius" ) * havokScale ); + drawCapsule( nif->get( iShape, "First Point" ), nif->get( iShape, "Second Point" ), nif->get( iShape, "Radius" ) ); } else if ( name == "bhkNiTriStripsShape" ) { glPushMatrix(); - float s = 1.0f / 7.0f; + float s = bhkInvScale( nif ); glScalef( s, s, s ); if ( Node::SELECTING ) { @@ -881,7 +874,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlockNumber( iShape ) ); glColor4ubv( (GLubyte *)&s_nodeId ); @@ -1067,8 +1056,6 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackselMode & Scene::SelObject) ) return; - QList tBodies; - QModelIndex iBodies = nif->getIndex( iConstraint, "Entities" ); + Transform tBodyA; + Transform tBodyB; - if ( !iBodies.isValid() ) { + auto iEntityA = bhkGetEntity( nif, iConstraint, "Entity A" ); + auto iEntityB = bhkGetEntity( nif, iConstraint, "Entity B" ); + if ( !iEntityA.isValid() || !iEntityB.isValid() ) return; - } - for ( int r = 0; r < nif->rowCount( iBodies ); r++ ) { - qint32 l = nif->getLink( iBodies.child( r, 0 ) ); + auto linkA = nif->getLink( iEntityA ); + auto linkB = nif->getLink( iEntityB ); + if ( !scene->bhkBodyTrans.contains( linkA ) || !scene->bhkBodyTrans.contains( linkB ) ) + return; - if ( !scene->bhkBodyTrans.contains( l ) ) - return; // TODO: Make sure this is not supposed to be continue; + tBodyA = scene->bhkBodyTrans.value( linkA ); + tBodyB = scene->bhkBodyTrans.value( linkB ); - tBodies.append( scene->bhkBodyTrans.value( l ) ); - } + auto hkFactor = bhkScaleMult( nif ); + auto hkFactorInv = 1.0 / hkFactor; - if ( tBodies.count() != 2 ) - return; + tBodyA.scale = tBodyA.scale * hkFactorInv; + tBodyB.scale = tBodyB.scale * hkFactorInv; Color3 color_a( 0.8f, 0.6f, 0.0f ); Color3 color_b( 0.6f, 0.8f, 0.0f ); @@ -1125,38 +1115,44 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c QString name = nif->itemName( iConstraint ); + QModelIndex iConstraintInfo; + if ( name == "bhkMalleableConstraint" || name == "bhkBreakableConstraint" ) { if ( nif->getIndex( iConstraint, "Ragdoll" ).isValid() ) { name = "bhkRagdollConstraint"; + iConstraintInfo = nif->getIndex( iConstraint, "Ragdoll" ); } else if ( nif->getIndex( iConstraint, "Limited Hinge" ).isValid() ) { name = "bhkLimitedHingeConstraint"; + iConstraintInfo = nif->getIndex( iConstraint, "Limited Hinge" ); } else if ( nif->getIndex( iConstraint, "Hinge" ).isValid() ) { name = "bhkHingeConstraint"; + iConstraintInfo = nif->getIndex( iConstraint, "Hinge" ); } else if ( nif->getIndex( iConstraint, "Stiff Spring" ).isValid() ) { name = "bhkStiffSpringConstraint"; + iConstraintInfo = nif->getIndex( iConstraint, "Stiff Spring" ); } + } else { + iConstraintInfo = nif->getIndex( iConstraint, "Constraint" ); + if ( !iConstraintInfo.isValid() ) + iConstraintInfo = iConstraint; } - if ( name == "bhkLimitedHingeConstraint" ) { - QModelIndex iHinge = nif->getIndex( iConstraint, "Limited Hinge" ); - if ( !iHinge.isValid() ) - iHinge = iConstraint; + Vector3 pivotA( nif->get( iConstraintInfo, "Pivot A" ) * hkFactor ); + Vector3 pivotB( nif->get( iConstraintInfo, "Pivot B" ) * hkFactor ); - const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); - const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); - - const Vector3 axisA( nif->get( iHinge, "Axis A" ) ); - const Vector3 axisA1( nif->get( iHinge, "Perp Axis In A1" ) ); - const Vector3 axisA2( nif->get( iHinge, "Perp Axis In A2" ) ); + if ( name == "bhkLimitedHingeConstraint" ) { + const Vector3 axisA( nif->get( iConstraintInfo, "Axis A" ) ); + const Vector3 axisA1( nif->get( iConstraintInfo, "Perp Axis In A1" ) ); + const Vector3 axisA2( nif->get( iConstraintInfo, "Perp Axis In A2" ) ); - const Vector3 axisB( nif->get( iHinge, "Axis B" ) ); - const Vector3 axisB2( nif->get( iHinge, "Perp Axis In B2" ) ); + const Vector3 axisB( nif->get( iConstraintInfo, "Axis B" ) ); + const Vector3 axisB2( nif->get( iConstraintInfo, "Perp Axis In B2" ) ); - const float minAngle = nif->get( iHinge, "Min Angle" ); - const float maxAngle = nif->get( iHinge, "Max Angle" ); + const float minAngle = nif->get( iConstraintInfo, "Min Angle" ); + const float maxAngle = nif->get( iConstraintInfo, "Max Angle" ); glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); + glMultMatrix( tBodyA ); if ( !Node::SELECTING ) glColor( color_a ); @@ -1165,12 +1161,12 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + axisA ); glEnd(); drawDashLine( pivotA, pivotA + axisA1, 14 ); drawDashLine( pivotA, pivotA + axisA2, 14 ); - drawCircle( pivotA, axisA, 1.0f ); + drawCircle( pivotA, axisA, 1.0 ); drawSolidArc( pivotA, axisA / 5, axisA2, axisA1, minAngle, maxAngle, 1.0f ); glPopMatrix(); glPushMatrix(); - glMultMatrix( tBodies.value( 1 ) ); + glMultMatrix( tBodyB ); if ( !Node::SELECTING ) glColor( color_b ); @@ -1183,8 +1179,8 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c drawSolidArc( pivotB, axisB / 7, axisB2, Vector3::crossproduct( axisB2, axisB ), minAngle, maxAngle, 1.01f ); glPopMatrix(); - glMultMatrix( tBodies.value( 0 ) ); - float angle = Vector3::angle( tBodies.value( 0 ).rotation * axisA2, tBodies.value( 1 ).rotation * axisB2 ); + glMultMatrix( tBodyA ); + float angle = Vector3::angle( tBodyA.rotation * axisA2, tBodyB.rotation * axisB2 ); if ( !Node::SELECTING ) glColor( color_a ); @@ -1194,18 +1190,11 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glVertex( pivotA + axisA1 * cosf( angle ) + axisA2 * sinf( angle ) ); glEnd(); } else if ( name == "bhkHingeConstraint" ) { - QModelIndex iHinge = nif->getIndex( iConstraint, "Hinge" ); - if ( !iHinge.isValid() ) - iHinge = iConstraint; - - const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); - const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); - - const Vector3 axisA1( nif->get( iHinge, "Perp Axis In A1" ) ); - const Vector3 axisA2( nif->get( iHinge, "Perp Axis In A2" ) ); + const Vector3 axisA1( nif->get( iConstraintInfo, "Perp Axis In A1" ) ); + const Vector3 axisA2( nif->get( iConstraintInfo, "Perp Axis In A2" ) ); const Vector3 axisA( Vector3::crossproduct( axisA1, axisA2 ) ); - const Vector3 axisB( nif->get( iHinge, "Axis B" ) ); + const Vector3 axisB( nif->get( iConstraintInfo, "Axis B" ) ); const Vector3 axisB1( axisB[1], axisB[2], axisB[0] ); const Vector3 axisB2( Vector3::crossproduct( axisB, axisB1 ) ); @@ -1223,8 +1212,8 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c } else if ( nif->checkVersion( 0x14020007, 0 ) ) { - Vector3 axisB1temp( nif->get( iHinge, "Perp Axis In B1" ) ); - Vector3 axisB2temp( nif->get( iHinge, "Perp Axis In B2" ) ); + Vector3 axisB1temp( nif->get( iConstraintInfo, "Perp Axis In B1" ) ); + Vector3 axisB2temp( nif->get( iConstraintInfo, "Perp Axis In B2" ) ); } const Vector3 axisB1( axisB1temp ); @@ -1235,7 +1224,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c const float maxAngle = (float)+PI; glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); + glMultMatrix( tBodyA ); if ( !Node::SELECTING ) glColor( color_a ); @@ -1246,7 +1235,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c drawSolidArc( pivotA, axisA / 5, axisA2, axisA1, minAngle, maxAngle, 1.0f, 16 ); glPopMatrix(); - glMultMatrix( tBodies.value( 1 ) ); + glMultMatrix( tBodyB ); if ( !Node::SELECTING ) glColor( color_b ); @@ -1255,45 +1244,32 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axisB ); glEnd(); drawSolidArc( pivotB, axisB / 7, axisB2, axisB1, minAngle, maxAngle, 1.01f, 16 ); } else if ( name == "bhkStiffSpringConstraint" ) { - QModelIndex iSpring = nif->getIndex( iConstraint, "Stiff Spring" ); - if ( !iSpring.isValid() ) - iSpring = iConstraint; - - const Vector3 pivotA = tBodies.value( 0 ) * Vector3( nif->get( iSpring, "Pivot A" ) ); - const Vector3 pivotB = tBodies.value( 1 ) * Vector3( nif->get( iSpring, "Pivot B" ) ); - const float length = nif->get( iSpring, "Length" ); + const float length = nif->get( iConstraintInfo, "Length" ); if ( !Node::SELECTING ) glColor( color_b ); drawSpring( pivotA, pivotB, length ); } else if ( name == "bhkRagdollConstraint" ) { - QModelIndex iRagdoll = nif->getIndex( iConstraint, "Ragdoll" ); - if ( !iRagdoll.isValid() ) - iRagdoll = iConstraint; - - const Vector3 pivotA( nif->get( iRagdoll, "Pivot A" ) ); - const Vector3 pivotB( nif->get( iRagdoll, "Pivot B" ) ); - - const Vector3 planeA( nif->get( iRagdoll, "Plane A" ) ); - const Vector3 planeB( nif->get( iRagdoll, "Plane B" ) ); + const Vector3 planeA( nif->get( iConstraintInfo, "Plane A" ) ); + const Vector3 planeB( nif->get( iConstraintInfo, "Plane B" ) ); - const Vector3 twistA( nif->get( iRagdoll, "Twist A" ) ); - const Vector3 twistB( nif->get( iRagdoll, "Twist B" ) ); + const Vector3 twistA( nif->get( iConstraintInfo, "Twist A" ) ); + const Vector3 twistB( nif->get( iConstraintInfo, "Twist B" ) ); - const float coneAngle( nif->get( iRagdoll, "Cone Max Angle" ) ); + const float coneAngle( nif->get( iConstraintInfo, "Cone Max Angle" ) ); - const float minPlaneAngle( nif->get( iRagdoll, "Plane Min Angle" ) ); - const float maxPlaneAngle( nif->get( iRagdoll, "Plane Max Angle" ) ); + const float minPlaneAngle( nif->get( iConstraintInfo, "Plane Min Angle" ) ); + const float maxPlaneAngle( nif->get( iConstraintInfo, "Plane Max Angle" ) ); // Unused? GCC complains /* - const float minTwistAngle( nif->get( iRagdoll, "Twist Min Angle" ) ); - const float maxTwistAngle( nif->get( iRagdoll, "Twist Max Angle" ) ); + const float minTwistAngle( nif->get( iConstraintInfo, "Twist Min Angle" ) ); + const float maxTwistAngle( nif->get( iConstraintInfo, "Twist Max Angle" ) ); */ glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); + glMultMatrix( tBodyA ); if ( !Node::SELECTING ) glColor( color_a ); @@ -1301,7 +1277,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glPopMatrix(); glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); + glMultMatrix( tBodyA ); if ( !Node::SELECTING ) glColor( color_a ); @@ -1313,7 +1289,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glPopMatrix(); glPushMatrix(); - glMultMatrix( tBodies.value( 1 ) ); + glMultMatrix( tBodyB ); if ( !Node::SELECTING ) glColor( color_b ); @@ -1324,21 +1300,18 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c drawRagdollCone( pivotB, twistB, planeB, coneAngle, minPlaneAngle, maxPlaneAngle ); glPopMatrix(); } else if ( name == "bhkPrismaticConstraint" ) { - const Vector3 pivotA( nif->get( iConstraint, "Pivot A" ) ); - const Vector3 pivotB( nif->get( iConstraint, "Pivot B" ) ); + const Vector3 planeNormal( nif->get( iConstraintInfo, "Plane A" ) ); + const Vector3 slidingAxis( nif->get( iConstraintInfo, "Sliding A" ) ); - const Vector3 planeNormal( nif->get( iConstraint, "Plane A" ) ); - const Vector3 slidingAxis( nif->get( iConstraint, "Sliding A" ) ); - - const float minDistance = nif->get( iConstraint, "Min Distance" ); - const float maxDistance = nif->get( iConstraint, "Max Distance" ); + const float minDistance = nif->get( iConstraintInfo, "Min Distance" ); + const float maxDistance = nif->get( iConstraintInfo, "Max Distance" ); const Vector3 d1 = pivotA + slidingAxis * minDistance; const Vector3 d2 = pivotA + slidingAxis * maxDistance; /* draw Pivot A and Plane */ glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); + glMultMatrix( tBodyA ); if ( !Node::SELECTING ) glColor( color_a ); @@ -1381,7 +1354,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c /* draw Pivot B */ glPushMatrix(); - glMultMatrix( tBodies.value( 1 ) ); + glMultMatrix( tBodyB ); if ( !Node::SELECTING ) glColor( color_b ); @@ -1558,7 +1531,7 @@ void Node::drawHavok() } glPointSize( 4.5 ); - glLineWidth( 1.0 ); + glLineWidth( 1.5 ); static const float colors[8][3] = { { 0.0f, 1.0f, 0.0f }, @@ -1592,18 +1565,14 @@ void Node::drawHavok() drawHvkShape( nif, nif->getBlock( nif->getLink( iBody, "Shape" ) ), shapeStack, scene, colors[ color_index ] ); - - // Scale up for Skyrim - float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; - if ( Node::SELECTING && scene->options & Scene::ShowAxes ) { int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iBody ) ); glColor4ubv( (GLubyte *)&s_nodeId ); glDepthFunc( GL_ALWAYS ); - drawAxes( Vector3( nif->get( iBody, "Center" ) ) * havokScale, 1.0f, false ); + drawAxes( Vector3( nif->get( iBody, "Center" ) ), 1.0f / bhkScaleMult( nif ), false ); glDepthFunc( GL_LEQUAL ); } else if ( scene->options & Scene::ShowAxes ) { - drawAxes( Vector3( nif->get( iBody, "Center" ) ) * havokScale, 1.0f ); + drawAxes( Vector3( nif->get( iBody, "Center" ) ), 1.0f / bhkScaleMult( nif ) ); } glPopMatrix(); @@ -1929,14 +1898,8 @@ BoundSphere Node::bounds() const boundsphere |= BoundSphere( trans, rad.length() ); } - if ( nif->getBlockType( iBlock ) == "NiMesh" ) { - auto iBound = nif->getIndex( iBlock, "Bound" ); - if ( iBound.isValid() ) { - auto center = nif->get( iBound, "Center" ); - auto radius = nif->get( iBound, "Radius" ); - boundsphere |= BoundSphere( center, radius ); - } - } + if ( nif->getBlockType( iBlock ) == "NiMesh" ) + boundsphere |= BoundSphere( nif, iBlock ); // BSBound collision bounding box QModelIndex iExtraDataList = nif->getIndex( iBlock, "Extra Data List" ); diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 05ed7a92d..bb3559090 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -190,6 +190,22 @@ BoundSphere::BoundSphere( const QVector & verts ) } } +void BoundSphere::update( NifModel * nif, const QModelIndex & index ) +{ + auto idx = index; + auto sph = nif->getIndex( idx, "Bounding Sphere" ); + if ( sph.isValid() ) + idx = sph; + + nif->set( idx, "Center", center ); + nif->set( idx, "Radius", radius ); +} + +void BoundSphere::setBounds( NifModel * nif, const QModelIndex & index, const Vector3 & center, float radius ) +{ + BoundSphere( center, radius ).update( nif, index ); +} + BoundSphere & BoundSphere::operator=( const BoundSphere & o ) { center = o.center; @@ -300,7 +316,72 @@ void drawAxes( const Vector3 & c, float axis, bool color ) glPopMatrix(); } -QVector sortAxes( QVector axesDots ) +const float hkScale660 = 1.0 / 1.42875 * 10.0; +const float hkScale2010 = 1.0 / 1.42875 * 100.0; + +float bhkScale( const NifModel * nif ) +{ + return (nif->getUserVersion2() < 47) ? hkScale660 : hkScale2010; +} + +float bhkInvScale( const NifModel * nif ) +{ + return (nif->getUserVersion2() < 47) ? 1.0 / hkScale660 : 1.0 / hkScale2010; +} + +float bhkScaleMult( const NifModel * nif ) +{ + return (nif->getUserVersion2() < 47) ? 1.0 : 10.0; +} + +Transform bhkBodyTrans( const NifModel * nif, const QModelIndex & index ) +{ + Transform t; + + if ( nif->isNiBlock( index, "bhkRigidBodyT" ) ) { + t.translation = Vector3( nif->get( index, "Translation" ) * bhkScale( nif ) ); + t.rotation.fromQuat( nif->get( index, "Rotation" ) ); + } + + t.scale = bhkScale( nif ); + + qint32 l = nif->getBlockNumber( index ); + + while ( (l = nif->getParent( l )) >= 0 ) { + QModelIndex iAV = nif->getBlock( l, "NiAVObject" ); + + if ( iAV.isValid() ) + t = Transform( nif, iAV ) * t; + } + + return t; +} + +QModelIndex bhkGetEntity( const NifModel * nif, const QModelIndex & index, const QString & name ) + { + auto iEntity = nif->getIndex( index, name ); + if ( !iEntity.isValid() ) { + iEntity = nif->getIndex( nif->getIndex( index, "Constraint Info" ), name ); + if ( !iEntity.isValid() ) + return {}; + } + + return iEntity; + } + +QModelIndex bhkGetRBInfo( const NifModel * nif, const QModelIndex & index, const QString & name ) +{ + auto iInfo = nif->getIndex( index, name ); + if ( !iInfo.isValid() ) { + iInfo = nif->getIndex( nif->getIndex( index, "Rigid Body Info" ), name ); + if ( !iInfo.isValid() ) + return {}; + } + + return iInfo; +} + + QVector sortAxes( QVector axesDots ) { QVector dotsSorted = axesDots; std::stable_sort( dotsSorted.begin(), dotsSorted.end() ); @@ -905,12 +986,6 @@ void drawNiTSS( const NifModel * nif, const QModelIndex & iShape, bool solid ) void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid ) { - // Scale up for Skyrim - float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; - - //QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( iShape ) ) ); - //Vector4 origin = Vector4( nif->get( iParent, "Origin" ), 0 ); - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); if ( iData.isValid() ) { QModelIndex iBigVerts = nif->getIndex( iData, "Big Verts" ); @@ -923,15 +998,13 @@ void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid ) glDisable( GL_CULL_FACE ); for ( int r = 0; r < nif->rowCount( iBigTris ); r++ ) { - quint16 a = nif->get( iBigTris.child( r, 0 ), "Triangle 1" ); - quint16 b = nif->get( iBigTris.child( r, 0 ), "Triangle 2" ); - quint16 c = nif->get( iBigTris.child( r, 0 ), "Triangle 3" ); + Triangle tri = nif->get( iBigTris.child( r, 0 ), "Triangle" ); glBegin( GL_TRIANGLES ); - glVertex( verts[a] * havokScale ); - glVertex( verts[b] * havokScale ); - glVertex( verts[c] * havokScale ); + glVertex( verts[tri.v1()] ); + glVertex( verts[tri.v2()] ); + glVertex( verts[tri.v3()] ); glEnd(); } @@ -966,7 +1039,6 @@ void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid ) for ( int n = 0; n < ((int)numOffsets / 3); n++ ) { vertices[n] = chunkOrigin + chunkTranslation + Vector4( offsets[3 * n], offsets[3 * n + 1], offsets[3 * n + 2], 0 ) / 1000.0f; - vertices[n] *= havokScale; } glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_FILL : GL_LINE ); diff --git a/src/gl/gltools.h b/src/gl/gltools.h index 853ca8fe0..60cbd26fe 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -53,6 +53,10 @@ class BoundSphere final Vector3 center; float radius; + void update( NifModel * nif, const QModelIndex & ); + + static void setBounds( NifModel * nif, const QModelIndex &, const Vector3 & center, float radius ); + BoundSphere & operator=( const BoundSphere & ); BoundSphere & operator|=( const BoundSphere & ); @@ -114,6 +118,15 @@ class SkinPartition final QVector > tristrips; }; +float bhkScale( const NifModel * nif ); +float bhkInvScale( const NifModel * nif ); +float bhkScaleMult( const NifModel * nif ); + +Transform bhkBodyTrans( const NifModel * nif, const QModelIndex & index ); + +QModelIndex bhkGetEntity( const NifModel * nif, const QModelIndex & index, const QString & name ); +QModelIndex bhkGetRBInfo( const NifModel * nif, const QModelIndex & index, const QString & name ); + QVector sortAxes( QVector axesDots ); void drawAxes( const Vector3 & c, float axis, bool color = true ); diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 9bf1a5302..1c4fd4b63 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -165,7 +165,7 @@ bool Renderer::ConditionSingle::eval( const NifModel * nif, const QVector> val.val.u32; else @@ -77,7 +78,6 @@ bool NifIStream::read( NifValue & val ) } case NifValue::tByte: { - val.val.u32 = 0; *dataStream >> val.val.u08; return (dataStream->status() == QDataStream::Ok); } @@ -86,7 +86,6 @@ bool NifIStream::read( NifValue & val ) case NifValue::tFlags: case NifValue::tBlockTypeIndex: { - val.val.u32 = 0; *dataStream >> val.val.u16; return (dataStream->status() == QDataStream::Ok); } @@ -109,6 +108,12 @@ bool NifIStream::read( NifValue & val ) return (dataStream->status() == QDataStream::Ok); } + case NifValue::tInt64: + case NifValue::tUInt64: + { + *dataStream >> val.val.u64; + return (dataStream->status() == QDataStream::Ok); + } case NifValue::tStringIndex: { *dataStream >> val.val.u32; @@ -126,11 +131,13 @@ bool NifIStream::read( NifValue & val ) } case NifValue::tFloat: { + val.val.u64 = 0; *dataStream >> val.val.f32; return (dataStream->status() == QDataStream::Ok); } case NifValue::tHfloat: { + val.val.u64 = 0; uint16_t half; *dataStream >> half; val.val.u32 = half_to_float( half ); @@ -514,6 +521,9 @@ bool NifOStream::write( const NifValue & val ) case NifValue::tULittle32: case NifValue::tStringIndex: return device->write( (char *)&val.val.u32, 4 ) == 4; + case NifValue::tInt64: + case NifValue::tUInt64: + return device->write( (char *)&val.val.u64, 8 ) == 8; case NifValue::tFileVersion: { if ( NifModel * mdl = static_cast(const_cast(model)) ) { @@ -827,6 +837,9 @@ int NifSStream::size( const NifValue & val ) case NifValue::tUpLink: case NifValue::tFloat: return 4; + case NifValue::tInt64: + case NifValue::tUInt64: + return 8; case NifValue::tHfloat: return 2; case NifValue::tByteVector3: diff --git a/src/lib/importex/3ds.cpp b/src/lib/importex/3ds.cpp index 568f6e87e..223dc4547 100644 --- a/src/lib/importex/3ds.cpp +++ b/src/lib/importex/3ds.cpp @@ -709,14 +709,8 @@ void import3ds( NifModel * nif, const QModelIndex & index ) nif->updateArray( iData, "Normals" ); nif->setArray( iData, "Normals", mesh->normals ); nif->set( iData, "Has UV", 1 ); - nif->set( iData, "Num UV Sets", 1 ); - nif->set( iData, "Num UV Sets 2", 1 ); + nif->set( iData, "Data Flags", 1 ); QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - - if ( !iTexCo.isValid() ) { - iTexCo = nif->getIndex( iData, "UV Sets 2" ); - } - nif->updateArray( iTexCo ); nif->updateArray( iTexCo.child( 0, 0 ) ); nif->setArray( iTexCo.child( 0, 0 ), mesh->texcoords ); diff --git a/src/lib/importex/col.cpp b/src/lib/importex/col.cpp index c99f40b59..e4b69514b 100644 --- a/src/lib/importex/col.cpp +++ b/src/lib/importex/col.cpp @@ -766,7 +766,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) } // UV maps - int uvCount = (nif->get( iProp, "Num UV Sets" ) & 63) | (nif->get( iProp, "BS Num UV Sets" ) & 1); + int uvCount = (nif->get( iProp, "Data Flags" ) & 63) | (nif->get( iProp, "BS Data Flags" ) & 1); QModelIndex iUV = nif->getIndex( iProp, "UV Sets" ); for ( int row = 0; row < uvCount; row++ ) { diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index 83b98eacb..5e3b1515b 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -73,10 +73,6 @@ static void writeData( const NifModel * nif, const QModelIndex & iData, QTextStr // copy texcoords QModelIndex iUV = nif->getIndex( iData, "UV Sets" ); - - if ( !iUV.isValid() ) - iUV = nif->getIndex( iData, "UV Sets 2" ); - QVector texco = nif->getArray( iUV.child( 0, 0 ) ); foreach( Vector2 t, texco ) { @@ -931,8 +927,8 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->setArray( iData, "Normals", norms ); nif->set( iData, "Has UV", 1 ); nif->set( iData, "Num UV Sets", 1 ); - nif->set( iData, "Vector Flags", 4097 ); - nif->set( iData, "BS Vector Flags", 4097 ); + nif->set( iData, "Data Flags", 4097 ); + nif->set( iData, "BS Data Flags", 4097 ); if ( nif->getUserVersion2() > 34 ) { nif->set( iData, "Has Vertex Colors", 1 ); @@ -940,10 +936,6 @@ void importObj( NifModel * nif, const QModelIndex & index ) } QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - - if ( !iTexCo.isValid() ) - iTexCo = nif->getIndex( iData, "UV Sets 2" ); - nif->updateArray( iTexCo ); nif->updateArray( iTexCo.child( 0, 0 ) ); nif->setArray( iTexCo.child( 0, 0 ), texco ); diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index c7e7eb1a2..be0a2d985 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -828,14 +828,24 @@ QVariant BaseModelEval::operator()(const QVariant & v) const QString left = v.toString(); const NifItem * i = item; - // resolve "ARG" + // Resolve "ARG" + bool argexpr = false; while ( left == XMLARG ) { if ( !i->parent() ) return false; i = i->parent(); left = i->arg(); + argexpr = !i->argexpr().noop(); } + // ARG is an expression + if ( argexpr ) + return i->argexpr().evaluateUInt64( BaseModelEval( model, i ) ); + + bool numeric; + int val = left.toInt( &numeric, 10 ); + if ( numeric ) + return QVariant( val ); // resolve reference to sibling const NifItem * sibling = model->getItem( i->parent(), left ); diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index 5c1c7ca41..c183c2e72 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -524,7 +524,7 @@ class spEditFlags : public Spell Spell::tr( "Save External Geom Data" ), // 512 Spell::tr( "No Decals" ), // 1024 Spell::tr( "Always Draw" ), // 2048 - Spell::tr( "Mesh LOD" ), // 4096 + Spell::tr( "Mesh LOD (FO4)" ), // 4096 Spell::tr( "Fixed Bound" ), // 8192 Spell::tr( "Top Fade Node" ), // 16384 Spell::tr( "Ignore Fade" ), // 32768 @@ -539,7 +539,7 @@ class spEditFlags : public Spell Spell::tr( "High Detail" ), // 1 << 24 Spell::tr( "Force Update" ), // 1 << 25 Spell::tr( "Pre-Processed Node" ), // 1 << 26 - Spell::tr( "Bit 27" ), // 1 << 27 + Spell::tr( "Mesh LOD (Skyrim)" ), // 1 << 27 Spell::tr( "Bit 28" ), // 1 << 28 Spell::tr( "Bit 29" ), // 1 << 29 Spell::tr( "Bit 30" ), // 1 << 30 diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index 24bf3537f..4c719f82f 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -1,5 +1,6 @@ #include "spellbook.h" +#include "gl/gltools.h" #include "spells/blocks.h" #include "lib/nvtristripwrapper.h" @@ -301,16 +302,16 @@ class spConstraintHelper final : public Spell } } - QModelIndex iBodyA = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 0, 0 ) ), "bhkRigidBody" ); - QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); + QModelIndex iBodyA = nif->getBlock( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity A" ) ), "bhkRigidBody" ); + QModelIndex iBodyB = nif->getBlock( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity B" ) ), "bhkRigidBody" ); if ( !iBodyA.isValid() || !iBodyB.isValid() ) { Message::warning( nullptr, Spell::tr( "Couldn't find the bodies for this constraint." ) ); return index; } - Transform transA = bodyTrans( nif, iBodyA ); - Transform transB = bodyTrans( nif, iBodyB ); + Transform transA = bhkBodyTrans( nif, iBodyA ); + Transform transB = bhkBodyTrans( nif, iBodyB ); QModelIndex iConstraintData; if ( name == "bhkLimitedHingeConstraint" ) { @@ -330,9 +331,9 @@ class spConstraintHelper final : public Spell if ( !iConstraintData.isValid() ) return index; - Vector3 pivot = Vector3( nif->get( iConstraintData, "Pivot A" ) ) * havokConst; + Vector3 pivot = Vector3( nif->get( iConstraintData, "Pivot A" ) ); pivot = transA * pivot; - pivot = transB.rotation.inverted() * ( pivot - transB.translation ) / transB.scale / havokConst; + pivot = transB.rotation.inverted() * ( pivot - transB.translation ) / transB.scale; nif->set( iConstraintData, "Pivot B", { pivot[0], pivot[1], pivot[2], 0 } ); QString axisA, axisB, twistA, twistB, twistA2, twistB2; @@ -372,27 +373,6 @@ class spConstraintHelper final : public Spell return index; } - - static Transform bodyTrans( const NifModel * nif, const QModelIndex & index ) - { - Transform t; - - if ( nif->isNiBlock( index, "bhkRigidBodyT" ) ) { - t.translation = Vector3( nif->get( index, "Translation" ) * 7 ); - t.rotation.fromQuat( nif->get( index, "Rotation" ) ); - } - - qint32 l = nif->getBlockNumber( index ); - - while ( ( l = nif->getParent( l ) ) >= 0 ) { - QModelIndex iAV = nif->getBlock( l, "NiAVObject" ); - - if ( iAV.isValid() ) - t = Transform( nif, iAV ) * t; - } - - return t; - } }; REGISTER_SPELL( spConstraintHelper ) @@ -416,21 +396,21 @@ class spStiffSpringHelper final : public Spell if ( !iSpring.isValid() ) iSpring = iConstraint; - QModelIndex iBodyA = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 0, 0 ) ), "bhkRigidBody" ); - QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); + QModelIndex iBodyA = nif->getBlock( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity A" ) ), "bhkRigidBody" ); + QModelIndex iBodyB = nif->getBlock( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity B" ) ), "bhkRigidBody" ); if ( !iBodyA.isValid() || !iBodyB.isValid() ) { Message::warning( nullptr, Spell::tr( "Couldn't find the bodies for this constraint" ) ); return idx; } - Transform transA = spConstraintHelper::bodyTrans( nif, iBodyA ); - Transform transB = spConstraintHelper::bodyTrans( nif, iBodyB ); + Transform transA = bhkBodyTrans( nif, iBodyA ); + Transform transB = bhkBodyTrans( nif, iBodyB ); - Vector3 pivotA( nif->get( iSpring, "Pivot A" ) * 7 ); - Vector3 pivotB( nif->get( iSpring, "Pivot B" ) * 7 ); + Vector3 pivotA( nif->get( iSpring, "Pivot A" ) ); + Vector3 pivotB( nif->get( iSpring, "Pivot B" ) ); - float length = ( transA * pivotA - transB * pivotB ).length() / 7; + float length = ( transA * pivotA - transB * pivotB ).length(); nif->set( iSpring, "Length", length ); diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index 741a9ea1a..5ac6b6896 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -670,8 +670,7 @@ QModelIndex spUpdateCenterRadius::cast( NifModel * nif, const QModelIndex & inde radius = d; } - nif->set( iData, "Center", center ); - nif->set( iData, "Radius", radius ); + BoundSphere::setBounds( nif, iData, center, radius ); return index; } @@ -705,11 +704,7 @@ class spUpdateBounds final : public Spell // Creating a bounding sphere from the verts BoundSphere bounds = BoundSphere( verts ); - - // Update the bounding sphere - auto boundsIdx = nif->getIndex( index, "Bounding Sphere" ); - nif->set( boundsIdx, "Center", bounds.center ); - nif->set( boundsIdx, "Radius", bounds.radius ); + bounds.update( nif, index ); return index; } @@ -774,7 +769,7 @@ QModelIndex spUpdateTrianglesFromSkin::cast( NifModel * nif, const QModelIndex & return QModelIndex(); QVector tris; - auto iParts = nif->getIndex( iSkinPart, "Skin Partition Blocks" ); + auto iParts = nif->getIndex( iSkinPart, "Partitions" ); for ( int i = 0; i < nif->rowCount( iParts ) && iParts.isValid(); i++ ) tris << SkinPartition( nif, iParts.child( i, 0 ) ).getRemappedTriangles(); diff --git a/src/spells/mesh.h b/src/spells/mesh.h index 7da9c452a..4f08e3569 100644 --- a/src/spells/mesh.h +++ b/src/spells/mesh.h @@ -10,7 +10,7 @@ class spUpdateCenterRadius final : public Spell { public: - QString name() const override final { return Spell::tr( "Update Center/Radius" ); } + QString name() const override final { return Spell::tr( "Update Bounds" ); } QString page() const override final { return Spell::tr( "Mesh" ); } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final; diff --git a/src/spells/moppcode.cpp b/src/spells/moppcode.cpp index d21ca9721..fbf7b3767 100644 --- a/src/spells/moppcode.cpp +++ b/src/spells/moppcode.cpp @@ -215,14 +215,12 @@ class spMoppCode final : public Spell if ( moppcode.size() == 0 ) { Message::critical( nullptr, Spell::tr( "Failed to generate MOPP code" ) ); } else { - QModelIndex iCodeOrigin = nif->getIndex( ibhkMoppBvTreeShape, "Origin" ); - nif->set( iCodeOrigin, origin ); + auto iMoppCode = nif->getIndex( ibhkMoppBvTreeShape, "MOPP Code" ); - QModelIndex iCodeScale = nif->getIndex( ibhkMoppBvTreeShape, "Scale" ); - nif->set( iCodeScale, scale ); + nif->set( nif->getIndex( iMoppCode, "Offset" ), Vector4(origin, scale) ); - QModelIndex iCodeSize = nif->getIndex( ibhkMoppBvTreeShape, "MOPP Data Size" ); - QModelIndex iCode = nif->getIndex( ibhkMoppBvTreeShape, "MOPP Data" ).child( 0, 0 ); + QModelIndex iCodeSize = nif->getIndex( iMoppCode, "Data Size" ); + QModelIndex iCode = nif->getIndex( iMoppCode, "Data" ).child( 0, 0 ); if ( iCodeSize.isValid() && iCode.isValid() ) { nif->set( iCodeSize, moppcode.size() ); diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index f66291928..1bca55271 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -113,8 +113,8 @@ class spFaceNormals final : public Spell numVerts = nif->get( iPart, "Data Size" ) / nif->get( iPart, "Vertex Size" ); // Get triangles from all partitions - auto numParts = nif->get( iPart, "Num Skin Partition Blocks" ); - auto iParts = nif->getIndex( iPart, "Partition" ); + auto numParts = nif->get( iPart, "Num Partitions" ); + auto iParts = nif->getIndex( iPart, "Partitions" ); for ( int i = 0; i < numParts; i++ ) triangles << nif->getArray( iParts.child( i, 0 ), "Triangles" ); } diff --git a/src/spells/skeleton.cpp b/src/spells/skeleton.cpp index 9954f05a7..223bda251 100644 --- a/src/spells/skeleton.cpp +++ b/src/spells/skeleton.cpp @@ -181,8 +181,9 @@ class spFixSkeleton final : public Spell t.writeBack( nif, iBone ); } - Vector3 center = nif->get( iShapeData, "Center" ); - nif->set( iShapeData, "Center", tparent * center ); + auto bound = BoundSphere( nif, iShapeData ).apply( tparent ); + bound.update( nif, iShapeData ); + return true; } }; @@ -478,8 +479,8 @@ class spSkinPartition final : public Spell defaultPart = qMin( nparts - 1, defaultPart ); // enumerate existing partitions and select faces into same partition - quint32 nskinparts = nif->get( iSkinPart, "Num Skin Partition Blocks" ); - iPartData = nif->getIndex( iSkinPart, "Skin Partition Blocks" ); + quint32 nskinparts = nif->get( iSkinPart, "Num Partitions" ); + iPartData = nif->getIndex( iSkinPart, "Partitions" ); for ( quint32 i = 0; i < nskinparts; ++i ) { QModelIndex iPart = iPartData.child( i, 0 ); @@ -789,8 +790,8 @@ class spSkinPartition final : public Spell // start writing NiSkinPartition - nif->set( iSkinPart, "Num Skin Partition Blocks", parts.count() ); - nif->updateArray( iSkinPart, "Skin Partition Blocks" ); + nif->set( iSkinPart, "Num Partitions", parts.count() ); + nif->updateArray( iSkinPart, "Partitions" ); QModelIndex iBSSkinInstPartData; @@ -809,7 +810,7 @@ class spSkinPartition final : public Spell QList prevPartBones; for ( int p = 0; p < parts.count(); p++ ) { - QModelIndex iPart = nif->getIndex( iSkinPart, "Skin Partition Blocks" ).child( p, 0 ); + QModelIndex iPart = nif->getIndex( iSkinPart, "Partitions" ).child( p, 0 ); QList bones = parts[p].bones; std::sort( bones.begin(), bones.end() /*, std::less()*/ ); @@ -1213,8 +1214,8 @@ class spFixBoneBounds final : public Spell auto sphIdx = nif->getIndex( iBoneDataList.child( b, 0 ) , "Bounding Sphere" ); - nif->set( sphIdx, "Bounding Sphere Offset", center ); - nif->set( sphIdx, "Bounding Sphere Radius", radius ); + nif->set( sphIdx, "Center", center ); + nif->set( sphIdx, "Radius", radius ); } return iSkinData; diff --git a/src/spells/strippify.cpp b/src/spells/strippify.cpp index 2f2fcb1bc..52245bd0c 100644 --- a/src/spells/strippify.cpp +++ b/src/spells/strippify.cpp @@ -1,6 +1,7 @@ #include "spellbook.h" #include "blocks.h" +#include "gl/gltools.h" #include "lib/nvtristripwrapper.h" @@ -95,7 +96,8 @@ class spStrippify final : public Spell copyValue( nif, iStripData, iData, "Has Normals" ); copyArray( nif, iStripData, iData, "Normals" ); - copyValue( nif, iStripData, iData, "TSpace Flag" ); + copyValue( nif, iStripData, iData, "Data Flags" ); + copyValue( nif, iStripData, iData, "BS Data Flags" ); copyArray( nif, iStripData, iData, "Bitangents" ); copyArray( nif, iStripData, iData, "Tangents" ); @@ -103,10 +105,6 @@ class spStrippify final : public Spell copyArray( nif, iStripData, iData, "Vertex Colors" ); copyValue( nif, iStripData, iData, "Has UV" ); - copyValue( nif, iStripData, iData, "Num UV Sets" ); - copyValue( nif, iStripData, iData, "Vector Flags" ); - copyValue( nif, iStripData, iData, "BS Vector Flags" ); - copyValue( nif, iStripData, iData, "Num UV Sets 2" ); QModelIndex iDstUV = nif->getIndex( iStripData, "UV Sets" ); QModelIndex iSrcUV = nif->getIndex( iData, "UV Sets" ); @@ -118,19 +116,8 @@ class spStrippify final : public Spell } } - iDstUV = nif->getIndex( iStripData, "UV Sets 2" ); - iSrcUV = nif->getIndex( iData, "UV Sets 2" ); - - if ( iDstUV.isValid() && iSrcUV.isValid() ) { - nif->updateArray( iDstUV ); - - for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) { - copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); - } - } - - copyValue( nif, iStripData, iData, "Center" ); - copyValue( nif, iStripData, iData, "Radius" ); + auto bound = BoundSphere( nif, iStripData ); + bound.update( nif, iData ); nif->set( iStripData, "Num Strips", strips.count() ); nif->set( iStripData, "Has Points", 1 ); @@ -303,10 +290,8 @@ class spTriangulate final : public Spell copyArray( nif, iTriData, iStripData, "Vertex Colors" ); copyValue( nif, iTriData, iStripData, "Has UV" ); - copyValue( nif, iTriData, iStripData, "Num UV Sets" ); - copyValue( nif, iTriData, iStripData, "Vector Flags" ); - copyValue( nif, iTriData, iStripData, "BS Vector Flags" ); - copyValue( nif, iTriData, iStripData, "Num UV Sets 2" ); + copyValue( nif, iTriData, iStripData, "Data Flags" ); + copyValue( nif, iTriData, iStripData, "BS Data Flags" ); QModelIndex iDstUV = nif->getIndex( iTriData, "UV Sets" ); QModelIndex iSrcUV = nif->getIndex( iStripData, "UV Sets" ); @@ -318,19 +303,8 @@ class spTriangulate final : public Spell } } - iDstUV = nif->getIndex( iTriData, "UV Sets 2" ); - iSrcUV = nif->getIndex( iStripData, "UV Sets 2" ); - - if ( iDstUV.isValid() && iSrcUV.isValid() ) { - nif->updateArray( iDstUV ); - - for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) { - copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); - } - } - - copyValue( nif, iTriData, iStripData, "Center" ); - copyValue( nif, iTriData, iStripData, "Radius" ); + auto bound = BoundSphere( nif, iTriData ); + bound.update( nif, iStripData ); nif->set( iTriData, "Num Triangles", triangles.count() ); nif->set( iTriData, "Num Triangle Points", triangles.count() * 3 ); diff --git a/src/spells/tangentspace.cpp b/src/spells/tangentspace.cpp index 79e8fdf03..2b7d57336 100644 --- a/src/spells/tangentspace.cpp +++ b/src/spells/tangentspace.cpp @@ -87,15 +87,9 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) } QVector vxcol = nif->getArray( iData, "Vertex Colors" ); - int numUVSets = nif->get( iData, "Num UV Sets" ); - int tspaceFlags = nif->get( iData, "TSpace Flag" ); if ( nif->getUserVersion2() < 100 ) { QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - - if ( !iTexCo.isValid() ) - iTexCo = nif->getIndex( iData, "UV Sets 2" ); - iTexCo = iTexCo.child( 0, 0 ); texco = nif->getArray( iTexCo ); } @@ -116,8 +110,8 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) } else if ( nif->getUserVersion2() >= 100 ) { if ( iPartBlock.isValid() ) { // Get triangles from all partitions - auto numParts = nif->get( iPartBlock, "Num Skin Partition Blocks" ); - auto iParts = nif->getIndex( iPartBlock, "Partition" ); + auto numParts = nif->get( iPartBlock, "Num Partitions" ); + auto iParts = nif->getIndex( iPartBlock, "Partitions" ); for ( int i = 0; i < numParts; i++ ) triangles << nif->getArray( iParts.child( i, 0 ), "Triangles" ); } else { @@ -277,11 +271,6 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) nif->set( iTSpace, "Binary Data", QByteArray( (const char *)tan.data(), tan.count() * sizeof( Vector3 ) ) + QByteArray( (const char *)bin.data(), bin.count() * sizeof( Vector3 ) ) ); } else if ( nif->getUserVersion2() < 100 ) { - if ( tspaceFlags == 0 ) - tspaceFlags = 0x10; - - nif->set( iShape, "TSpace Flag", tspaceFlags ); - nif->set( iShape, "Num UV Sets", numUVSets ); QModelIndex iBinorms = nif->getIndex( iData, "Bitangents" ); QModelIndex iTangents = nif->getIndex( iData, "Tangents" ); nif->updateArray( iBinorms ); @@ -388,11 +377,11 @@ class spAddAllTangentSpaces final : public Spell // Do not do anything without proper UV/Vert/Tri data auto numVerts = nif->get( iData, "Num Vertices" ); auto numTris = nif->get( iData, "Num Triangles" ); - bool hasUVs = nif->get( iData, "Vector Flags" ) & 1; + bool hasUVs = nif->get( iData, "Data Flags" ) & 1; if ( !hasUVs || !numVerts || !numTris ) continue; - nif->set( iData, "Vector Flags", 4097 ); + nif->set( iData, "Data Flags", 4097 ); nif->updateArray( iData, "Tangents" ); nif->updateArray( iData, "Bitangents" ); diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index bd884ed0b..97b1d6468 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -642,8 +642,8 @@ class spTextureTemplate final : public Spell iVertData = nif->getIndex( iPartBlock, "Vertex Data" ); // Get triangles from all partitions - auto numParts = nif->get( iPartBlock, "Num Skin Partition Blocks" ); - auto iParts = nif->getIndex( iPartBlock, "Partition" ); + auto numParts = nif->get( iPartBlock, "Num Partitions" ); + auto iParts = nif->getIndex( iPartBlock, "Partitions" ); for ( int i = 0; i < numParts; i++ ) tri << nif->getArray( iParts.child( i, 0 ), "Triangles" ); diff --git a/src/spells/transform.cpp b/src/spells/transform.cpp index c43dec0bd..53d73185d 100644 --- a/src/spells/transform.cpp +++ b/src/spells/transform.cpp @@ -1,6 +1,7 @@ #include "transform.h" #include "ui/widgets/nifeditors.h" +#include "gl/gltools.h" #include #include @@ -172,15 +173,10 @@ QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & ind } } - QModelIndex iCenter = nif->getIndex( iData, "Center" ); - - if ( iCenter.isValid() ) - nif->set( iCenter, t * nif->get( iCenter ) ); - - QModelIndex iRadius = nif->getIndex( iData, "Radius" ); - - if ( iRadius.isValid() ) - nif->set( iRadius, t.scale * nif->get( iRadius ) ); + auto bound = BoundSphere( nif, iData ); + bound.center = t * bound.center; + bound.radius = t.scale * bound.radius; + bound.update( nif, iData ); t = Transform(); t.writeBack( nif, index ); @@ -194,14 +190,10 @@ QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & ind Transform t( nif, index ); // Update Bounding Sphere - auto iBound = nif->getIndex( index, "Bounding Sphere" ); - QModelIndex iCenter = nif->getIndex( iBound, "Center" ); - if ( iCenter.isValid() ) - nif->set( iCenter, t * nif->get( iCenter ) ); - - QModelIndex iRadius = nif->getIndex( iBound, "Radius" ); - if ( iRadius.isValid() ) - nif->set( iRadius, t.scale * nif->get( iRadius ) ); + auto bound = BoundSphere( nif, index ); + bound.center = t * bound.center; + bound.radius = t.scale * bound.radius; + bound.update( nif, index ); nif->setState( BaseModel::Processing ); for ( int i = 0; i < nif->rowCount( iVertData ); i++ ) { diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 1360e7a53..55d0142e4 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -974,7 +974,7 @@ bool UVWidget::setTexCoords() if ( !isDataOnSkin ) { tris = nif->getArray( iShape, "Triangles" ); } else { - auto partIdx = nif->getIndex( iPartBlock, "Partition" ); + auto partIdx = nif->getIndex( iPartBlock, "Partitions" ); for ( int i = 0; i < nif->rowCount( partIdx ); i++ ) { tris << nif->getArray( nif->index( i, 0, partIdx ), "Triangles" ); } @@ -1569,8 +1569,8 @@ void UVWidget::getCoordSets() { coordSetSelect->clear(); - // TODO: Broken for newer nif.xml where NiGeometryData has been corrected - quint8 numUvSets = nif->get( iShapeData, "Num UV Sets" ); + quint8 numUvSets = (nif->get( iShapeData, "Data Flags" ) & 0x3F) + | (nif->get( iShapeData, "BS Data Flags" ) & 0x1); for ( int i = 0; i < numUvSets; i++ ) { QAction * temp; @@ -1620,8 +1620,12 @@ void UVWidget::duplicateCoordSet() // this signal close the UVWidget disconnect( nif, &NifModel::dataChanged, this, &UVWidget::nifDataChanged ); // expand the UV Sets array and duplicate the current coordinates - quint8 numUvSets = nif->get( iShapeData, "Num UV Sets" ); - nif->set( iShapeData, "Num UV Sets", numUvSets + 1 ); + auto dataFlags = nif->get( iShapeData, "Data Flags" ); + quint8 numUvSets = nif->get( iShapeData, "Data Flags" ) & 0x3F; + numUvSets += 1; + dataFlags = dataFlags | ((dataFlags & 0x3F) | numUvSets); + + nif->set( iShapeData, "Data Flags", numUvSets ); QModelIndex uvSets = nif->getIndex( iShapeData, "UV Sets" ); nif->updateArray( uvSets ); nif->setArray( uvSets.child( numUvSets, 0 ), nif->getArray( uvSets.child( currentCoordSet, 0 ) ) ); diff --git a/src/xml/nifexpr.cpp b/src/xml/nifexpr.cpp index 97165795f..f3b61d0e0 100644 --- a/src/xml/nifexpr.cpp +++ b/src/xml/nifexpr.cpp @@ -144,6 +144,10 @@ NifExpr::Operator NifExpr::operatorFromString( const QString & str ) return NifExpr::e_bool_and; else if ( str == "||" ) return NifExpr::e_bool_or; + else if ( str == "<<" ) + return NifExpr::e_lsh; + else if ( str == ">>" ) + return NifExpr::e_rsh; return NifExpr::e_nop; } @@ -172,7 +176,7 @@ void NifExpr::partition( const QString & cond, int offset /*= 0*/ ) ostartpos = -1, oendpos = -1, // Operator Start/End rstartpos = -1, rendpos = -1; // Right Start/End - QRegularExpression reOps( "(!=|==|>=|<=|>|<|\\+|-|/|\\*|\\&\\&|\\|\\||\\&|\\|)" ); + QRegularExpression reOps( "(!=|==|>=|<=|>>|<<|>|<|\\+|-|/|\\*|\\&\\&|\\|\\||\\&|\\|)" ); QRegularExpression reLParen( "^\\s*\\(.*" ); QRegularExpressionMatch reLParenMatch = reLParen.match( cond, offset ); @@ -291,6 +295,10 @@ QString NifExpr::toString() const return QString( "(%1 && %2)" ).arg( l, r ); case NifExpr::e_bool_or: return QString( "(%1 || %2)" ).arg( l, r ); + case NifExpr::e_lsh: + return QString( "(%1 << %2)" ).arg( l, r ); + case NifExpr::e_rsh: + return QString( "(%1 >> %2)" ).arg( l, r ); case NifExpr::e_nop: return QString( "%1" ).arg( l ); } diff --git a/src/xml/nifexpr.h b/src/xml/nifexpr.h index 7e1fab481..faa27d145 100644 --- a/src/xml/nifexpr.h +++ b/src/xml/nifexpr.h @@ -46,7 +46,7 @@ class NifExpr final enum Operator { e_nop, e_not_eq, e_eq, e_gte, e_lte, e_gt, e_lt, e_bit_and, e_bit_or, - e_add, e_sub, e_div, e_mul, e_bool_and, e_bool_or, e_not, + e_add, e_sub, e_div, e_mul, e_bool_and, e_bool_or, e_not, e_lsh, e_rsh }; QVariant lhs; QVariant rhs; @@ -72,6 +72,11 @@ class NifExpr final QString toString() const; + bool noop() const + { + return opcode == NifExpr::e_nop; + } + public: template QVariant evaluateValue( const F & convert ) const @@ -111,6 +116,10 @@ class NifExpr final return QVariant::fromValue( l.toBool() && r.toBool() ); case NifExpr::e_bool_or: return QVariant::fromValue( l.toBool() || r.toBool() ); + case NifExpr::e_lsh: + return QVariant::fromValue( l.toULongLong() << r.toUInt() ); + case NifExpr::e_rsh: + return QVariant::fromValue( l.toULongLong() >> r.toUInt() ); case NifExpr::e_nop: return l; } @@ -130,6 +139,12 @@ class NifExpr final return evaluateValue( convert ).toUInt(); } + template + int evaluateUInt64( const F & convert ) const + { + return evaluateValue( convert ).toULongLong(); + } + private: static Operator operatorFromString( const QString & str ); void partition( const QString & cond, int offset = 0 ); diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 7675ecde9..9ded69403 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -73,6 +73,7 @@ class NifXmlHandler final : public QXmlDefaultHandler tagCompound, tagBlock, tagAdd, + tagAddDefault, tagBasic, tagEnum, tagOption, @@ -80,7 +81,8 @@ class NifXmlHandler final : public QXmlDefaultHandler tagBitfield, tagMember, tagToken, - tagTokenTag + tagTokenTag, + tagModule }; //! i18n wrapper for various strings @@ -96,9 +98,11 @@ class NifXmlHandler final : public QXmlDefaultHandler { tags.insert( "niftoolsxml", tagFile ); tags.insert( "version", tagVersion ); + tags.insert( "module", tagModule ); tags.insert( "compound", tagCompound ); tags.insert( "niobject", tagBlock ); tags.insert( "add", tagAdd ); + tags.insert( "default", tagAddDefault ); tags.insert( "basic", tagBasic ); tags.insert( "enum", tagEnum ); tags.insert( "option", tagOption ); @@ -227,7 +231,7 @@ class NifXmlHandler final : public QXmlDefaultHandler blk = NifBlockPtr( new NifBlock ); blk->id = id; - blk->abstract = ( list.value( "abstract" ) == "1" ); + blk->abstract = (list.value( "abstract" ) == "1" || list.value( "abstract" ) == "true"); if ( x == tagBlock ) { blk->ancestor = list.value( "inherit" ); @@ -301,6 +305,8 @@ class NifXmlHandler final : public QXmlDefaultHandler attrlist = list.value( "attrs" ); } break; + case tagModule: + break; default: err( tr( "expected basic, enum, compound, niobject or version got %1 instead" ).arg( tagid ) ); } @@ -358,13 +364,14 @@ class NifXmlHandler final : public QXmlDefaultHandler static const QVector mixinTypes { "HavokFilter", "HavokMaterial", - "RagdollDescriptor", - "LimitedHingeDescriptor", - "HingeDescriptor", - "BallAndSocketDescriptor", - "PrismaticDescriptor", - "MalleableDescriptor", - "ConstraintData" + "bhkRagdollConstraintCInfo", + "bhkLimitedHingeConstraintCInfo", + "bhkHingeConstraintCInfo", + "bhkBallAndSocketConstraintCInfo", + "bhkPrismaticConstraintCInfo", + "bhkMalleableConstraintCInfo", + "bhkConstraintData", + "bhkConstraintCInfo" }; isMixin = mixinTypes.contains( type ); @@ -431,6 +438,15 @@ class NifXmlHandler final : public QXmlDefaultHandler err( tr( "only add tags allowed in block declaration" ) ); } + break; + case tagAdd: + // Member child tags + push( x ); + switch ( x ) { + case tagAddDefault: + // Subclass defaults + break; + } break; case tagEnum: case tagBitFlag: From 49370472aff98dd23bc09211f38e29a11aa0a2a1 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 11 Nov 2018 01:01:49 -0500 Subject: [PATCH 010/118] [XML] nifxml 1.0 sync shader files --- res/shaders/fo4_default.prog | 8 ++++---- res/shaders/fo4_effectshader.prog | 2 +- res/shaders/sk_default.prog | 4 ++-- res/shaders/sk_msn.prog | 2 +- res/shaders/sk_multilayer.prog | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/res/shaders/fo4_default.prog b/res/shaders/fo4_default.prog index 22a60e3ac..a78d2a704 100644 --- a/res/shaders/fo4_default.prog +++ b/res/shaders/fo4_default.prog @@ -2,11 +2,11 @@ checkgroup begin and # Fallout 4 - check HEADER/BS Header/BS Version >= 130 + check HEADER/BS Header/BS Version == 130 check BSLightingShaderProperty - #check BSLightingShaderProperty/Skyrim Shader Type != 1 - #check BSLightingShaderProperty/Skyrim Shader Type != 16 - check BSLightingShaderProperty/Skyrim Shader Type != 18 + #check BSLightingShaderProperty/Shader Type != 1 + #check BSLightingShaderProperty/Shader Type != 16 + check BSLightingShaderProperty/Shader Type != 18 checkgroup begin or check BSTriShape check BSSubIndexTriShape diff --git a/res/shaders/fo4_effectshader.prog b/res/shaders/fo4_effectshader.prog index b56583d0d..f7412fed7 100644 --- a/res/shaders/fo4_effectshader.prog +++ b/res/shaders/fo4_effectshader.prog @@ -2,7 +2,7 @@ checkgroup begin and # Fallout 4 - check HEADER/BS Header/BS Version >= 130 + check HEADER/BS Header/BS Version == 130 check BSEffectShaderProperty checkgroup end diff --git a/res/shaders/sk_default.prog b/res/shaders/sk_default.prog index 0535dcd2e..246b93796 100644 --- a/res/shaders/sk_default.prog +++ b/res/shaders/sk_default.prog @@ -5,10 +5,10 @@ checkgroup begin and check HEADER/BS Header/BS Version >= 83 check HEADER/BS Header/BS Version <= 100 check BSLightingShaderProperty - check BSLightingShaderProperty/Skyrim Shader Type != 11 + check BSLightingShaderProperty/Shader Type != 11 checkgroup begin or check NiTriBasedGeomData/Has Normals == 1 - check BSTriShape/Vertex Desc & 8 + check BSTriShape/Vertex Desc & 0x800000000000 checkgroup end checkgroup end diff --git a/res/shaders/sk_msn.prog b/res/shaders/sk_msn.prog index c0bb38cc9..466dfe45f 100644 --- a/res/shaders/sk_msn.prog +++ b/res/shaders/sk_msn.prog @@ -19,7 +19,7 @@ checkgroup begin or checkgroup begin and check BSLightingShaderProperty check BSTriShape - check not BSTriShape/Vertex Desc & 8 + check not BSTriShape/Vertex Desc & 0x800000000000 checkgroup end checkgroup end diff --git a/res/shaders/sk_multilayer.prog b/res/shaders/sk_multilayer.prog index 2631200a4..957076cad 100644 --- a/res/shaders/sk_multilayer.prog +++ b/res/shaders/sk_multilayer.prog @@ -5,7 +5,7 @@ checkgroup begin and check HEADER/BS Header/BS Version >= 83 check HEADER/BS Header/BS Version <= 100 check BSLightingShaderProperty - check BSLightingShaderProperty/Skyrim Shader Type == 11 + check BSLightingShaderProperty/Shader Type == 11 checkgroup begin or check NiTriBasedGeomData/Has Normals == 1 check BSTriShape/Vertex Desc & 8 From 761de1a309dcdc13c961f075844833115017fee8 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 11 Nov 2018 12:28:11 -0500 Subject: [PATCH 011/118] BSA/BA2 improvements Supported more BA2 texture formats. Made file filter case insensitive. --- lib/fsengine/bsa.cpp | 48 +++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index df5d8bd21..a8d0fd78b 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -77,18 +77,14 @@ static bool BSAReadSizedString( QFile & bsa, QString & s ) return false; } -QByteArray gUncompress( const QByteArray & data, const int size ) +QByteArray gUncompress( const char * data, const int size ) { - if ( data.size() <= 4 ) { - qWarning( "gUncompress: Input data is truncated" ); - return QByteArray(); - } - QByteArray result; + result.reserve( size ); int ret; z_stream strm; - static const int CHUNK_SIZE = 1024; + static const int CHUNK_SIZE = 4096; char out[CHUNK_SIZE]; /* allocate inflate state */ @@ -96,7 +92,7 @@ QByteArray gUncompress( const QByteArray & data, const int size ) strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = size; - strm.next_in = (Bytef*)(data.data()); + strm.next_in = (Bytef*)(data); ret = inflateInit2( &strm, 15 + 32 ); // gzip decoding Q_ASSERT( ret == Z_OK ); @@ -128,6 +124,16 @@ QByteArray gUncompress( const QByteArray & data, const int size ) return result; } +QByteArray gUncompress( const QByteArray & data, const int size ) +{ + if ( data.size() <= 4 ) { + qWarning( "gUncompress: Input data is truncated" ); + return QByteArray(); + } + + return gUncompress( data.data(), size ); +} + // see bsa.h BSA::BSA( const QString & filename ) : FSArchiveFile(), bsa( filename ), bsaInfo( QFileInfo(filename) ), status( "initialized" ) @@ -575,8 +581,27 @@ bool BSA::fileContents( const QString & fn, QByteArray & content ) ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp break; - case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC4_UNORM: case DXGI_FORMAT_BC1_UNORM_SRGB: + ddsHeader.ddspf.dwFlags = DDS_FOURCC; + ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'D', 'X', '1', '0' ); + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height / 2; + + dx10 = true; + dx10Header.dxgiFormat = DXGI_FORMAT( file->tex.header.format ); + break; + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + ddsHeader.ddspf.dwFlags = DDS_FOURCC; + ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'D', 'X', '1', '0' ); + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height * 4; + + dx10 = true; + dx10Header.dxgiFormat = DXGI_FORMAT( file->tex.header.format ); + break; + + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC5_SNORM: case DXGI_FORMAT_BC2_UNORM_SRGB: case DXGI_FORMAT_BC3_UNORM_SRGB: case DXGI_FORMAT_BC7_UNORM_SRGB: @@ -587,7 +612,6 @@ bool BSA::fileContents( const QString & fn, QByteArray & content ) dx10 = true; dx10Header.dxgiFormat = DXGI_FORMAT( file->tex.header.format ); break; - default: supported = false; break; @@ -620,7 +644,7 @@ bool BSA::fileContents( const QString & fn, QByteArray & content ) // Start at 1st chunk now for ( int i = 0; i < file->tex.chunks.count(); i++ ) { - F4TexChunk chunk = file->tex.chunks[i]; + const F4TexChunk & chunk = file->tex.chunks[i]; if ( bsa.seek( chunk.offset ) ) { QByteArray chunkData; @@ -873,7 +897,7 @@ bool BSAProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex & sourceP if ( filetypes.count() ) { typeMatch = false; for ( auto f : filetypes ) { - typeMatch |= key1.endsWith( f ); + typeMatch |= key1.endsWith( f, Qt::CaseInsensitive ); } } From c70b73a5d019389c1165139ab5f8aa90da3afc7c Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 11 Nov 2018 12:29:36 -0500 Subject: [PATCH 012/118] [GL] Fix init problems on some Windows installs Some platforms were trying to choose ANGLE instead of OpenGL. --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 0a351c5e3..efc9d8877 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,7 +69,7 @@ int main( int argc, char * argv[] ) QScopedPointer app( createApplication( argc, argv ) ); if ( auto a = qobject_cast(app.data()) ) { - + QApplication::setAttribute( Qt::AA_UseDesktopOpenGL ); a->setOrganizationName( "NifTools" ); a->setOrganizationDomain( "niftools.org" ); a->setApplicationName( "NifSkope " + NifSkopeVersion::rawToMajMin( NIFSKOPE_VERSION ) ); From c3c42b665f470c9c77973765b71e6ec8a09e8e2e Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Sun, 11 Nov 2018 12:32:05 -0500 Subject: [PATCH 013/118] [GL] Fix NiStencilProperty potential rendering crash Rendering support added for some versions of NiStencilProperty could crash. --- src/gl/glproperty.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 1fd81254a..a4ef0aac2 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -786,10 +786,10 @@ void StencilProperty::update( const NifModel * nif, const QModelIndex & block ) int drawMode = 0; if ( nif->checkVersion( 0, 0x14000005 ) ) { drawMode = nif->get( iBlock, "Draw Mode" ); - func = funcMap[std::max(nif->get( iBlock, "Stencil Function" ), (quint32)TEST_MAX - 1 )]; - failop = opMap[std::max( nif->get( iBlock, "Fail Action" ), (quint32)ACTION_MAX - 1 )]; - zfailop = opMap[std::max( nif->get( iBlock, "Z Fail Action" ), (quint32)ACTION_MAX - 1 )]; - zpassop = opMap[std::max( nif->get( iBlock, "Pass Action" ), (quint32)ACTION_MAX - 1 )]; + func = funcMap[std::min(nif->get( iBlock, "Stencil Function" ), (quint32)TEST_MAX - 1 )]; + failop = opMap[std::min( nif->get( iBlock, "Fail Action" ), (quint32)ACTION_MAX - 1 )]; + zfailop = opMap[std::min( nif->get( iBlock, "Z Fail Action" ), (quint32)ACTION_MAX - 1 )]; + zpassop = opMap[std::min( nif->get( iBlock, "Pass Action" ), (quint32)ACTION_MAX - 1 )]; stencil = (nif->get( iBlock, "Stencil Enabled" ) & ENABLE_MASK); } else { auto flags = nif->get( iBlock, "Flags" ); From 4e3facd0b616ac17f77d300ff5d0ea06ec3a3d73 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 12 Nov 2018 10:51:30 -0500 Subject: [PATCH 014/118] Hack for stream 155 shader properties nifxml does not have sufficient means to deal with short circuiting of blocks based on a string value. Also some general fixes. --- src/io/nifstream.cpp | 5 +++++ src/io/nifstream.h | 2 ++ src/model/nifmodel.cpp | 50 +++++++++++++++++++++++++++++++++--------- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp index 0a7619c97..c4db148ee 100644 --- a/src/io/nifstream.cpp +++ b/src/io/nifstream.cpp @@ -486,6 +486,11 @@ bool NifIStream::read( NifValue & val ) return false; } +void NifIStream::reset() +{ + dataStream->device()->reset(); +} + /* * NifOStream diff --git a/src/io/nifstream.h b/src/io/nifstream.h index 1f6a866a4..c394d3a2d 100644 --- a/src/io/nifstream.h +++ b/src/io/nifstream.h @@ -60,6 +60,8 @@ class NifIStream final //! Reads a NifValue from the underlying device. Returns true if successful. bool read( NifValue & ); + void reset(); + private: //! The model that data is being read into. BaseModel * model; diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index d62f71e1d..7a3eedf4a 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -670,8 +670,14 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at ) branch->prepareInsert( block->types.count() ); - for ( const NifData& data : block->types ) { - insertType( branch, data ); + if ( getUserVersion2() == 155 && identifier.startsWith( "BSLighting" ) ) { + for ( const NifData& data : block->types ) { + insertType( branch, data ); + } + } else { + for ( const NifData& data : block->types ) { + insertType( branch, data ); + } } if ( state != Loading ) { @@ -2004,7 +2010,7 @@ bool NifModel::load( QIODevice & device ) catch ( QString & err ) { if ( msgMode == UserMessage ) { - Message::append( nullptr, tr( readFail ), err, QMessageBox::Critical ); + Message::append( nullptr, tr( readFail ), QString( "Pos %1: " ).arg( device.pos() ) + err, QMessageBox::Critical ); } else { testMsg( err ); } @@ -2282,6 +2288,14 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) if ( !parent ) return false; + bool stopRead = false; + if ( getUserVersion2() == 155 && parent->parent() == root ) { + if ( parent->name() == "BSLightingShaderProperty" || parent->name() == "BSEffectShaderProperty" ) + stopRead = true; + } + + QString name; + for ( auto child : parent->children() ) { if ( !child->isConditionless() ) child->invalidateCondition(); @@ -2303,6 +2317,18 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) return false; } } + + if ( stopRead && child->name() == "Name" ) { + auto idx = child->value().get(); + if ( idx != -1 ) { + NifItem * header = getHeaderItem(); + QModelIndex stringIndex = createIndex( header->row(), 0, header ); + name = get( this->index( idx, 0, getIndex( stringIndex, "Strings" ) ) ); + } + } + + if ( stopRead && child->name() == "Controller" && !name.isEmpty() ) + break; } return true; @@ -2315,12 +2341,16 @@ bool NifModel::loadHeader( NifItem * header, NifIStream & stream ) if ( !header ) return false; + // Load Version String to set NifModel state + NifValue verstr = NifValue(NifValue::tHeaderString); + stream.read(verstr); + // Reset Stream Device + stream.reset(); + set( header, "User Version", 0 ); set( getItem(header, "BS Header"), "BS Version", 0 ); - - invalidateConditions( header, false ); - - return loadItem( header, stream ); + invalidateConditions(header, true); + return loadItem(header, stream); } bool NifModel::saveItem( NifItem * parent, NifOStream & stream ) const @@ -2910,12 +2940,12 @@ bool NifModel::assignString( NifItem * item, const QString & string, bool replac } QVector stringVector = getArray( iArray ); - idx = stringVector.indexOf( string ); + auto newidx = stringVector.indexOf( string ); // Already exists. Just update the Index - if ( idx >= 0 && idx < stringVector.size() ) { + if ( newidx >= 0 && newidx < stringVector.size() ) { v.changeType( NifValue::tStringIndex ); - return set( pItem, idx ); + return set( pItem, newidx ); } // Append string to end of list From 022fd5bf993b8e6663240058db6d9150dc3e0865 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Mon, 12 Nov 2018 10:54:58 -0500 Subject: [PATCH 015/118] Material versions 1-20 support --- src/io/material.cpp | 218 +++++++++++++++++++++++++++----------------- src/io/material.h | 36 +++++++- 2 files changed, 165 insertions(+), 89 deletions(-) diff --git a/src/io/material.cpp b/src/io/material.cpp index bc2f3c01d..036e2fc35 100644 --- a/src/io/material.cpp +++ b/src/io/material.cpp @@ -58,7 +58,7 @@ Material::Material( QString name ) fileExists = !data.isEmpty(); } -bool Material::readFile() +bool Material::openFile() { if ( data.isEmpty() ) return false; @@ -75,28 +75,40 @@ bool Material::readFile() if ( magic != BGSM && magic != BGEM ) return false; - in >> version >> tileFlags; - - bTileU = (tileFlags & 0x2) != 0; - bTileV = (tileFlags & 0x1) != 0; - - in >> fUOffset >> fVOffset >> fUScale >> fVScale; - in >> fAlpha; - in >> bAlphaBlend >> iAlphaSrc >> iAlphaDst; - in >> iAlphaTestRef; - in >> bAlphaTest >> bZBufferWrite >> bZBufferTest; - in >> bScreenSpaceReflections >> bWetnessControl_ScreenSpaceReflections; - in >> bDecal >> bTwoSided >> bDecalNoFade >> bNonOccluder; - in >> bRefraction >> bRefractionFalloff >> fRefractionPower; - in >> bEnvironmentMapping >> fEnvironmentMappingMaskScale; - in >> bGrayscaleToPaletteColor; - - return in.status() == QDataStream::Ok; + return readFile(); } return false; } +bool Material::readFile() +{ + in >> version; + + in >> tileFlags; + bTileU = (tileFlags & 0x2) != 0; + bTileV = (tileFlags & 0x1) != 0; + + in >> fUOffset >> fVOffset >> fUScale >> fVScale; + in >> fAlpha; + in >> bAlphaBlend >> iAlphaSrc >> iAlphaDst; + in >> iAlphaTestRef; + in >> bAlphaTest >> bZBufferWrite >> bZBufferTest; + in >> bScreenSpaceReflections >> bWetnessControl_ScreenSpaceReflections; + in >> bDecal >> bTwoSided >> bDecalNoFade >> bNonOccluder; + in >> bRefraction >> bRefractionFalloff >> fRefractionPower; + in >> bEnvironmentMapping; + if ( version < 10 ) + in >> fEnvironmentMappingMaskScale; + + in >> bGrayscaleToPaletteColor; + + if ( version >= 6 ) + in >> ucMaskWrites; + + return in.status() == QDataStream::Ok; +} + QByteArray Material::find( QString path ) { QSettings settings; @@ -166,106 +178,140 @@ QString Material::getPath() const ShaderMaterial::ShaderMaterial( QString name ) : Material( name ) { if ( fileExists ) - readable = readFile(); + readable = openFile(); } bool ShaderMaterial::readFile() { - if ( data.isEmpty() || !Material::readFile() ) - return false; + Material::readFile(); - QBuffer f( &data ); - if ( f.open( QIODevice::ReadOnly ) ) { - in.setDevice( &f ); - in.setByteOrder( QDataStream::LittleEndian ); - in.setFloatingPointPrecision( QDataStream::SinglePrecision ); + size_t numTex = (version >= 17) ? 10 : 9; + for ( int i = 0; i < numTex; i++ ) { + char * str; + in >> str; + textureList << QString( str ); + } - in.skipRawData( 63 ); + in >> bEnableEditorAlphaRef; + if ( version >= 8 ) { + in >> bTranslucency >> bTranslucencyThickObject >> bTranslucencyMixAlbedoWithSubsurfaceCol; + in >> subR >> subG >> subB; + cTranslucencySubsurfaceColor.setRGB( subR, subG, subB ); + in >> fTranslucencyTransmissiveScale >> fTranslucencyTurbulence; + } + else + in >> bRimLighting >> fRimPower >> fBacklightPower >> bSubsurfaceLighting >> fSubsurfaceLightingRolloff; - for ( int i = 0; i < 9; i++ ) { - char * str; - in >> str; - textureList << QString( str ); - } + in >> bSpecularEnabled; + in >> specR >> specG >> specB; + cSpecularColor.setRGB( specR, specG, specB ); + in >> fSpecularMult >> fSmoothness; + in >> fFresnelPower; + in >> fWetnessControl_SpecScale >> fWetnessControl_SpecPowerScale >> fWetnessControl_SpecMinvar; + if ( version < 10 ) + in >> fWetnessControl_EnvMapScale; - in >> bEnableEditorAlphaRef >> bRimLighting; - in >> fRimPower >> fBacklightPower; - in >> bSubsurfaceLighting >> fSubsurfaceLightingRolloff; - in >> bSpecularEnabled; - in >> specR >> specG >> specB; - cSpecularColor.setRGB( specR, specG, specB ); - in >> fSpecularMult >> fSmoothness >> fFresnelPower; - in >> fWetnessControl_SpecScale >> fWetnessControl_SpecPowerScale >> fWetnessControl_SpecMinvar; - in >> fWetnessControl_EnvMapScale >> fWetnessControl_FresnelPower >> fWetnessControl_Metalness; + in >> fWetnessControl_FresnelPower >> fWetnessControl_Metalness; - char * rootMaterialStr; - in >> rootMaterialStr; - sRootMaterialPath = QString( rootMaterialStr ); + if ( version > 2 ) + in >> bPBR; - in >> bAnisoLighting >> bEmitEnabled; + if ( version >= 9 ) + in >> bCustomPorosity >> fPorosityValue; - if ( bEmitEnabled ) - in >> emitR >> emitG >> emitB; - cEmittanceColor.setRGB( emitR, emitG, emitB ); + char * rootMaterialStr; + in >> rootMaterialStr; + sRootMaterialPath = QString( rootMaterialStr ); - in >> fEmittanceMult >> bModelSpaceNormals; - in >> bExternalEmittance >> bBackLighting; - in >> bReceiveShadows >> bHideSecret >> bCastShadows; - in >> bDissolveFade >> bAssumeShadowmask >> bGlowmap; - in >> bEnvironmentMappingWindow >> bEnvironmentMappingEye; - in >> bHair >> hairR >> hairG >> hairB; - cHairTintColor.setRGB( hairR, hairG, hairB ); + in >> bAnisoLighting >> bEmitEnabled; - in >> bTree >> bFacegen >> bSkinTint >> bTessellate; - in >> fDisplacementTextureBias >> fDisplacementTextureScale; - in >> fTessellationPnScale >> fTessellationBaseFactor >> fTessellationFadeDistance; - in >> fGrayscaleToPaletteScale >> bSkewSpecularAlpha; + if ( bEmitEnabled ) + in >> emitR >> emitG >> emitB; + cEmittanceColor.setRGB( emitR, emitG, emitB ); - return in.status() == QDataStream::Ok; + in >> fEmittanceMult >> bModelSpaceNormals; + in >> bExternalEmittance; + if ( version >= 12 ) + in >> fLumEmittance; + if ( version >= 13 ) + in >> bUseAdaptativeEmissive >> fAdaptativeEmissive_ExposureOffset >> fAdaptativeEmissive_FinalExposureMin >> fAdaptativeEmissive_FinalExposureMax; + + if ( version < 8 ) + in >> bBackLighting; + in >> bReceiveShadows >> bHideSecret >> bCastShadows; + in >> bDissolveFade >> bAssumeShadowmask >> bGlowmap; + + if ( version < 7 ) + in >> bEnvironmentMappingWindow >> bEnvironmentMappingEye; + in >> bHair >> hairR >> hairG >> hairB; + cHairTintColor.setRGB( hairR, hairG, hairB ); + + in >> bTree >> bFacegen >> bSkinTint >> bTessellate; + if ( version == 1 ) + in >> fDisplacementTextureBias >> fDisplacementTextureScale >> + fTessellationPnScale >> fTessellationBaseFactor >> fTessellationFadeDistance; + in >> fGrayscaleToPaletteScale >> bSkewSpecularAlpha; + + if ( version >= 3 ) { + in >> bTerrain; + if ( bTerrain ) { + if ( version == 3 ) + in.skipRawData(4); + in >> fTerrainThresholdFalloff >> fTerrainTilingDistance >> fTerrainRotationAngle; + } } - return false; + return in.status() == QDataStream::Ok; } EffectMaterial::EffectMaterial( QString name ) : Material( name ) { if ( fileExists ) - readable = readFile(); + readable = openFile(); } bool EffectMaterial::readFile() { - if ( data.isEmpty() || !Material::readFile() ) - return false; + Material::readFile(); - QBuffer f( &data ); - if ( f.open( QIODevice::ReadOnly ) ) { - in.setDevice( &f ); - in.setByteOrder( QDataStream::LittleEndian ); - in.setFloatingPointPrecision( QDataStream::SinglePrecision ); + size_t numTex = (version >= 10) ? 8 : 5; + for ( int i = 0; i < numTex; i++ ) { + char * str; + in >> str; + textureList << QString( str ); + } - in.skipRawData( 63 ); + if ( version >= 10 ) { + in >> bEnvironmentMapping; + in >> fEnvironmentMappingMaskScale; + } - for ( int i = 0; i < 5; i++ ) { - char * str; - in >> str; - textureList << QString( str ); - } + in >> bBloodEnabled >> bEffectLightingEnabled; + in >> bFalloffEnabled >> bFalloffColorEnabled; + in >> bGrayscaleToPaletteAlpha >> bSoftEnabled; + in >> baseR >> baseG >> baseB; - in >> bBloodEnabled >> bEffectLightingEnabled; - in >> bFalloffEnabled >> bFalloffColorEnabled; - in >> bGrayscaleToPaletteAlpha >> bSoftEnabled; - in >> baseR >> baseG >> baseB; + cBaseColor.setRGB( baseR, baseG, baseB ); - cBaseColor.setRGB( baseR, baseG, baseB ); + in >> fBaseColorScale; + in >> fFalloffStartAngle >> fFalloffStopAngle; + in >> fFalloffStartOpacity >> fFalloffStopOpacity; + in >> fLightingInfluence >> iEnvmapMinLOD >> fSoftDepth; - in >> fBaseColorScale; - in >> fFalloffStartAngle >> fFalloffStopAngle; - in >> fFalloffStartOpacity >> fFalloffStopOpacity; - in >> fLightingInfluence >> iEnvmapMinLOD >> fSoftDepth; + if ( version >= 11 ) { + in >> emitR >> emitG >> emitB; + cEmittanceColor.setRGB( emitR, emitG, emitB ); + + if ( version >= 15 ) { + in >> fAdaptativeEmissive_ExposureOffset >> fAdaptativeEmissive_FinalExposureMin >> fAdaptativeEmissive_FinalExposureMax; + + if ( version >= 16 ) + in >> bGlowmap; - return in.status() == QDataStream::Ok; + if ( version >= 20 ) + in >> bEffectPbrSpecular; + } } - return false; + return in.status() == QDataStream::Ok; } diff --git a/src/io/material.h b/src/io/material.h index df4465b1b..3f953df30 100644 --- a/src/io/material.h +++ b/src/io/material.h @@ -61,6 +61,7 @@ class Material : public QObject QString getPath() const; protected: + bool openFile(); virtual bool readFile(); QByteArray find( QString path ); QString toLocalPath( QString path ) const; @@ -108,6 +109,17 @@ class Material : public QObject quint8 bEnvironmentMapping = 0; float fEnvironmentMappingMaskScale = 1.0; quint8 bGrayscaleToPaletteColor = 1.0; + quint8 ucMaskWrites = 63; + + float emitR = 0, emitG = 0, emitB = 0; + Color3 cEmittanceColor; + + quint8 bGlowmap = 0; + + float fLumEmittance = 100.0; + float fAdaptativeEmissive_ExposureOffset = 13.5; + float fAdaptativeEmissive_FinalExposureMin = 2.0; + float fAdaptativeEmissive_FinalExposureMax = 3.0; }; @@ -147,8 +159,6 @@ class ShaderMaterial : public Material QString sRootMaterialPath; quint8 bAnisoLighting = 0; quint8 bEmitEnabled = 0; - float emitR = 0, emitG = 0, emitB = 0; - Color3 cEmittanceColor; float fEmittanceMult = 0; quint8 bModelSpaceNormals = 0; quint8 bExternalEmittance = 0; @@ -158,7 +168,6 @@ class ShaderMaterial : public Material quint8 bCastShadows = 1; quint8 bDissolveFade = 0; quint8 bAssumeShadowmask = 0; - quint8 bGlowmap = 0; quint8 bEnvironmentMappingWindow = 0; quint8 bEnvironmentMappingEye = 0; quint8 bHair = 0; @@ -176,6 +185,25 @@ class ShaderMaterial : public Material float fGrayscaleToPaletteScale = 0; quint8 bSkewSpecularAlpha = 0; + quint8 bPBR = 0; + + quint8 bTranslucency = 0; + quint8 bTranslucencyThickObject = 0; + quint8 bTranslucencyMixAlbedoWithSubsurfaceCol = 0; + float subR = 0, subG = 0, subB = 0; + Color3 cTranslucencySubsurfaceColor; + float fTranslucencyTransmissiveScale = 0.0; + float fTranslucencyTurbulence = 0.0; + + quint8 bCustomPorosity = 0; + float fPorosityValue = 0.0; + + quint8 bUseAdaptativeEmissive = 0; + + quint8 bTerrain = 0; + float fTerrainThresholdFalloff = 0.0; + float fTerrainTilingDistance = 0.0; + float fTerrainRotationAngle = 0.0; }; @@ -210,6 +238,8 @@ class EffectMaterial : public Material float fLightingInfluence = 1.0; quint8 iEnvmapMinLOD = 0; float fSoftDepth = 100.0; + + quint8 bEffectPbrSpecular = 0; }; From 072f35004632634a8fece81aa345c12a8ae869da Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 15 Nov 2018 13:36:44 -0500 Subject: [PATCH 016/118] [GL] Initial stream 155 renderer support Very minimal rendering and shader support for new Lighting and Effect shaders. For Lighting, mostly correctly uses the new PBR pipeline with metallicity/reflectivity and an external cubemap (currently 1 hardcoded). New emissive masks and AO masks are also used to various degrees. AO mask is used for specular occlusion. Uses sRGB framebuffer when needed. Does not add support for sRGB textures in SSE and FO4 NIFs. --- res/shaders/cubemap.dds | Bin 32888 -> 524408 bytes res/shaders/default_ns.dds | Bin 0 -> 22020 bytes res/shaders/f76_default.frag | 348 ++++++++++++++++++++++++++++++ res/shaders/f76_default.prog | 20 ++ res/shaders/f76_default.vert | 117 ++++++++++ res/shaders/f76_effectshader.frag | 165 ++++++++++++++ res/shaders/f76_effectshader.prog | 15 ++ res/shaders/lighting.dds | Bin 0 -> 312 bytes src/gl/bsshape.cpp | 21 +- src/gl/glmesh.cpp | 6 + src/gl/glproperty.cpp | 134 +++++++++--- src/gl/glproperty.h | 80 ++++++- src/gl/renderer.cpp | 34 ++- src/gl/renderer.h | 14 +- src/glview.cpp | 24 ++- src/spells/blocks.cpp | 2 +- src/spells/mesh.cpp | 2 +- src/spells/sanitize.cpp | 12 +- 18 files changed, 932 insertions(+), 62 deletions(-) create mode 100644 res/shaders/default_ns.dds create mode 100644 res/shaders/f76_default.frag create mode 100644 res/shaders/f76_default.prog create mode 100644 res/shaders/f76_default.vert create mode 100644 res/shaders/f76_effectshader.frag create mode 100644 res/shaders/f76_effectshader.prog create mode 100644 res/shaders/lighting.dds diff --git a/res/shaders/cubemap.dds b/res/shaders/cubemap.dds index df43eb8cd332703834073bbce2ee3896d037053f..777038385ebc84aa172825133e5feead64a33082 100644 GIT binary patch literal 524408 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�)yrg@+Gj$Ui+pAYs6 zu#rJ*om`R{eiU^kG#+9=@OJ z8nFbske%dYOtH+_XZ!2)eA$HTKsM0WTxTN-x^3&GVf*AQr^xf^>oLFYGu-%o*t-?# z`=PgY+aFw0BV5#dP zugiFB7f!@~aV58X4p;p^mT*rm@QfTUTk#qZmyze=W6?hJ9N`}yPuqj+%3yo|=>rl=D$)P-Xu{Tkh1S^FWyREfQsniS^m}tMiT%0Ecu@mP zspmugFPFN19yTFdg}ZZtYs48=OpM4PXGrB`dVe6lfc^oGWz9L;@t6ISm8OUX89Wyr zfG3Kvt)}KGTRhflmz>(-`!>A3e_C%xbDXE}{R_R$cUv;c6=MFLbYCyl_r&`i^Dmd* zN6uew7oS)I_RH+#v87)B4>>sBqx-FLEY!5y5+hL z?8|w5nQRuki>L(_V*?dDRz#kVLoBGOC%5nGx8C_f*0*4h^}+#-9bG)X7@lu*8xb$n zwRc(P$TDl_>_Pj(C&c|M{69BawO;UM<>t&n6U^Wo5cb(xDTNGA$U@k3sU znsE;OLhL8Yeg7>*;BAHF;J zpZry}5cb3BOqMW5?f94B=fYJSEnlWyqItn$YHMLOC}8L~@_vq~{5ig_-dhX&Rf~77 z*W+L(yfrJLSg-!GxL?}wdKQxmG|=(COmE3Bru z&GFC1$1^h&v&sAL+471atFN!L%Bm7-zMT01);ZWmipR4vq!Xs_+^JSoS!g}|oz~se zWUVdLR#QzZ!ZtEyo@p8IzxoI@^b1PLOVF6~lF*J-XvC)aA{!cNw{>gg!R3cN&->!% zub{7c33Wr&dBFaYLGLT(yXNy*uKnr0Uaaq3ht|L53UvM@==_V;C-HurojSfU?fWOs zUz+%|;(y5hLrq?Bw}QuX-iPvMzFU^~yM%s8>;nqpaxjoxC{J*E06*D4DKW$Mhhqy~ zH}Ka68wkEG4Z!E{9>vVys{SU;E&P@1DQB##t#d!F1WVyr0_PWjSvh}OA>3Vvua|MX zRCvP;W&FK`#l=jXSiH#H$=kvE0@NX*PF>(^@s;RNe+M3I3-G(vSvv;jscg=o3 zpC{z`VU{ah-`6ac=J?$Asq?%))c8VeAHC39vtY zcsyuxzQZs_FYXUIP_edD-#I=gyyUyW*=vJIA5b7iU3G?^WM#_8hRJE|4YM zAaX~=3fYG^qC~R;;Ge_XNTJUX=C^ zTi82Z0XxuiufB^i2Z$NO|(ZPJq7T|&IUVb0MdaOV4F$Y zS5BZMZjc^OeL%fHX@VTILCWu=FN9{4ZqRddzw|>Mwva_Hv7#E=EvHriuN0#LTboM3 zeucgNM)qpmah>~q*sm+S7xrwaw-aYLLv24|PfzcfZC>LM=S$~bevx#({9f2A-cMkE z{KyipUt~x2k9z$>ejM}tBI$j6!F^kJ%QslU%yCcf*W(dy@wIZk;z^ImF7$lob6tX| zZYzg~_^S`=y*TCI_^k9@75JB7cZI}q`FnXKwRzsRn%EFHo_K_>2mF=$QR~TJe;4_F z39k|77s-~|dab&nAN=#JuC)#AKabpR#5&=D_MSfH0>uySE5Uifzpi_}we$}WJNSFy z5^4PuI$jzuOSzsd73WzK@g9B1W6A}^11TOH>Dfd$kJbp7nJ-bFuYs#U=I zUEPh=+g)$x?b%|t+;XGcb>|K4_riYiIUSR+_uBs3_uQ9HC}#5qv8p6BWbrV)F`iTfhEDUlFmr#d`4fz7gTyhaRBT0}p7PPkn(BY(}-> zhR$AULc}1rznK1?@_+B|=Vwy)Q|)e+Yra%AKF*TX_uL;$3&;iYwS>20O)bFdy`j#d zoS=kUU$sAJMCFdk3z{0sZOf)*_Nm)HgwDU-_i4xZ-3h;+T;JQv4Bte3FZzAJek*<- z>UwYWd-44RhnL!sedBg`?;@}twFA57J9f(Ny{5x=8*@O}fbdKEcye9XyUr7S!jUDv z*1j*tllygB%U>@|pjiv>*L@yC{Bd5dnLCzxwL$Zd%Q1ga3J(;L-%0D2aHfNDyISzA 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%0A~R`?C9V0Q}gO-@O{d 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 Ipbrku{YfAO=m<#E_n(Efkj2d|CLymcHE&}G% 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 z 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 literal 32888 zcmeI)F$%&!5QfpwLhQ^D_9zOuf!DF{37> z3Mim}0tzUgfC37%3S?3D;j498F~9%=3^2d|0}L?000Rs#zyJdbFu=gy8}N6kA&~pP zXutpi3^2d|0}L?000Rs#zyJdbFu(u<9RsQ4MO6(LV1NMz7+`<_1{h#~0R|XgfB^;= JV1R+9fgQ!tx7h#y diff --git a/res/shaders/default_ns.dds b/res/shaders/default_ns.dds new file mode 100644 index 0000000000000000000000000000000000000000..1969ac7d746beaa749480bf9d7705413f12a8fa8 GIT binary patch literal 22020 zcmeIuF%Cdb3BX9%5y8(m47#!UbHU_KY8`>s++vQ7X?zf1jyEgVb&x+}6 z5zRF|_dZ`%xzaigZ%<7(J>n%N<((Fft$+Xl0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U WAV7cs0RjXF5FkK+009F37B~Pc2?VkL literal 0 HcmV?d00001 diff --git a/res/shaders/f76_default.frag b/res/shaders/f76_default.frag new file mode 100644 index 000000000..7b5880ef9 --- /dev/null +++ b/res/shaders/f76_default.frag @@ -0,0 +1,348 @@ +#version 130 +#extension GL_ARB_shader_texture_lod : require + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +uniform sampler2D GlowMap; +uniform sampler2D ReflMap; +uniform sampler2D LightingMap; +uniform sampler2D GreyscaleMap; +uniform samplerCube CubeMap; + +uniform vec4 solidColor; +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; // "Smoothness" in FO4; 0-1 +uniform float fresnelPower; + +uniform float paletteScale; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform float alpha; + +uniform vec3 tintColor; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasGlowMap; +uniform bool hasSoftlight; +uniform bool hasTintColor; +uniform bool hasCubeMap; +uniform bool hasSpecularMap; +uniform bool greyscaleColor; +uniform bool doubleSided; + +uniform float subsurfaceRolloff; +uniform float rimPower; +uniform float backlightPower; + +uniform float envReflection; + +uniform bool isWireframe; +uniform bool isSkinned; +uniform mat4 worldMatrix; + +in vec3 LightDir; +in vec3 ViewDir; + +in vec4 A; +in vec4 C; +in vec4 D; + +in vec3 N; +in vec3 t; +in vec3 b; + +in mat4 reflMatrix; + +#ifndef M_PI + #define M_PI 3.1415926535897932384626433832795 +#endif + +#define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0 + FLT_EPSILON != 1.0 + +float G1V(float NdotV, float k) +{ + return 1.0 / (NdotV * (1.0 - k) + k); +} + +float LightingFuncGGX_REF0(float NdotL, float NdotH, float NdotV, float LdotH, float roughness, float F0) +{ + float alpha = roughness * roughness; + float F, D, vis; + // D + float alphaSqr = alpha * alpha; + float denom = NdotH * NdotH * (alphaSqr - 1.0) + 1.0; + D = alphaSqr / (M_PI * denom * denom); + // F + float LdotH5 = pow(1.0 - LdotH, 5); + F = F0 + (1.0 - F0) * LdotH5; + + // V + float k = alpha/2.0f; + vis = G1V(NdotL, k) * G1V(NdotV, k); + + float specular = NdotL * D * F * vis; + return specular; +} + +vec3 LightingFuncGGX_REF(float NdotL, float NdotH, float NdotV, float LdotH, float roughness, vec3 F0, float specOcc) +{ + float alpha = roughness * roughness; + // D (GGX normal distribution) + float alphaSqr = alpha * alpha; + float denom = NdotH * NdotH * (alphaSqr - 1.0) + 1.0; + float D = alphaSqr / (denom * denom); + // no pi because BRDF -> lighting + // F (Fresnel term) + float F_a = 1.0; + float F_b = pow(1.0 - LdotH, 5.0); + vec3 F = mix(vec3(F_b), vec3(F_a), F0) * specOcc; + // G (remapped hotness, see Unreal Shading) + float k = (alpha + 2 * roughness + 1) / 8.0; + float G = NdotL / (mix(NdotL, 1, k) * mix(NdotV, 1, k)); + + return D * F * G / 4.0; +} + +float OrenNayar(vec3 L, vec3 V, vec3 N, float roughness, float NdotL) +{ + //float NdotL = dot(N, L); + float NdotV = dot(N, V); + float LdotV = dot(L, V); + + float rough2 = roughness * roughness; + + float A = 1.0 - 0.5 * (rough2 / (rough2 + 0.57)); + float B = 0.45 * (rough2 / (rough2 + 0.09)); + + float a = min( NdotV, NdotL ); + float b = max( NdotV, NdotL ); + b = (sign(b) == 0.0) ? FLT_EPSILON : sign(b) * max( 0.01, abs(b) ); // For fudging the smoothness of C + float C = sqrt( (1.0 - a * a) * (1.0 - b * b) ) / b; + + float gamma = LdotV - NdotL * NdotV; + float L1 = A + B * max( gamma, FLT_EPSILON ) * C; + + return L1 * max( NdotL, FLT_EPSILON ); +} + +float OrenNayarFull(vec3 L, vec3 V, vec3 N, float roughness, float NdotL) +{ + //float NdotL = dot(N, L); + float NdotV = dot(N, V); + float LdotV = dot(L, V); + + float angleVN = acos(max(NdotV, FLT_EPSILON)); + float angleLN = acos(max(NdotL, FLT_EPSILON)); + + float alpha = max(angleVN, angleLN); + float beta = min(angleVN, angleLN); + float gamma = LdotV - NdotL * NdotV; + + float roughnessSquared = roughness * roughness; + float roughnessSquared9 = (roughnessSquared / (roughnessSquared + 0.09)); + + // C1, C2, and C3 + float C1 = 1.0 - 0.5 * (roughnessSquared / (roughnessSquared + 0.33)); + float C2 = 0.45 * roughnessSquared9; + + if( gamma >= 0.0 ) { + C2 *= sin(alpha); + } else { + C2 *= (sin(alpha) - pow((2.0 * beta) / M_PI, 3.0)); + } + + float powValue = (4.0 * alpha * beta) / (M_PI * M_PI); + float C3 = 0.125 * roughnessSquared9 * powValue * powValue; + + // Avoid asymptote at pi/2 + float asym = M_PI / 2.0; + float lim1 = asym + 0.01; + float lim2 = asym - 0.01; + + float ab2 = (alpha + beta) / 2.0; + + if ( beta >= asym && beta < lim1 ) + beta = lim1; + else if ( beta < asym && beta >= lim2 ) + beta = lim2; + + if ( ab2 >= asym && ab2 < lim1 ) + ab2 = lim1; + else if ( ab2 < asym && ab2 >= lim2 ) + ab2 = lim2; + + // Reflection + float A = gamma * C2 * tan(beta); + float B = (1.0 - abs(gamma)) * C3 * tan(ab2); + + float L1 = max(FLT_EPSILON, NdotL) * (C1 + A + B); + + // Interreflection + float twoBetaPi = 2.0 * beta / M_PI; + float L2 = 0.17 * max(FLT_EPSILON, NdotL) * (roughnessSquared / (roughnessSquared + 0.13)) * (1.0 - gamma * twoBetaPi * twoBetaPi); + + return L1 + L2; +} + +// Schlick's Fresnel approximation +float fresnelSchlick(float VdotH, float F0) +{ + float base = 1.0 - VdotH; + float exp = pow(base, fresnelPower); + return clamp(exp + F0 * (1.0 - exp), 0.0, 1.0); +} + +vec3 fresnelSchlickRoughness(float NdotV, vec3 F0, float roughness) +{ + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - NdotV, 5.0); +} + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec4 colorLookup(float x, float y) { + + return texture2D(GreyscaleMap, vec2(clamp(x, 0.0, 1.0), clamp(y, 0.0, 1.0))); +} + +void main(void) +{ + if ( isWireframe ) { + gl_FragColor = solidColor; + return; + } + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D(BaseMap, offset); + vec4 normalMap = texture2D(NormalMap, offset); + vec4 lightingMap = texture2D(LightingMap, offset); + vec4 reflMap = texture2D(ReflMap, offset); + vec4 glowMap = texture2D(GlowMap, offset); + + vec3 normal = normalMap.rgb; + // Calculate missing blue channel + normal.b = sqrt(1.0 - dot(normal.rg, normal.rg)); + if ( !gl_FrontFacing && doubleSided ) { + normal *= -1.0; + } + // For _msn (Test with FSF1_Face) + //normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); + + vec3 L = normalize(LightDir); + vec3 V = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize(L + V); + + float NdotL = dot(normal, L); + float NdotL0 = max(NdotL, FLT_EPSILON); + float NdotH = max(dot(normal, H), FLT_EPSILON); + float NdotV = max(dot(normal, V), FLT_EPSILON); + float VdotH = max(dot(V, H), FLT_EPSILON); + float LdotH = max(dot(L, H), FLT_EPSILON); + float NdotNegL = max(dot(normal, -L), FLT_EPSILON); + + vec3 reflected = reflect(V, normal); + vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; + vec3 reflectedWS = vec3(reflMatrix * (gl_ModelViewMatrixInverse * vec4(reflectedVS, 0.0))); + + vec4 color; + vec3 albedo = baseMap.rgb * C.rgb; + vec3 diffuse = A.rgb + D.rgb * NdotL0; + if ( greyscaleColor ) { + vec4 luG = colorLookup(baseMap.g, paletteScale - (1 - C.r)); + + albedo = luG.rgb; + } + + // Emissive + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + if ( hasGlowMap ) { + emissive *= glowMap.rgb; + } + } else if ( hasGlowMap ) { + emissive += glowMap.rgb * glowMult; + } + + emissive *= lightingMap.a; + //float f0 = 1.0; + float metallicity = max(reflMap.r, max(reflMap.g, reflMap.b)); + //f0 = mix(0.02, 0.9, metallicity); + + vec3 f0 = (reflMap.g == 0 && reflMap.b == 0) ? vec3(reflMap.r) : reflMap.rgb; + f0 = max(vec3(0.01), f0); + + // Specular + float g = 1.0; + float smoothness = clamp(specGlossiness, 0.0, 1.0); + float specOcc = 1.0; + vec3 spec = vec3(0.0); + if ( hasSpecularMap ) { + g = lightingMap.r; + specOcc = lightingMap.g; + smoothness = g * smoothness; + spec = vec3(LightingFuncGGX_REF(NdotL0, NdotH, NdotV, LdotH, 1.0 - smoothness, f0, specOcc)) * NdotL0 * D.rgb; + spec *= 1.0 - metallicity; + } + + // Diffuse + float diff = OrenNayarFull(L, V, normal, 1.0 - smoothness, NdotL); + diffuse = vec3(diff); + diffuse *= 1.0 - metallicity; + + // Environment + vec4 cube = textureLod(CubeMap, reflectedWS, 8.0 - smoothness * 8.0); + vec3 refl = vec3(0.0); + if ( hasCubeMap ) { + cube.rgb *= envReflection * specStrength; + + // diffuse term alt: (vec3(1.0 - diff) * spec) + (vec3(1.0 - diff) * albedo) + refl += cube.rgb * fresnelSchlickRoughness(NdotV, f0, 1.0 - smoothness) * D.rgb * specOcc; + } + + //vec3 soft = vec3(0.0); + //float wrap = NdotL; + //if ( hasSoftlight || subsurfaceRolloff > 0.0 ) { + // wrap = (wrap + subsurfaceRolloff) / (1.0 + subsurfaceRolloff); + // soft = albedo * max(0.0, wrap) * smoothstep(1.0, 0.0, sqrt(diff)); + // + // diffuse += soft; + //} + + //if ( hasTintColor ) { + // albedo *= tintColor; + //} + + // Diffuse + color.rgb = diffuse * albedo * D.rgb; + // Ambient + color.rgb += A.rgb * albedo; + // Specular + color.rgb += spec; + color.rgb += refl; + + // Emissive + color.rgb += emissive; + + color.rgb = tonemap(color.rgb) / tonemap(vec3(1.0)); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/f76_default.prog b/res/shaders/f76_default.prog new file mode 100644 index 000000000..854796a23 --- /dev/null +++ b/res/shaders/f76_default.prog @@ -0,0 +1,20 @@ +# default shader + +checkgroup begin and + # Fallout 76 + check HEADER/BS Header/BS Version == 155 + check BSLightingShaderProperty + checkgroup begin or + check BSTriShape + check BSSubIndexTriShape + check BSMeshLODTriShape + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents +texcoords 3 indices +texcoords 4 weights + +shaders f76_default.vert f76_default.frag diff --git a/res/shaders/f76_default.vert b/res/shaders/f76_default.vert new file mode 100644 index 000000000..919eaadb7 --- /dev/null +++ b/res/shaders/f76_default.vert @@ -0,0 +1,117 @@ +#version 130 + +out vec3 LightDir; +out vec3 ViewDir; + +out vec3 N; +out vec3 t; +out vec3 b; +out vec3 v; + +out vec4 A; +out vec4 C; +out vec4 D; + +out mat4 reflMatrix; + +uniform bool isSkinned; +uniform bool isGPUSkinned; +uniform mat4 boneTransforms[100]; +uniform mat4 worldMatrix; + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +void main( void ) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_TexCoord[0] = gl_MultiTexCoord0; + + if ( !isGPUSkinned ) { + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + v = vec3(gl_ModelViewMatrix * gl_Vertex); + reflMatrix = worldMatrix; + if ( isSkinned ) { + mat4 bt = boneTransforms[int(gl_MultiTexCoord3[0])] * gl_MultiTexCoord4[0]; + bt += boneTransforms[int(gl_MultiTexCoord3[1])] * gl_MultiTexCoord4[1]; + bt += boneTransforms[int(gl_MultiTexCoord3[2])] * gl_MultiTexCoord4[2]; + bt += boneTransforms[int(gl_MultiTexCoord3[3])] * gl_MultiTexCoord4[3]; + reflMatrix = inverse(bt); + } + } else if ( isSkinned && isGPUSkinned ) { + mat4 bt = boneTransforms[int(gl_MultiTexCoord3[0])] * gl_MultiTexCoord4[0]; + bt += boneTransforms[int(gl_MultiTexCoord3[1])] * gl_MultiTexCoord4[1]; + bt += boneTransforms[int(gl_MultiTexCoord3[2])] * gl_MultiTexCoord4[2]; + bt += boneTransforms[int(gl_MultiTexCoord3[3])] * gl_MultiTexCoord4[3]; + + vec4 V = bt * gl_Vertex; + vec3 normal = vec3(bt * vec4(gl_Normal, 0.0)); + vec3 tan = vec3(bt * vec4(gl_MultiTexCoord1.xyz, 0.0)); + vec3 bit = vec3(bt * vec4(gl_MultiTexCoord2.xyz, 0.0)); + + gl_Position = gl_ModelViewProjectionMatrix * V; + N = normalize(gl_NormalMatrix * normal); + t = normalize(gl_NormalMatrix * tan); + b = normalize(gl_NormalMatrix * bit); + v = vec3(gl_ModelViewMatrix * V); + + reflMatrix = inverse(bt); + } else { + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + v = vec3(gl_ModelViewMatrix * gl_Vertex); + reflMatrix = worldMatrix; + } + + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} diff --git a/res/shaders/f76_effectshader.frag b/res/shaders/f76_effectshader.frag new file mode 100644 index 000000000..21a74640e --- /dev/null +++ b/res/shaders/f76_effectshader.frag @@ -0,0 +1,165 @@ +#version 130 + +uniform sampler2D BaseMap; +uniform sampler2D GreyscaleMap; +uniform samplerCube CubeMap; +uniform sampler2D NormalMap; +uniform sampler2D ReflMap; +uniform sampler2D LightingMap; + +uniform bool isWireframe; +uniform vec4 solidColor; + +uniform bool doubleSided; + +uniform bool hasSourceTexture; +uniform bool hasGreyscaleMap; +uniform bool hasCubeMap; +uniform bool hasNormalMap; +uniform bool hasEnvMask; + +uniform bool greyscaleAlpha; +uniform bool greyscaleColor; + +uniform bool useFalloff; +uniform bool hasRGBFalloff; + +uniform bool hasWeaponBlood; + +uniform vec4 glowColor; +uniform float glowMult; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform vec4 falloffParams; +uniform float falloffDepth; + +uniform float lightingInfluence; +uniform float envReflection; + +uniform float fLumEmittance; + +uniform mat4 worldMatrix; + +in vec3 LightDir; +in vec3 ViewDir; + +in vec4 A; +in vec4 C; +in vec4 D; + +in vec3 N; +in vec3 t; +in vec3 b; +in vec3 v; + +in mat4 reflMatrix; + +vec4 colorLookup( float x, float y ) { + + return texture2D( GreyscaleMap, vec2( clamp(x, 0.0, 1.0), clamp(y, 0.0, 1.0)) ); +} + +vec3 fresnelSchlickRoughness(float NdotV, vec3 F0, float roughness) +{ + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - NdotV, 5.0); +} + +void main( void ) +{ + if ( isWireframe ) { + gl_FragColor = solidColor; + return; + } + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + vec4 reflMap = texture2D(ReflMap, offset); + vec4 lightingMap = texture2D(LightingMap, offset); + + vec3 normal = normalMap.rgb; + // Calculate missing blue channel + normal.b = sqrt(1.0 - dot(normal.rg, normal.rg)); + if ( !gl_FrontFacing && doubleSided ) { + normal *= -1.0; + } + + vec3 f0 = (reflMap.g == 0 && reflMap.b == 0) ? vec3(reflMap.r) : reflMap.rgb; + vec3 L = normalize(LightDir); + vec3 V = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + V ); + + float NdotL = max( dot(normal, L), 0.000001 ); + float NdotH = max( dot(normal, H), 0.000001 ); + float NdotV = max( dot(normal, V), 0.000001 ); + float LdotH = max( dot(L, H), 0.000001 ); + float NdotNegL = max( dot(normal, -L), 0.000001 ); + + vec3 reflected = reflect( V, normal ); + vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; + vec3 reflectedWS = vec3(reflMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 ))); + + if ( greyscaleAlpha ) + baseMap.a = 1.0; + + vec4 baseColor = glowColor; + if ( !greyscaleColor ) + baseColor.rgb *= glowMult; + + // Falloff + float falloff = 1.0; + if ( useFalloff || hasRGBFalloff ) { + falloff = smoothstep( falloffParams.x, falloffParams.y, abs(dot(normal, V)) ); + falloff = mix( max(falloffParams.z, 0.0), min(falloffParams.w, 1.0), falloff ); + + if ( useFalloff ) + baseMap.a *= falloff; + + if ( hasRGBFalloff ) + baseMap.rgb *= falloff; + } + + float alphaMult = baseColor.a * baseColor.a; + + vec4 color; + color.rgb = baseMap.rgb * C.rgb * baseColor.rgb; + color.a = alphaMult * C.a * baseMap.a; + + if ( greyscaleColor ) { + vec4 luG = colorLookup( texture2D( BaseMap, offset ).g, baseColor.r * C.r * falloff ); + + color.rgb = luG.rgb; + } + + if ( greyscaleAlpha ) { + vec4 luA = colorLookup( texture2D( BaseMap, offset ).a, color.a ); + + color.a = luA.a; + } + + vec3 diffuse = A.rgb + (D.rgb * NdotL); + color.rgb = mix( color.rgb, color.rgb * D.rgb, lightingInfluence ); + + // Specular + float g = 1.0; + float s = 1.0; + //if ( hasEnvMask ) { + // g = specMap.r; + // s = specMap.g; + //} + + // Environment + vec4 cube = textureCube( CubeMap, reflectedWS ); + if ( hasCubeMap ) { + cube.rgb *= envReflection * s; + cube.rgb = mix( cube.rgb, cube.rgb * D.rgb, lightingInfluence ); + + color.rgb += cube.rgb * falloff * fresnelSchlickRoughness(NdotV, f0, 1.0 - lightingMap.r); + } + + gl_FragColor.rgb = color.rgb; + gl_FragColor.a = color.a; +} diff --git a/res/shaders/f76_effectshader.prog b/res/shaders/f76_effectshader.prog new file mode 100644 index 000000000..259938c08 --- /dev/null +++ b/res/shaders/f76_effectshader.prog @@ -0,0 +1,15 @@ +# effect shader + +checkgroup begin and + # Fallout 76 + check HEADER/BS Header/BS Version == 155 + check BSEffectShaderProperty +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents +texcoords 3 indices +texcoords 4 weights + +shaders f76_default.vert f76_effectshader.frag diff --git a/res/shaders/lighting.dds b/res/shaders/lighting.dds new file mode 100644 index 0000000000000000000000000000000000000000..63dec7666f0f67110492b21be4d7072a2fe84e3c GIT binary patch literal 312 zcmZ>930A0KU|?Vu;9?K}(jeRb#Ed}93dE!U1)yRUAa;oeF~p{iL%;z;_`&}N|3QL7 GjRpYMXj$$6 literal 0 HcmV?d00001 diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index f9cb6d505..44bb71455 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -44,14 +44,13 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) if ( iBlock != index && iSkin != index && iSkinData != index && !extraData ) return; + nifVersion = nif->getUserVersion2(); // Update shaders from this mesh's shader property updateShaderProperties( nif ); if ( extraData ) return; - nifVersion = nif->getUserVersion2(); - isLOD = nif->isNiBlock( iBlock, "BSMeshLODTriShape" ); if ( isLOD ) emit nif->lodSliderChanged( true ); @@ -64,7 +63,7 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) bool isDataOnSkin = false; bool isSkinned = vertexFlags & VertexFlags::VF_SKINNED; - if ( nifVersion == 130 ) { + if ( nifVersion >= 130 ) { skinInstName = "BSSkin::Instance"; skinDataName = "BSSkin::BoneData"; } else { @@ -333,7 +332,7 @@ void BSShape::transformShapes() } transColors = colors; - if ( bslsp ) { + if ( nifVersion < 130 && bslsp ) { if ( !(bslsp->getFlags1() & ShaderFlags::SLSF1_Vertex_Alpha) ) { for ( int c = 0; c < colors.count(); c++ ) transColors[c] = Color4( colors[c].red(), colors[c].green(), colors[c].blue(), 1.0f ); @@ -395,13 +394,13 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) bool doVCs = (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); // Always do vertex colors for FO4 if colors present - if ( nifVersion == 130 && hasVertexColors && colors.count() ) + 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) ) { + } else if ( nifVersion < 130 && !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 ) ); @@ -411,9 +410,17 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) } - if ( !Node::SELECTING ) + if ( !Node::SELECTING ) { + if ( nifVersion == 155 ) + glEnable( GL_FRAMEBUFFER_SRGB ); + else + glDisable( GL_FRAMEBUFFER_SRGB ); shader = scene->renderer->setupProgram( this, shader ); + } else if ( nifVersion == 155 ) { + glDisable( GL_FRAMEBUFFER_SRGB ); + } + if ( isDoubleSided ) { glCullFace( GL_FRONT ); glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.constData() ); diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 32d0490cd..362b99d79 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -118,6 +118,12 @@ void Shape::update( const NifModel * nif, const QModelIndex & index ) { updateShaderProperties( nif ); } + + if ( bssp && nifVersion == 155 ) { + depthTest = bssp->getDepthTest(); + depthWrite = bssp->getDepthWrite(); + isDoubleSided = bssp->getIsDoubleSided(); + } } void Shape::updateShaderProperties( const NifModel * nif ) diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index a4ef0aac2..93ce3109d 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -231,7 +231,7 @@ void AlphaProperty::update( const NifModel * nif, const QModelIndex & block ) alphaSort = ( flags & 0x2000 ) == 0; // Temporary Weapon Blood fix for FO4 - if ( nif->getUserVersion2() == 130 ) + if ( nif->getUserVersion2() >= 130 ) alphaTest |= (flags == 20547); } } @@ -861,7 +861,7 @@ void BSShaderLightingProperty::update( const NifModel * nif, const QModelIndex & if ( !iTextureSet.isValid() ) iSourceTexture = iBlock; - iWetMaterial = nif->getIndex( iBlock, "Wet Material" ); + iWetMaterial = nif->getIndex( iBlock, "Root Material" ); } } @@ -959,6 +959,23 @@ bool BSShaderLightingProperty::bindCube( int id, const QString & fname ) return true; } +enum +{ + BGSM1_DIFFUSE = 0, + BGSM1_NORMAL, + BGSM1_SPECULAR, + BGSM1_G2P, + BGSM1_ENV, + BGSM20_GLOW = 4, + BGSM1_GLOW = 5, + BGSM1_ENVMASK = 5, + BGSM20_REFLECT, + BGSM20_LIGHTING, + + BGSM1_MAX = 9, + BGSM20_MAX = 10 +}; + QString BSShaderLightingProperty::fileName( int id ) const { const NifModel * nif; @@ -970,35 +987,50 @@ QString BSShaderLightingProperty::fileName( int id ) const auto m = static_cast(material); if ( m && m->isValid() ) { auto tex = m->textures(); - if ( tex.count() == 9 ) { + if ( tex.count() >= BGSM1_MAX ) { switch ( id ) { case 0: // Diffuse - if ( !tex[0].isEmpty() ) - return tex[0]; + if ( !tex[BGSM1_DIFFUSE].isEmpty() ) + return tex[BGSM1_DIFFUSE]; break; case 1: // Normal - if ( !tex[1].isEmpty() ) - return tex[1]; + if ( !tex[BGSM1_NORMAL].isEmpty() ) + return tex[BGSM1_NORMAL]; break; case 2: // Glow - if ( m->bGlowmap && !tex[5].isEmpty() ) - return tex[5]; + if ( tex.count() == BGSM1_MAX && m->bGlowmap && !tex[BGSM1_GLOW].isEmpty() ) + return tex[BGSM1_GLOW]; + + if ( tex.count() == BGSM20_MAX && m->bGlowmap && !tex[BGSM20_GLOW].isEmpty() ) + return tex[BGSM20_GLOW]; break; case 3: // Greyscale - if ( m->bGrayscaleToPaletteColor && !tex[3].isEmpty() ) - return tex[3]; + if ( m->bGrayscaleToPaletteColor && !tex[BGSM1_G2P].isEmpty() ) + return tex[BGSM1_G2P]; break; case 4: // Cubemap - if ( m->bEnvironmentMapping && !tex[4].isEmpty() ) - return tex[4]; + if ( tex.count() == BGSM1_MAX && m->bEnvironmentMapping && !tex[BGSM1_ENV].isEmpty() ) + return tex[BGSM1_ENV]; break; case 5: // Env Mask - if ( m->bEnvironmentMapping && !tex[5].isEmpty() ) - return tex[5]; + if ( m->bEnvironmentMapping && !tex[BGSM1_ENVMASK].isEmpty() ) + return tex[BGSM1_ENVMASK]; break; case 7: // Specular - if ( m->bSpecularEnabled && !tex[2].isEmpty() ) - return tex[2]; + if ( m->bSpecularEnabled && !tex[BGSM1_SPECULAR].isEmpty() ) + return tex[BGSM1_SPECULAR]; + break; + } + } + if ( tex.count() >= BGSM20_MAX ) { + switch ( id ) { + case 8: + if ( m->bSpecularEnabled && !tex[BGSM20_REFLECT].isEmpty() ) + return tex[BGSM20_REFLECT]; + break; + case 9: + if ( m->bSpecularEnabled && !tex[BGSM20_LIGHTING].isEmpty() ) + return tex[BGSM20_LIGHTING]; break; } } @@ -1029,6 +1061,10 @@ QString BSShaderLightingProperty::fileName( int id ) const return nif->get( iSourceTexture, "Normal Texture" ); case 4: return nif->get( iSourceTexture, "Env Mask Texture" ); + case 6: + return nif->get( iSourceTexture, "Reflectance Texture" ); + case 7: + return nif->get( iSourceTexture, "Lighting Texture" ); } } else if ( m && m->isValid() ) { auto tex = m->textures(); @@ -1070,14 +1106,38 @@ unsigned int BSShaderLightingProperty::getFlags2() const return (unsigned int)flags2; } -void BSShaderLightingProperty::setFlags1( unsigned int val ) +void BSShaderLightingProperty::setFlags1( const NifModel * nif, const QModelIndex & prop ) { - flags1 = ShaderFlags::SF1( val ); + flags1 = ShaderFlags::SF1( nif->get( prop, "Shader Flags 1" ) ); + if ( stream == 155 ) { + auto sf1 = nif->getArray( prop, "SF1" ); + auto sf2 = nif->getArray( prop, "SF2" ); + sf1.append( sf2 ); + + uint64_t flags = 0; + for ( auto sf : sf1 ) { + flags |= ShaderFlags::CRC_TO_FLAG.value( sf, 0 ); + } + + flags1 = ShaderFlags::SF1((uint32_t)flags); + } } -void BSShaderLightingProperty::setFlags2( unsigned int val ) +void BSShaderLightingProperty::setFlags2( const NifModel * nif, const QModelIndex & prop ) { - flags2 = ShaderFlags::SF2( val ); + flags2 = ShaderFlags::SF2( nif->get( prop, "Shader Flags 2" ) ); + if ( stream == 155 ) { + auto sf1 = nif->getArray( prop, "SF1" ); + auto sf2 = nif->getArray( prop, "SF2" ); + sf1.append( sf2 ); + + uint64_t flags = 0; + for ( auto sf : sf1 ) { + flags |= ShaderFlags::CRC_TO_FLAG.value( sf, 0 ); + } + + flags2 = ShaderFlags::SF2( (uint32_t)(flags >> 32) ); + } } UVScale BSShaderLightingProperty::getUvScale() const @@ -1144,16 +1204,23 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI if ( mat() && mat()->isValid() ) m = static_cast(mat()); - auto stream = nif->getUserVersion2(); + stream = nif->getUserVersion2(); auto textures = nif->getArray( getTextureSet(), "Textures" ); - setShaderType( nif->get( prop, "Skyrim Shader Type" ) ); - setFlags1( nif->get( prop, "Shader Flags 1" ) ); - setFlags2( nif->get( prop, "Shader Flags 2" ) ); + setShaderType( nif->get( prop, "Shader Type" ) ); + setFlags1( nif, prop ); + setFlags2( nif, prop ); hasVertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); hasVertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); + if ( stream == 155 ) { + shaderType = ShaderFlags::ShaderType::ST_EnvironmentMap; + hasVertexAlpha = true; + hasVertexColors = true; + } + + if ( !m ) { isDoubleSided = hasSF2( ShaderFlags::SLSF2_Double_Sided ); depthTest = hasSF1( ShaderFlags::SLSF1_ZBuffer_Test ); @@ -1275,7 +1342,8 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI greyscaleColor = m->bGrayscaleToPaletteColor; paletteScale = m->fGrayscaleToPaletteScale; - hasSpecularMap = m->bSpecularEnabled && !m->textureList[2].isEmpty(); + hasSpecularMap = m->bSpecularEnabled && (!m->textureList[2].isEmpty() + || (stream == 155 && !m->textureList[7].isEmpty())); hasGlowMap = m->bGlowmap; hasEmittance = m->bEmitEnabled; hasBacklight = m->bBackLighting; @@ -1287,8 +1355,8 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI depthTest = m->bZBufferTest; depthWrite = m->bZBufferWrite; - hasEnvironmentMap = m->bEnvironmentMapping; - hasCubeMap = m->bEnvironmentMapping && !m->textureList[4].isEmpty(); + hasEnvironmentMap = m->bEnvironmentMapping || m->bPBR; + hasCubeMap = m->bEnvironmentMapping && stream == 130 && !m->textureList[4].isEmpty(); useEnvironmentMask = hasEnvironmentMap && !m->bGlowmap && !m->textureList[5].isEmpty(); environmentReflection = m->fEnvironmentMappingMaskScale; @@ -1475,16 +1543,16 @@ void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelInd if ( mat() && mat()->isValid() ) m = static_cast(mat()); - auto stream = nif->getUserVersion2(); + stream = nif->getUserVersion2(); - setFlags1( nif->get( prop, "Shader Flags 1" ) ); - setFlags2( nif->get( prop, "Shader Flags 2" ) ); + setFlags1( nif, prop ); + setFlags2( nif, prop ); hasVertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); hasVertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); if ( !m ) { - setEmissive( nif->get( prop, "Emissive Color" ), nif->get( prop, "Emissive Multiple" ) ); + setEmissive( nif->get( prop, "Base Color" ), nif->get( prop, "Base Color Scale" ) ); hasSourceTexture = !nif->get( prop, "Source Texture" ).isEmpty(); hasGreyscaleMap = !nif->get( prop, "Greyscale Texture" ).isEmpty(); @@ -1549,6 +1617,8 @@ void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelInd depthWrite = m->bZBufferWrite; isDoubleSided = m->bTwoSided; + lumEmittance = m->fLumEmittance; + setUvScale( m->fUScale, m->fVScale ); setUvOffset( m->fUOffset, m->fVOffset ); diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 752c83f09..55ba519f9 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -540,6 +540,77 @@ namespace ShaderFlags ST_WorldMap4, ST_WorldLODMultitexture }; + + enum BSShaderCRC32 : unsigned int + { + CAST_SHADOWS = 1563274220, + ZBUFFER_TEST = 1740048692, + ZBUFFER_WRITE = 3166356979, + TWO_SIDED = 759557230, + VERTEXCOLORS = 348504749, + PBR = 731263983, + SKINNED = 3744563888, + ENVMAP = 2893749418, + VERTEX_ALPHA = 2333069810, + FACE = 314919375, + GRAYSCALE_TO_PALETTE_COLOR = 442246519, + DECAL = 3849131744, + DYNAMIC_DECAL = 1576614759, + HAIRTINT = 1264105798, + SKIN_TINT = 1483897208, + EMIT_ENABLED = 2262553490, + GLOWMAP = 2399422528, + REFRACTION = 1957349758, + REFRACTION_FALLOFF = 902349195, + NOFADE = 2994043788, + INVERTED_FADE_PATTERN = 3030867718, + RGB_FALLOFF = 3448946507, + EXTERNAL_EMITTANCE = 2150459555, + MODELSPACENORMALS = 2548465567, + TRANSFORM_CHANGED = 3196772338, + EFFECT_LIGHTING = 3473438218, + FALLOFF = 3980660124, + SOFT_EFFECT = 3503164976, + GRAYSCALE_TO_PALETTE_ALPHA = 2901038324, + WEAPON_BLOOD = 2078326675 + }; + + static QMap CRC_TO_FLAG = { + // SF1 + {CAST_SHADOWS, SLSF1_Cast_Shadows}, + {ZBUFFER_TEST, SLSF1_ZBuffer_Test}, + {SKINNED, SLSF1_Skinned}, + {ENVMAP, SLSF1_Environment_Mapping}, + {VERTEX_ALPHA, SLSF1_Vertex_Alpha}, + {FACE, SLSF1_Facegen_Detail_Map}, + {GRAYSCALE_TO_PALETTE_COLOR, SLSF1_Greyscale_To_PaletteColor}, + {GRAYSCALE_TO_PALETTE_ALPHA, SLSF1_Greyscale_To_PaletteAlpha}, + {DECAL, SLSF1_Decal}, + {DYNAMIC_DECAL, SLSF1_Dynamic_Decal}, + {EMIT_ENABLED, SLSF1_Own_Emit}, + {REFRACTION, SLSF1_Refraction}, + {SKIN_TINT, SLSF1_FaceGen_RGB_Tint}, + {RGB_FALLOFF, SLSF1_Recieve_Shadows}, + {EXTERNAL_EMITTANCE, SLSF1_External_Emittance}, + {MODELSPACENORMALS, SLSF1_Model_Space_Normals}, + {FALLOFF, SLSF1_Use_Falloff}, + {SOFT_EFFECT, SLSF1_Soft_Effect}, + // SF2 + {ZBUFFER_WRITE, (uint64_t)SLSF2_ZBuffer_Write << 32}, + {GLOWMAP, (uint64_t)SLSF2_Glow_Map << 32}, + {TWO_SIDED, (uint64_t)SLSF2_Double_Sided << 32}, + {VERTEXCOLORS, (uint64_t)SLSF2_Vertex_Colors << 32}, + {NOFADE, (uint64_t)SLSF2_No_Fade << 32}, + {WEAPON_BLOOD, (uint64_t)SLSF2_Weapon_Blood << 32}, + {TRANSFORM_CHANGED, (uint64_t)SLSF2_Assume_Shadowmask << 32}, + {EFFECT_LIGHTING, (uint64_t)SLSF2_Effect_Lighting << 32}, + + // TODO + {PBR, 0}, + {REFRACTION_FALLOFF, 0}, + {INVERTED_FADE_PATTERN, 0}, + {HAIRTINT, 0}, + }; } enum TexClampMode : unsigned int @@ -597,8 +668,8 @@ class BSShaderLightingProperty : public Property unsigned int getFlags1() const; unsigned int getFlags2() const; - void setFlags1( unsigned int ); - void setFlags2( unsigned int ); + void setFlags1( const NifModel * nif, const QModelIndex & prop ); + void setFlags2( const NifModel * nif, const QModelIndex & prop ); UVScale getUvScale() const; UVOffset getUvOffset() const; @@ -640,6 +711,8 @@ class BSShaderLightingProperty : public Property bool depthWrite = false; bool isDoubleSided = false; bool isTranslucent = false; + + quint32 stream = 83; }; REGISTER_PROPERTY( BSShaderLightingProperty, ShaderLighting ) @@ -739,6 +812,7 @@ class BSLightingShaderProperty final : public BSShaderLightingProperty Color3 emissiveColor; Color3 specularColor; Color3 tintColor; + Color3 subsurfaceColor; float alpha = 1.0; @@ -816,6 +890,8 @@ class BSEffectShaderProperty final : public BSShaderLightingProperty Falloff falloff; + float lumEmittance = 0.0; + protected: void setController( const NifModel * nif, const QModelIndex & controller ) override final; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 1c4fd4b63..cf9d3fd2c 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -560,6 +560,8 @@ bool Renderer::Program::uniSampler( BSShaderLightingProperty * bsprop, UniformTy GLint uniSamp = uniformLocations[var]; if ( uniSamp >= 0 ) { + // TODO: On stream 155 bsprop->fileName can reference incorrect strings because + // the BSSTS is not filled out nor linked from the BSSP QString fname = (forced.isEmpty()) ? bsprop->fileName( textureSlot ) : forced; if ( fname.isEmpty() ) fname = alternate; @@ -595,9 +597,11 @@ bool Renderer::Program::uniSamplerBlank( UniformType var, int & texunit ) static QString white = "shaders/white.dds"; static QString black = "shaders/black.dds"; +static QString lighting = "shaders/lighting.dds"; static QString gray = "shaders/gray.dds"; static QString magenta = "shaders/magenta.dds"; static QString default_n = "shaders/default_n.dds"; +static QString default_ns = "shaders/default_ns.dds"; static QString cube = "shaders/cubemap.dds"; bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & props, @@ -622,6 +626,9 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & else if ( mesh->bsesp && mesh->bsesp->mat() ) mat = mesh->bsesp->mat(); + QString default_n = ::default_n; + if ( mesh->nifVersion == 155 ) + default_n = ::default_ns; // texturing @@ -759,7 +766,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // Glow params - if ( (opts & Scene::DoGlow) && (opts & Scene::DoLighting) && mesh->bslsp->hasEmittance ) + if ( (opts & Scene::DoGlow) && (opts & Scene::DoLighting) && (mesh->bslsp->hasEmittance || mesh->nifVersion == 155) ) prog->uni1f( GLOW_MULT, mesh->bslsp->getEmissiveMult() ); else prog->uni1f( GLOW_MULT, 0 ); @@ -782,12 +789,14 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & prog->uni1i( HAS_MAP_SPEC, mesh->bslsp->hasSpecularMap ); - if ( mesh->bslsp->hasSpecularMap && (mesh->nifVersion == 130 || !mesh->bslsp->hasBacklight) ) - prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, white, clamp ); - else - prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, black, clamp ); + if ( mesh->nifVersion <= 130 ) { + if ( mesh->nifVersion == 130 || (mesh->bslsp->hasSpecularMap && !mesh->bslsp->hasBacklight) ) + prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, white, clamp ); + else + prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, black, clamp ); + } - if ( mesh->nifVersion == 130 ) { + if ( mesh->nifVersion >= 130 ) { prog->uni1i( DOUBLE_SIDE, mesh->bslsp->getIsDoubleSided() ); prog->uni1f( G2P_SCALE, mesh->bslsp->paletteScale ); prog->uni1f( SS_ROLLOFF, mesh->bslsp->getLightingEffect1() ); @@ -837,6 +846,11 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // Always bind mask regardless of shader settings prog->uniSampler( bsprop, SAMP_ENV_MASK, 5, texunit, white, clamp ); + if ( mesh->nifVersion == 155 ) { + prog->uniSampler( bsprop, SAMP_REFLECTIVITY, 8, texunit, black, clamp ); + prog->uniSampler( bsprop, SAMP_LIGHTING, 9, texunit, lighting, clamp ); + } + // Parallax prog->uni1i( HAS_MAP_HEIGHT, mesh->bslsp->hasHeightMap ); prog->uniSampler( bsprop, SAMP_HEIGHT, 3, texunit, gray, clamp ); @@ -889,7 +903,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // BSEffectShader textures prog->uniSampler( bsprop, SAMP_GRAYSCALE, 1, texunit, "", TexClampMode::MIRRORED_S_MIRRORED_T ); - if ( mesh->nifVersion == 130 ) { + if ( mesh->nifVersion >= 130 ) { prog->uni1f( LIGHT_INF, mesh->bsesp->getLightingInfluence() ); @@ -919,6 +933,12 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & fn->glUniform1i( uniCubeMap, texunit++ ); } prog->uniSampler( bsprop, SAMP_SPECULAR, 4, texunit, white, clamp ); + if ( mesh->nifVersion == 155 ) { + prog->uniSampler( bsprop, SAMP_REFLECTIVITY, 6, texunit, black, clamp ); + prog->uniSampler( bsprop, SAMP_LIGHTING, 7, texunit, lighting, clamp ); + } + + prog->uni1f( LUM_EMIT, mesh->bsesp->lumEmittance ); } } diff --git a/src/gl/renderer.h b/src/gl/renderer.h index c3d522f07..90e1b52dd 100644 --- a/src/gl/renderer.h +++ b/src/gl/renderer.h @@ -93,6 +93,8 @@ class Renderer : public QObject SAMP_BASE = 0, SAMP_NORMAL, SAMP_SPECULAR, + SAMP_REFLECTIVITY, + SAMP_LIGHTING, SAMP_CUBE, SAMP_ENV_MASK, SAMP_GLOW, @@ -151,8 +153,12 @@ class Renderer : public QObject USE_FALLOFF, UV_OFFSET, UV_SCALE, + SKINNED, GPU_SKINNED, GPU_BONES, + WIREFRAME, + SOLID_COLOR, + LUM_EMIT, NUM_UNIFORM_TYPES } UniformType; @@ -270,6 +276,8 @@ public slots: "BaseMap", "NormalMap", "SpecularMap", + "ReflMap", + "LightingMap", "CubeMap", "EnvironmentMap", "GlowMap", @@ -327,8 +335,12 @@ public slots: "useFalloff", "uvOffset", "uvScale", + "isSkinned", "isGPUSkinned", - "boneTransforms" + "boneTransforms", + "isWireframe", + "solidColor", + "fLumEmittance" } }; int uniformLocations[NUM_UNIFORM_TYPES]; diff --git a/src/glview.cpp b/src/glview.cpp index d44d8c0d1..bfb278871 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -166,6 +166,8 @@ GLView::GLView( const QGLFormat & format, QWidget * p, const QGLWidget * shareWi // Make the context current on this window makeCurrent(); + if ( !isValid() ) + return; // Create an OpenGL context glContext = context()->contextHandle(); @@ -213,11 +215,15 @@ GLView::GLView( const QGLFormat & format, QWidget * p, const QGLWidget * shareWi 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::update3D, this, static_cast(&GLView::updateSettings)); + connect(NifSkope::getOptions(), &SettingsDialog::update3D, [this]() { + // Calling update() here in a lambda can crash.. + //updateSettings(); + qglClearColor(cfg.background); + //update(); + }); + connect(NifSkope::getOptions(), &SettingsDialog::update3D, this, static_cast(&GLView::update)); } GLView::~GLView() @@ -320,6 +326,8 @@ void GLView::initializeGL() void GLView::updateShaders() { makeCurrent(); + if ( !isValid() ) + return; scene->updateShaders(); update(); } @@ -381,6 +389,8 @@ void GLView::glProjection( int x, int y ) void GLView::paintEvent( QPaintEvent * event ) { makeCurrent(); + if ( !isValid() ) + return; QPainter painter; painter.begin( this ); @@ -695,6 +705,8 @@ void GLView::resizeGL( int width, int height ) resize( width, height ); makeCurrent(); + if ( !isValid() ) + return; aspect = (GLdouble)width / (GLdouble)height; glViewport( 0, 0, width, height ); qglClearColor( cfg.background ); @@ -855,6 +867,8 @@ QModelIndex GLView::indexAt( const QPoint & pos, int cycle ) return QModelIndex(); makeCurrent(); + if ( !isValid() ) + return {}; glPushAttrib( GL_ALL_ATTRIB_BITS ); glMatrixMode( GL_PROJECTION ); diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 6347f1ea1..8e75832b5 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -184,7 +184,7 @@ QStringList rootStringList = "Shape Name", // NiPhysXShapeDesc "Actor Name", // NiPhysXActorDesc "Joint Name", // NiPhysXJointDesc - "Wet Material", // BSLightingShaderProperty FO4+ + "Root Material", // BSLightingShaderProperty FO4+ "Behaviour Graph File", // BSBehaviorGraphExtraData }; diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index 5ac6b6896..ea1739e28 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -724,7 +724,7 @@ class spUpdateAllBounds final : public Spell if ( !nif || idx.isValid() ) return false; - if ( nif->getUserVersion2() == 130 ) + if ( nif->getUserVersion2() >= 130 ) return true; return false; diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index b87adf64f..5a3735ca4 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -433,10 +433,10 @@ class spFixInvalidNames final : public Spell shapeNames << nameString; } - // Fix "Wet Material" field - if ( isProp && nif->getIndex( iBlock, "Wet Material" ).isValid() ) { - auto wetIdx = nif->get( iBlock, "Wet Material" ); - auto wetString = nif->get( iBlock, "Wet Material" ); + // Fix "Root Material" field + if ( isProp && nif->getIndex( iBlock, "Root Material" ).isValid() ) { + auto wetIdx = nif->get( iBlock, "Root Material" ); + auto wetString = nif->get( iBlock, "Root Material" ); int newWetIdx = -1; @@ -446,8 +446,8 @@ class spFixInvalidNames final : public Spell } if ( newWetIdx > -1 ) { - nif->set( iBlock, "Wet Material", newWetIdx ); - modifiedBlocks.insert( nif->getIndex( iBlock, "Wet Material" ), "Wet Material" ); + nif->set( iBlock, "Root Material", newWetIdx ); + modifiedBlocks.insert( nif->getIndex( iBlock, "Root Material" ), "Root Material" ); } } From 765029b562d57df4b61a6c44063e2a683ae69e58 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 23 Apr 2019 01:52:40 -0400 Subject: [PATCH 017/118] [XML] nifxml 1.0 sync 3 --- src/xml/nifxml.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 9ded69403..f4aadeba2 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -101,7 +101,7 @@ class NifXmlHandler final : public QXmlDefaultHandler tags.insert( "module", tagModule ); tags.insert( "compound", tagCompound ); tags.insert( "niobject", tagBlock ); - tags.insert( "add", tagAdd ); + tags.insert( "field", tagAdd ); tags.insert( "default", tagAddDefault ); tags.insert( "basic", tagBasic ); tags.insert( "enum", tagEnum ); @@ -132,6 +132,8 @@ class NifXmlHandler final : public QXmlDefaultHandler QString optId; //! Current enumeration value QString optVal; + //! Current enumeration bit + QString optBit; //! Current enumeration text QString optTxt; @@ -318,7 +320,7 @@ class NifXmlHandler final : public QXmlDefaultHandler case tagCompound: if ( x != tagAdd ) - err( tr( "only add tags allowed in compound type declaration" ) ); + err( tr( "only field tags allowed in compound type declaration" ) ); case tagBlock: push( x ); @@ -329,11 +331,11 @@ class NifXmlHandler final : public QXmlDefaultHandler QString type = list.value( "type" ); QString tmpl = list.value( "template" ); QString arg = get( "arg" ); - QString arr1 = get( "arr1" ); - QString arr2 = get( "arr2" ); + QString arr1 = get( "length" ); + QString arr2 = get( "width" ); QString cond = get( "cond" ); - QString ver1 = list.value( "ver1" ); - QString ver2 = list.value( "ver2" ); + QString ver1 = list.value( "since" ); + QString ver2 = list.value( "until" ); QString abs = list.value( "abstract" ); QString bin = list.value( "binary" ); QString vercond = get( "vercond" ); @@ -431,11 +433,11 @@ class NifXmlHandler final : public QXmlDefaultHandler data.setIsConditionless( true ); if ( data.name().isEmpty() || data.type().isEmpty() ) - err( tr( "add needs at least name and type attributes" ) ); + err( tr( "field needs at least name and type attributes" ) ); } break; default: - err( tr( "only add tags allowed in block declaration" ) ); + err( tr( "only field tags allowed in block declaration" ) ); } break; @@ -456,6 +458,10 @@ class NifXmlHandler final : public QXmlDefaultHandler case tagOption: optId = list.value( "name" ); optVal = list.value( "value" ); + optBit = list.value( "bit" ); + if ( !optBit.isEmpty() ) + optVal = optBit; + optTxt = QString(); if ( optId.isEmpty() || optVal.isEmpty() ) From 9bbbe8337295b62e3e2dc48a0e62d00ccd722504 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 09:29:30 -0400 Subject: [PATCH 018/118] Initial Game Manager commit Resource collisions between Fallout 4 and Fallout 76 were severe due to a vastly different asset pipeline while maintaining the same filenames. Technically resource collisions happened with Skyrim and Skyrim SE too, but the vanilla files were largely identical. The game manager associates texture and material lookups to a particular game, based on the NIF version. This does mean that Fallout 3 and Fallout NV are considered the same meta-game and lookups for either game's files will be done in both games' paths. A "game mode override" is planned to address this. --- NifSkope.pro | 8 +- lib/fsengine/bsa.cpp | 5 + lib/fsengine/bsa.h | 2 + lib/fsengine/fsengine.cpp | 2 +- lib/fsengine/fsmanager.cpp | 161 ------- lib/fsengine/fsmanager.h | 86 ---- src/gamemanager.cpp | 409 +++++++++++++++++ src/gamemanager.h | 258 +++++++++++ src/gl/glproperty.cpp | 4 +- src/gl/glscene.cpp | 8 +- src/gl/glscene.h | 4 + src/gl/gltex.cpp | 28 +- src/gl/gltex.h | 10 +- src/io/material.cpp | 16 +- src/io/material.h | 9 +- src/main.cpp | 5 + src/nifskope.cpp | 37 -- src/ui/settingspane.cpp | 289 +++++++----- src/ui/settingspane.h | 9 +- src/ui/settingsresources.ui | 891 ++++++++++++++++++++++++++++++++++++ 20 files changed, 1809 insertions(+), 432 deletions(-) delete mode 100644 lib/fsengine/fsmanager.cpp delete mode 100644 lib/fsengine/fsmanager.h create mode 100644 src/gamemanager.cpp create mode 100644 src/gamemanager.h diff --git a/NifSkope.pro b/NifSkope.pro index ecbf0fe49..c97ade11d 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -196,6 +196,7 @@ HEADERS += \ src/ui/settingspane.h \ src/xml/nifexpr.h \ src/xml/xmlconfig.h \ + src/gamemanager.h \ src/glview.h \ src/message.h \ src/nifskope.h \ @@ -279,6 +280,7 @@ SOURCES += \ src/xml/kfmxml.cpp \ src/xml/nifexpr.cpp \ src/xml/nifxml.cpp \ + src/gamemanager.cpp \ src/glview.cpp \ src/main.cpp \ src/message.cpp \ @@ -310,12 +312,10 @@ fsengine { INCLUDEPATH += lib/fsengine HEADERS += \ lib/fsengine/bsa.h \ - lib/fsengine/fsengine.h \ - lib/fsengine/fsmanager.h + lib/fsengine/fsengine.h SOURCES += \ lib/fsengine/bsa.cpp \ - lib/fsengine/fsengine.cpp \ - lib/fsengine/fsmanager.cpp + lib/fsengine/fsengine.cpp } nvtristrip { diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index a8d0fd78b..6ef7a528a 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -747,6 +747,11 @@ const BSA::BSAFolder * BSA::getFolder( QString fn ) const return folders.value( fn ); } +const BSA::BSAFolder * BSA::getRootFolder() const +{ + return root; +} + // see bsa.h const BSA::BSAFile * BSA::getFile( QString fn ) const { diff --git a/lib/fsengine/bsa.h b/lib/fsengine/bsa.h index 16b4d8312..04012da7d 100644 --- a/lib/fsengine/bsa.h +++ b/lib/fsengine/bsa.h @@ -327,6 +327,8 @@ class BSA final : public FSArchiveFile //! Gets the specified folder, or the root folder if not found const BSAFolder * getFolder( QString fn ) const; + //! Gets the root folder + const BSAFolder * getRootFolder() const; //! Gets the specified file, or null if not found const BSAFile * getFile( QString fn ) const; diff --git a/lib/fsengine/fsengine.cpp b/lib/fsengine/fsengine.cpp index e8d7514e7..245c9ae51 100644 --- a/lib/fsengine/fsengine.cpp +++ b/lib/fsengine/fsengine.cpp @@ -48,7 +48,7 @@ std::shared_ptr FSArchiveHandler::openArchive( const QString & if ( BSA::canOpen( fn ) ) { BSA * bsa = new BSA( fn ); - if ( bsa->open() ) { + if ( bsa && bsa->open() ) { //qDebug() << "BSA Open: " << fn; return std::shared_ptr( new FSArchiveHandler( bsa ) ); } diff --git a/lib/fsengine/fsmanager.cpp b/lib/fsengine/fsmanager.cpp deleted file mode 100644 index e6531be5a..000000000 --- a/lib/fsengine/fsmanager.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - - -#include "fsmanager.h" -#include "fsengine.h" -#include "bsa.h" - -#include -#include -#include -#include -#include -#include -#include - - -//! Global BSA file manager -static FSManager * theFSManager = nullptr; -// see fsmanager.h -FSManager* FSManager::get() -{ - if (!theFSManager) - theFSManager = new FSManager(); - return theFSManager; -} - -void FSManager::del() -{ - if ( theFSManager ) { - delete theFSManager; - theFSManager = nullptr; - } -} - -// see fsmanager.h -QList FSManager::archiveList() -{ - QList archives; - for ( std::shared_ptr an : get()->archives.values() ) { - archives.append( an->getArchive() ); - } - return archives; -} - -// see fsmanager.h -FSManager::FSManager( QObject * parent ) - : QObject( parent ), automatic( false ) -{ - initialize(); -} - -// see fsmanager.h -FSManager::~FSManager() -{ - archives.clear(); -} - -void FSManager::initialize() -{ - QSettings cfg; - QStringList list = cfg.value( "Settings/Resources/Archives", QStringList() ).toStringList(); - - for ( const QString an : list ) { - if ( auto a = FSArchiveHandler::openArchive( an ) ) - archives.insert( an, a ); - } -} - -// see fsmanager.h -QStringList FSManager::regPathBSAList( QString regKey, QString dataDir ) -{ - QStringList list; -#ifdef Q_OS_WIN32 - QSettings reg( regKey, QSettings::Registry32Format ); - QString dataPath = reg.value( "Installed Path" ).toString(); - if ( ! dataPath.isEmpty() ) - { - if ( ! dataPath.endsWith( '/' ) && ! dataPath.endsWith( '\\' ) ) - dataPath += "/"; - dataPath += dataDir; - QDir fs( dataPath ); - for ( const QString& fn : fs.entryList( { "*.bsa", "*.ba2" }, QDir::Files ) ) - { - list << QDir::fromNativeSeparators(dataPath + QDir::separator() + fn); - } - } -#endif - return list; -} - -QStringList FSManager::autodetectArchives( const QString & folder ) -{ - QStringList list; - -#ifdef Q_OS_WIN32 - list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Morrowind", "Data Files" ); - list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Oblivion", "Data" ); - list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Fallout3", "Data" ); - list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\FalloutNV", "Data" ); - list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Skyrim", "Data" ); - list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Fallout4", "Data" ); - list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Skyrim Special Edition", "Data" ); -#endif - - return filterArchives( list, folder ); -} - -QStringList FSManager::filterArchives( const QStringList & list, const QString & folder ) -{ - QStringList listCopy; - if ( !folder.isEmpty() ) { - // Looking for a specific folder here - // Remove the BSAs that do not contain this folder - for ( auto f : list ) { - auto handler = FSArchiveHandler::openArchive( f ); - if ( handler ) { - auto bsa = handler->getArchive(); - if ( bsa ) { - auto rootFolder = bsa->getFolder( "" ); - if ( rootFolder->children.contains( folder.toLower() ) ) { - listCopy.append( f ); - } - } - } - } - } else { - listCopy = list; - } - - return listCopy; -} diff --git a/lib/fsengine/fsmanager.h b/lib/fsengine/fsmanager.h deleted file mode 100644 index 20e460e35..000000000 --- a/lib/fsengine/fsmanager.h +++ /dev/null @@ -1,86 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2015, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - - -#ifndef FSMANAGER_H -#define FSMANAGER_H - - -#include -#include -#include - -#include - -class FSArchiveHandler; -class FSArchiveFile; - -//! The file system manager class. -class FSManager : public QObject -{ - Q_OBJECT -public: - //! Gets the global file system manager - static FSManager * get(); - - //! Deletes the manager - static void del(); - - //! Gets the list of globally registered BSA files - static QList archiveList(); - - //! Filters a list of BSAs from a provided list - static QStringList filterArchives( const QStringList & list, const QString & folder = "" ); - -protected: - //! Constructor - FSManager( QObject * parent = nullptr ); - //! Destructor - ~FSManager(); - -protected: - QMap > archives; - bool automatic; - - //! Builds a list of global BSAs on Windows platforms - static QStringList autodetectArchives( const QString & folder = "" ); - //! Helper function to build a list of BSAs - static QStringList regPathBSAList( QString regKey, QString dataDir ); - - void initialize(); - - friend class NifSkope; - friend class SettingsResources; -}; - -#endif - diff --git a/src/gamemanager.cpp b/src/gamemanager.cpp new file mode 100644 index 000000000..0bf55a896 --- /dev/null +++ b/src/gamemanager.cpp @@ -0,0 +1,409 @@ +#include "gamemanager.h" + +#include "bsa.h" +#include "message.h" + +#include +#include +#include +#include + +namespace Game +{ + +QString registry_game_path( const QString& key ) +{ +#ifdef Q_OS_WIN32 + QString data_path; + QSettings cfg(key, QSettings::Registry32Format); + data_path = cfg.value("Installed Path").toString(); // Steam + if ( data_path.isEmpty() ) + data_path = cfg.value("Path").toString(); // Microsoft Uninstall + // Remove encasing quotes + data_path.remove('"'); + if ( data_path.isEmpty() ) + return {}; + + QDir data_path_dir(data_path); + if ( data_path_dir.exists() ) + return QDir::cleanPath(data_path); + +#endif + return {}; +} + +QStringList archives_list( const QString& path, const QString& data_dir, const QString& folder = {} ) +{ + if ( path.isEmpty() ) + return {}; + + QStringList list; + QDir path_dir(path); + if ( path_dir.exists(data_dir) ) + path_dir.cd(data_dir); + for ( const auto& finfo : path_dir.entryInfoList({"*.bsa", "*.ba2"}, QDir::Files) ) + list << finfo.absoluteFilePath(); + + if ( folder.isEmpty() ) + return list; + + // Remove the archives that do not contain this folder + QStringList list_filtered; + for ( const auto& f : list ) + if ( GameManager::archive_contains_folder(f, folder) ) + list_filtered.append( f ); + return list_filtered; +} + +QString StringForMode( GameMode game ) +{ + if ( game >= NUM_GAMES ) + return {}; + + return STRING.value(game, ""); +} + +GameMode ModeForString( QString game ) +{ + return STRING.key(game, OTHER); +} + +GameManager::GameManager() +{ + QSettings settings; + int manager_version = settings.value("Game Manager Version", 0).toInt(); + if ( manager_version == 0 ) { + manager_version++; + QProgressDialog* dlg = new QProgressDialog( "Initializing the Game Manager", {}, 0, NUM_GAMES ); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->show(); + // Initial game manager settings + initialize(manager_version, dlg); + dlg->close(); + } + + load(); + reset_archive_handles(); +} + +GameMode GameManager::GetGame( uint32_t version, uint32_t user, uint32_t bsver ) +{ + switch ( bsver ) { + case 0: + break; + case BSSTREAM_1: + case BSSTREAM_3: + case BSSTREAM_4: + case BSSTREAM_5: + case BSSTREAM_6: + case BSSTREAM_7: + case BSSTREAM_8: + case BSSTREAM_9: + return OBLIVION; + case BSSTREAM_11: + if ( user == 10 ) // TODO: Enumeration + return OBLIVION; + else if ( user == 11 ) + return FALLOUT_3NV; + return OTHER; + case BSSTREAM_14: + case BSSTREAM_16: + case BSSTREAM_21: + case BSSTREAM_24: + case BSSTREAM_25: + case BSSTREAM_26: + case BSSTREAM_27: + case BSSTREAM_28: + case BSSTREAM_30: + case BSSTREAM_31: + case BSSTREAM_32: + case BSSTREAM_33: + case BSSTREAM_34: + return FALLOUT_3NV; + case BSSTREAM_83: + return SKYRIM; + case BSSTREAM_100: + return SKYRIM_SE; + case BSSTREAM_130: + return FALLOUT_4; + case BSSTREAM_155: + return FALLOUT_76; + default: + break; + }; + + return OTHER; +} + +static GameManager * g_game_manager = nullptr; + +GameManager * GameManager::get() +{ + if ( !g_game_manager ) + g_game_manager = new GameManager(); + return g_game_manager; +} + +void GameManager::del() +{ + if ( g_game_manager ) { + delete g_game_manager; + g_game_manager = nullptr; + } +} + +void GameManager::initialize( int manager_version, QProgressDialog* dlg ) +{ + QSettings settings; + QMap paths; + QMap folders; + QMap archives; + QMap status; + for ( const auto & key : KEY.toStdMap() ) { + // Progress Bar + if ( dlg ) { + dlg->setValue(key.first); + QCoreApplication::processEvents(); + } + if ( key.second.isEmpty() ) + continue; + auto game = key.first; + auto game_str = StringForMode(game); + auto path = registry_game_path(key.second); + paths.insert(game_str, path); + folders.insert(game_str, FOLDERS.value(game, {})); + + // Filter and insert archives + QStringList filtered; + if ( game == FALLOUT_4 || game == FALLOUT_76 ) + filtered.append(archives_list(path, DATA.value(game, {}), "materials")); + filtered.append(archives_list(path, DATA.value(game, {}), "textures")); + filtered.removeDuplicates(); + + archives.insert(game_str, filtered); + + // Game Enabled Status + status.insert(game_str, !path.isEmpty()); + } + + settings.setValue("Game Paths", paths); + settings.setValue("Game Folders", folders); + settings.setValue("Game Archives", archives); + settings.setValue("Game Status", status); + settings.setValue("Game Manager Version", manager_version); +} + +QList GameManager::get_archive_handles( Game::GameMode game ) +{ + if ( get()->get_game_status(game) == false ) + return {}; + + QList archives; + for ( std::shared_ptr an : get()->handles.value(game) ) { + archives.append(an->getArchive()); + } + return archives; +} + +bool GameManager::archive_contains_folder( const QString& archive, const QString& folder ) +{ + bool contains = false; + if ( BSA::canOpen(archive) ) { + BSA * bsa = new BSA(archive); + if ( bsa && bsa->open() ) { + contains = bsa->getRootFolder()->children.contains(folder.toLower()); + } + delete bsa; + } + return contains; +} + +void GameManager::reset_archive_handles() +{ + handles.clear(); + for ( const auto ar : game_archives.toStdMap() ) { + for ( const auto an : ar.second ) { + // Skip loading of archives for disabled games + if ( game_status.value(ar.first, false) == false ) + continue; + if ( auto a = FSArchiveHandler::openArchive(an) ) + handles[ar.first].append(a); + } + } +} + +QString GameManager::get_game_path( const GameMode game ) +{ + return get()->game_paths.value(game, {}); +} + +QString GameManager::get_game_path( const QString& game ) +{ + return get()->game_paths.value(ModeForString(game), {}); +} + +QString GameManager::get_data_path( const GameMode game ) +{ + return get_game_path(game) + "/" + DATA[game]; +} + +QString GameManager::get_data_path( const QString& game ) +{ + return get_data_path(ModeForString(game)); +} + +QStringList GameManager::get_folder_list( const GameMode game ) +{ + if ( game == FALLOUT_3NV ) + return get_folder_list(FALLOUT_NV) + get_folder_list(FALLOUT_3); + if ( get_game_status(game) ) + return get()->game_folders.value(game, {}); + return {}; +} +QStringList GameManager::get_folder_list( const QString& game ) +{ + return get_folder_list(ModeForString(game)); +} + +QStringList GameManager::get_archive_list( const GameMode game ) +{ + if ( game == FALLOUT_3NV ) + return get_archive_list(FALLOUT_NV) + get_archive_list(FALLOUT_3); + if ( get_game_status(game) ) + return get()->game_archives.value(game, {}); + return {}; +} + + +QStringList GameManager::get_archive_list( const QString& game ) +{ + return get_archive_list(ModeForString(game)); +} + +QStringList GameManager::get_archive_file_list( const GameMode game ) +{ + QDir data_dir = QDir(GameManager::get_data_path(game)); + if ( !data_dir.exists() ) + return {}; + + QStringList data_archives = data_dir.entryList({"*.bsa", "*.ba2"}, QDir::Files); + QStringList archive_paths; + for ( const auto& a : data_archives ) { + archive_paths << data_dir.absoluteFilePath(a); + } + + return archive_paths; +} + +QStringList GameManager::get_archive_file_list( const QString& game ) +{ + return get_archive_file_list(ModeForString(game)); +} + +QStringList GameManager::get_filtered_archives_list( const QStringList& list, const QString& folder ) +{ + if ( folder.isEmpty() ) + return list; + + QStringList filtered; + for ( auto f : list ) { + if ( archive_contains_folder(f, folder) ) + filtered.append(f); + } + return filtered; +} + +void GameManager::set_game_status( const GameMode game, bool status ) +{ + get()->game_status[game] = status; +} + +void GameManager::set_game_status( const QString& game, bool status ) +{ + set_game_status(ModeForString(game), status); +} + +bool GameManager::get_game_status( const GameMode game ) +{ + if ( game == FALLOUT_3NV ) + return get_game_status(FALLOUT_3) || get_game_status(FALLOUT_NV); + return get()->game_status.value(game, false); +} + +bool GameManager::get_game_status( const QString& game ) +{ + return get_game_status(ModeForString(game)); +} + +void GameManager::update_game( const QString& game, const QString& path ) +{ + get()->game_paths.insert(ModeForString(game), path); +} + +void GameManager::update_folders( const QString& game, const QStringList& list ) +{ + get()->game_folders.insert(ModeForString(game), list); +} + +void GameManager::update_archives( const QString& game, const QStringList& list ) +{ + get()->game_archives.insert(ModeForString(game), list); +} + +void GameManager::save() +{ + QSettings settings; + + QMap paths; + QMap folders; + QMap archives; + QMap status; + + for ( const auto& p : game_paths.toStdMap() ) + paths.insert(StringForMode(p.first), p.second); + + for ( const auto& f : game_folders.toStdMap() ) + folders.insert(StringForMode(f.first), f.second); + + for ( const auto& a : game_archives.toStdMap() ) + archives.insert(StringForMode(a.first), a.second); + + for ( const auto& s : game_status.toStdMap() ) + status.insert(StringForMode(s.first), s.second); + + settings.setValue("Game Paths", paths); + settings.setValue("Game Folders", folders); + settings.setValue("Game Archives", archives); + settings.setValue("Game Status", status); +} + +void GameManager::load() +{ + QSettings settings; + + game_paths.clear(); + auto paths = settings.value("Game Paths").toMap().toStdMap(); + for ( const auto& p : paths ) { + game_paths[ModeForString(p.first)] = p.second.toString(); + } + + game_folders.clear(); + auto fol = settings.value("Game Folders").toMap().toStdMap(); + for ( const auto& f : fol ) { + game_folders[ModeForString(f.first)] = f.second.toStringList(); + } + + game_archives.clear(); + auto arc = settings.value("Game Archives").toMap().toStdMap(); + for ( const auto& a : arc ) { + game_archives[ModeForString(a.first)] = a.second.toStringList(); + } + + game_status.clear(); + auto stat = settings.value("Game Status").toMap().toStdMap(); + for ( const auto& s : stat ) { + game_status[ModeForString(s.first)] = s.second.toBool(); + } +} + +} // end namespace Game diff --git a/src/gamemanager.h b/src/gamemanager.h new file mode 100644 index 000000000..559629728 --- /dev/null +++ b/src/gamemanager.h @@ -0,0 +1,258 @@ +#ifndef GAMEMANAGER_H +#define GAMEMANAGER_H + +#include +#include + +#include +#include +#include +#include + + +class FSArchiveHandler; +class FSArchiveFile; +class QProgressDialog; + +namespace Game +{ + +enum VersionMasks +{ + USER_MASK = 0xFFFFF, + USER_MASK_BS = 0xFFFF, + BS_MASK = 0xFF +}; + +constexpr uint64_t VersionDef( uint64_t major = 0, uint64_t minor = 0, uint64_t patch = 0, uint64_t inter = 0, + uint64_t user = 0, uint64_t bs_min = 0, uint64_t bs_max = 0 ) +{ + return (bs_min > 0) + ? (major << 24) | (minor << 16) | (patch << 8) | inter | ((user & USER_MASK_BS) << 32) + | ((bs_min & BS_MASK) << 48) | ((((bs_max < bs_min) ? bs_min : bs_max) & BS_MASK) << 56) + : (major << 24) | (minor << 16) | (patch << 8) | inter | ((user & USER_MASK) << 32); +} + +constexpr uint32_t VersionInt( uint64_t version ) +{ + return (uint32_t)version; +} + +enum GameMode +{ + OTHER, + MORROWIND, + OBLIVION, + FALLOUT_3, + FALLOUT_NV, + SKYRIM, + SKYRIM_SE, + FALLOUT_4, + FALLOUT_76, + + NUM_GAMES, + + // Not a physical game, exclude from NUM_GAMES count + FALLOUT_3NV, +}; + +using GameMap = QMap; +using GameEnabledMap = QMap; +using ResourceListMap = QMap; + +using namespace std::string_literals; + +static auto beth = QString("HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\%1"); +static auto msft = QString("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1"); + + +static GameMap STRING = { + {MORROWIND, "Morrowind"}, + {OBLIVION, "Oblivion"}, + {FALLOUT_3, "Fallout 3"}, + {FALLOUT_NV, "Fallout: New Vegas"}, + {SKYRIM, "Skyrim"}, + {SKYRIM_SE, "Skyrim SE"}, + {FALLOUT_4, "Fallout 4"}, + {FALLOUT_76, "Fallout 76"}, + {OTHER, "Other Games"} +}; + +static GameMap KEY = { + {MORROWIND, beth.arg("Morrowind")}, + {OBLIVION, beth.arg("Oblivion")}, + {FALLOUT_3, beth.arg("Fallout3")}, + {FALLOUT_NV, beth.arg("FalloutNV")}, + {SKYRIM, beth.arg("Skyrim")}, + {SKYRIM_SE, beth.arg("Skyrim Special Edition")}, + {FALLOUT_4, beth.arg("Fallout4")}, + {FALLOUT_76, msft.arg("Fallout 76")}, + {OTHER, ""} +}; + +static GameMap DATA = { + {MORROWIND, "Data Files"}, + {OBLIVION, "Data"}, + {FALLOUT_3, "Data"}, + {FALLOUT_NV, "Data"}, + {SKYRIM, "Data"}, + {SKYRIM_SE, "Data"}, + {FALLOUT_4, "Data"}, + {FALLOUT_76, "Data"}, + {OTHER, ""} +}; + +static ResourceListMap FOLDERS = { + {MORROWIND, {"."}}, + {OBLIVION, {"."}}, + {FALLOUT_3, {"."}}, + {FALLOUT_NV, {"."}}, + {SKYRIM, {"."}}, + {SKYRIM_SE, {"."}}, + {FALLOUT_4, {".", "Textures"}}, + {FALLOUT_76, {".", "Textures"}}, + {OTHER, {}} +}; + +enum GameVersion : uint64_t +{ + V3_3_0_13 = VersionDef(3, 3, 0, 13), + V4_0_0_0 = VersionDef(4), + V4_0_0_2 = VersionDef(4, 0, 0, 2), + V4_1_0_12 = VersionDef(4, 1, 0, 12), + V4_2_0_2 = VersionDef(4, 2, 0, 2), + V4_2_1_0 = VersionDef(4, 2, 1, 0), + V4_2_2_0 = VersionDef(4, 2, 2, 0), + V10_0_1_0 = VersionDef(10, 0, 1, 0), + V10_0_1_2 = VersionDef(10, 0, 1, 2, 0, 1, 3), + V10_1_0_0 = VersionDef(10, 1, 0, 0), + V10_1_0_101 = VersionDef(10, 1, 0, 101, 10, 4), + V10_1_0_106 = VersionDef(10, 1, 0, 106, 10, 5), + V10_2_0_0 = VersionDef(10, 2, 0, 0), + V10_2_0_0__1 = VersionDef(10, 2, 0, 0, 1), + V10_2_0_0__10 = VersionDef(10, 2, 0, 0, 10, 6, 9), + V10_2_0_1 = VersionDef(10, 2, 0, 1), + V10_3_0_1 = VersionDef(10, 3, 0, 1), + V10_4_0_1 = VersionDef(10, 4, 0, 1), + V20_0_0_4 = VersionDef(20, 0, 0, 4), + V20_0_0_4__10 = VersionDef(20, 0, 0, 4, 10, 11), + V20_0_0_4__11 = VersionDef(20, 0, 0, 4, 11, 11), + V20_0_0_5_OBL = VersionDef(20, 0, 0, 5, 10, 11), + V20_1_0_3 = VersionDef(20, 1, 0, 3), + V20_2_0_7 = VersionDef(20, 2, 0, 7), + V20_2_0_7__11_1 = VersionDef(20, 2, 0, 7, 11, 14), + V20_2_0_7__11_2 = VersionDef(20, 2, 0, 7, 11, 16), + V20_2_0_7__11_3 = VersionDef(20, 2, 0, 7, 11, 21), + V20_2_0_7__11_4 = VersionDef(20, 2, 0, 7, 11, 24), + V20_2_0_7__11_5 = VersionDef(20, 2, 0, 7, 11, 25), + V20_2_0_7__11_6 = VersionDef(20, 2, 0, 7, 11, 26), + V20_2_0_7__11_7 = VersionDef(20, 2, 0, 7, 11, 27, 28), + V20_2_0_7__11_8 = VersionDef(20, 2, 0, 7, 11, 30, 33), + V20_2_0_7_FO3 = VersionDef(20, 2, 0, 7, 11, 34), + V20_2_0_7_SKY = VersionDef(20, 2, 0, 7, 11, 83), + V20_2_0_7_SSE = VersionDef(20, 2, 0, 7, 11, 100), + V20_2_0_7_FO4 = VersionDef(20, 2, 0, 7, 11, 130), + V20_2_0_7_F76 = VersionDef(20, 2, 0, 7, 11, 155), + V20_2_0_8 = VersionDef(20, 2, 0, 8), + V20_3_0_1 = VersionDef(20, 3, 0, 1), + V20_3_0_2 = VersionDef(20, 3, 0, 2), + V20_3_0_3 = VersionDef(20, 3, 0, 3), + V20_3_0_6 = VersionDef(20, 3, 0, 6), + V20_3_0_9 = VersionDef(20, 3, 0, 9), + V20_3_0_9_DIV2 = VersionDef(20, 3, 0, 9, 0x20000), // TODO: 0x30000? + V20_5_0_0 = VersionDef(20, 5, 0, 0), + V20_6_0_0 = VersionDef(20, 6, 0, 0), + V20_6_5_0_DEM = VersionDef(20, 6, 5, 0, 17), + V30_0_0_2 = VersionDef(30, 0, 0, 2), + V30_1_0_1 = VersionDef(30, 1, 0, 1), + V30_1_0_3 = VersionDef(30, 1, 0, 3), + V30_2_0_3 = VersionDef(30, 2, 0, 3), +}; + +enum BSVersion +{ + BSSTREAM_1 = 1, + BSSTREAM_3 = 3, + BSSTREAM_4 = 4, + BSSTREAM_5 = 5, + BSSTREAM_6 = 6, + BSSTREAM_7 = 7, + BSSTREAM_8 = 8, + BSSTREAM_9 = 9, + BSSTREAM_11 = 11, + BSSTREAM_14 = 14, + BSSTREAM_16 = 16, + BSSTREAM_21 = 21, + BSSTREAM_24 = 24, + BSSTREAM_25 = 25, + BSSTREAM_26 = 26, + BSSTREAM_27 = 27, + BSSTREAM_28 = 28, + BSSTREAM_30 = 30, + BSSTREAM_31 = 31, + BSSTREAM_32 = 32, + BSSTREAM_33 = 33, + BSSTREAM_34 = 34, + BSSTREAM_83 = 83, + BSSTREAM_100 = 100, + BSSTREAM_130 = 130, + BSSTREAM_155 = 155, +}; + +QString StringForMode(GameMode game); +GameMode ModeForString(QString game); + +class GameManager +{ +public: + GameManager(); + + static GameMode GetGame(uint32_t version, uint32_t user, uint32_t bsver); + + static GameManager * get(); + static void del(); + + static QList get_archive_handles(Game::GameMode game); + static bool archive_contains_folder(const QString& archive, const QString& folder); + + static QString get_game_path(const GameMode game); + static QString get_game_path(const QString& game); + static QString get_data_path(const GameMode game); + static QString get_data_path(const QString& game); + static QStringList get_folder_list(const GameMode game); + static QStringList get_folder_list(const QString& game); + static QStringList get_archive_list(const GameMode game); + static QStringList get_archive_list(const QString& game); + + static QStringList get_archive_file_list(const GameMode game); + static QStringList get_archive_file_list(const QString& game); + static QStringList get_filtered_archives_list(const QStringList& list, const QString& folder); + + static void set_game_status(const GameMode game, bool status); + static void set_game_status(const QString& game, bool status); + static bool get_game_status(const GameMode game); + static bool get_game_status(const QString& game); + + static void update_game(const QString& game, const QString& path); + static void update_folders(const QString& game, const QStringList& list); + static void update_archives(const QString& game, const QStringList& list); + + void initialize(int manager_version, QProgressDialog* dlg = nullptr); + void save(); + void load(); + void reset_archive_handles(); + +private: + QMutex mutex; + + GameMap game_paths; + GameEnabledMap game_status; + ResourceListMap game_folders; + ResourceListMap game_archives; + + QMap>> handles; +}; + +} // end namespace Game + +#endif // GAMEMANAGER_H diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 93ce3109d..9bd5249a1 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -1186,7 +1186,7 @@ void BSLightingShaderProperty::update( const NifModel * nif, const QModelIndex & BSShaderLightingProperty::update( nif, property ); if ( name.endsWith( ".bgsm", Qt::CaseInsensitive ) ) - material = new ShaderMaterial( name ); + material = new ShaderMaterial( name, scene->game ); if ( material && !material->isValid() ) material = nullptr; @@ -1526,7 +1526,7 @@ void BSEffectShaderProperty::update( const NifModel * nif, const QModelIndex & p BSShaderLightingProperty::update( nif, property ); if ( name.endsWith( ".bgem", Qt::CaseInsensitive ) ) - material = new EffectMaterial( name ); + material = new EffectMaterial( name, scene->game); if ( material && !material->isValid() ) material = nullptr; diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 265d07a1e..072de1893 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -122,6 +122,8 @@ void Scene::clear( bool flushTextures ) textures->flush(); sceneBoundsValid = timeBoundsValid = false; + + game = Game::OTHER; } void Scene::update( const NifModel * nif, const QModelIndex & index ) @@ -213,6 +215,8 @@ void Scene::make( NifModel * nif, bool flushTextures ) if ( !nif ) return; + game = Game::GameManager::GetGame(nif->getVersionNumber(), nif->getUserVersion(), nif->getUserVersion2()); + update( nif, QModelIndex() ); if ( !animGroups.contains( animGroup ) ) { @@ -467,7 +471,7 @@ int Scene::bindTexture( const QString & fname ) if ( !(options & DoTexturing) || fname.isEmpty() ) return 0; - return textures->bind( fname ); + return textures->bind( fname, game ); } int Scene::bindTexture( const QModelIndex & iSource ) @@ -475,6 +479,6 @@ int Scene::bindTexture( const QModelIndex & iSource ) if ( !(options & DoTexturing) || !iSource.isValid() ) return 0; - return textures->bind( iSource ); + return textures->bind( iSource, game ); } diff --git a/src/gl/glscene.h b/src/gl/glscene.h index 4adab9b3b..0f46f8a71 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -37,6 +37,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glproperty.h" #include "gltools.h" +#include "gamemanager.h" + #include #include #include @@ -90,6 +92,8 @@ class Scene final : public QObject Node * getNode( const NifModel * nif, const QModelIndex & iNode ); Property * getProperty( const NifModel * nif, const QModelIndex & iProperty ); + Game::GameMode game = Game::OTHER; + enum SceneOption { None = 0x0, diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index bb64e9798..372ffeb53 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -38,7 +38,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/nifmodel.h" #include -#include + +#include "gamemanager.h" #include #include @@ -155,12 +156,12 @@ TexCache::~TexCache() //flush(); } -QString TexCache::find( const QString & file, const QString & nifdir ) +QString TexCache::find( const QString & file, const QString & nifdir, Game::GameMode game ) { - return find( file, nifdir, *(new QByteArray()) ); + return find( file, nifdir, *(new QByteArray()), game ); } -QString TexCache::find( const QString & file, const QString & nifdir, QByteArray & data ) +QString TexCache::find( const QString & file, const QString & nifdir, QByteArray & data, Game::GameMode game ) { if ( file.isEmpty() ) return QString(); @@ -212,10 +213,7 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray return dir.filePath( filename ); } - - QStringList folders = settings.value( "Settings/Resources/Folders", QStringList() ).toStringList(); - - for ( QString folder : folders ) { + for ( QString folder : Game::GameManager::get_folder_list(game) ) { // TODO: Always search nifdir without requiring a relative entry // in folders? Not too intuitive to require ".\" in your texture folder list // even if it is added by default. @@ -233,7 +231,7 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray } // Search through archives last, and load any requested textures into memory. - for ( FSArchiveFile * archive : FSManager::archiveList() ) { + for ( FSArchiveFile * archive : Game::GameManager::get_archive_handles(game) ) { if ( archive ) { filename = QDir::fromNativeSeparators( filename.toLower() ); if ( archive->hasFile( filename ) ) { @@ -263,7 +261,7 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray filename.prepend( "textures\\" ); } - return find( filename, nifdir, data ); + return find( filename, nifdir, data, game ); } if ( !replaceExt ) @@ -295,6 +293,8 @@ QString TexCache::stripPath( const QString & filepath, const QString & nifFolder QDir basePath; QSettings settings; + + // TODO: New asset manager support QStringList folders = settings.value( "Settings/Resources/Folders", QStringList() ).toStringList(); for ( QString base : folders ) { @@ -359,7 +359,7 @@ void TexCache::fileChanged( const QString & filepath ) } } -int TexCache::bind( const QString & fname ) +int TexCache::bind( const QString & fname, Game::GameMode game ) { Tex * tx = textures.value( fname ); if ( !tx ) { @@ -382,7 +382,7 @@ int TexCache::bind( const QString & fname ) QByteArray outData; if ( tx->filepath.isEmpty() || tx->reload ) - tx->filepath = find( tx->filename, nifFolder, outData ); + tx->filepath = find( tx->filename, nifFolder, outData, game ); if ( !outData.isEmpty() || tx->reload ) { tx->data = outData; @@ -404,7 +404,7 @@ int TexCache::bind( const QString & fname ) return tx->mipmaps; } -int TexCache::bind( const QModelIndex & iSource ) +int TexCache::bind( const QModelIndex & iSource, Game::GameMode game ) { const NifModel * nif = qobject_cast( iSource.model() ); @@ -436,7 +436,7 @@ int TexCache::bind( const QModelIndex & iSource ) return tx->mipmaps; } } else if ( !nif->get( iSource, "File Name" ).isEmpty() ) { - return bind( nif->get( iSource, "File Name" ) ); + return bind( nif->get( iSource, "File Name" ), game ); } } diff --git a/src/gl/gltex.h b/src/gl/gltex.h index fca704e12..f21de2a56 100644 --- a/src/gl/gltex.h +++ b/src/gl/gltex.h @@ -33,6 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLTEX_H #define GLTEX_H +#include "gamemanager.h" + #include // Inherited #include #include @@ -97,9 +99,9 @@ class TexCache final : public QObject ~TexCache(); //! Bind a texture from filename - int bind( const QString & fname ); + int bind( const QString & fname, Game::GameMode game = Game::OTHER ); //! Bind a texture from pixel data - int bind( const QModelIndex & iSource ); + int bind( const QModelIndex & iSource, Game::GameMode game = Game::OTHER ); //! Debug function for getting info about a texture QString info( const QModelIndex & iSource ); @@ -110,8 +112,8 @@ class TexCache final : public QObject bool importFile( NifModel * nif, const QModelIndex & iSource, QModelIndex & iData ); //! Find a texture based on its filename - static QString find( const QString & file, const QString & nifFolder ); - static QString find( const QString & file, const QString & nifFolder, QByteArray & data ); + static QString find( const QString & file, const QString & nifFolder, Game::GameMode game = Game::OTHER ); + static QString find( const QString & file, const QString & nifFolder, QByteArray & data, Game::GameMode game = Game::OTHER ); //! Remove the path from a filename static QString stripPath( const QString & file, const QString & nifFolder ); //! Checks whether the given file can be loaded diff --git a/src/io/material.cpp b/src/io/material.cpp index 036e2fc35..e8426e493 100644 --- a/src/io/material.cpp +++ b/src/io/material.cpp @@ -33,7 +33,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "material.h" #include -#include #include #include @@ -47,13 +46,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define BGSM 0x4D534742 #define BGEM 0x4D454742 -Material::Material( QString name ) +Material::Material( QString name, Game::GameMode game ) { localPath = toLocalPath( name.replace( "\\", "/" ) ); if ( localPath.startsWith( "data/", Qt::CaseInsensitive ) ) { localPath.remove( 0, 5 ); } - data = find( localPath ); + data = find( localPath, game ); fileExists = !data.isEmpty(); } @@ -109,14 +108,13 @@ bool Material::readFile() return in.status() == QDataStream::Ok; } -QByteArray Material::find( QString path ) +QByteArray Material::find( QString path, Game::GameMode game ) { QSettings settings; - QStringList folders = settings.value( "Settings/Resources/Folders", QStringList() ).toStringList(); QString filename; QDir dir; - for ( QString folder : folders ) { + for ( QString folder : Game::GameManager::get_folder_list(game) ) { dir.setPath( folder ); if ( dir.exists( path ) ) { @@ -128,7 +126,7 @@ QByteArray Material::find( QString path ) } } - for ( FSArchiveFile * archive : FSManager::archiveList() ) { + for ( FSArchiveFile * archive : Game::GameManager::get_archive_handles(game) ) { if ( archive ) { filename = QDir::fromNativeSeparators( path.toLower() ); if ( archive->hasFile( filename ) ) { @@ -175,7 +173,7 @@ QString Material::getPath() const } -ShaderMaterial::ShaderMaterial( QString name ) : Material( name ) +ShaderMaterial::ShaderMaterial( QString name, Game::GameMode game ) : Material( name, game ) { if ( fileExists ) readable = openFile(); @@ -264,7 +262,7 @@ bool ShaderMaterial::readFile() return in.status() == QDataStream::Ok; } -EffectMaterial::EffectMaterial( QString name ) : Material( name ) +EffectMaterial::EffectMaterial( QString name, Game::GameMode game ) : Material( name, game ) { if ( fileExists ) readable = openFile(); diff --git a/src/io/material.h b/src/io/material.h index 3f953df30..9b8d2f494 100644 --- a/src/io/material.h +++ b/src/io/material.h @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define MATERIAL_H #include "data/niftypes.h" +#include "gamemanager.h" #include #include @@ -54,7 +55,7 @@ class Material : public QObject friend class BSEffectShaderProperty; public: - Material( QString name ); + Material( QString name, Game::GameMode game ); bool isValid() const; QStringList textures() const; @@ -63,7 +64,7 @@ class Material : public QObject protected: bool openFile(); virtual bool readFile(); - QByteArray find( QString path ); + QByteArray find( QString path, Game::GameMode game ); QString toLocalPath( QString path ) const; @@ -133,7 +134,7 @@ class ShaderMaterial : public Material friend class BSLightingShaderProperty; public: - ShaderMaterial( QString name ); + ShaderMaterial( QString name, Game::GameMode game ); protected: bool readFile() override final; @@ -217,7 +218,7 @@ class EffectMaterial : public Material friend class BSEffectShaderProperty; public: - EffectMaterial( QString name ); + EffectMaterial( QString name, Game::GameMode game ); protected: bool readFile() override final; diff --git a/src/main.cpp b/src/main.cpp index efc9d8877..1c40b4fec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,6 +36,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/nifmodel.h" #include "model/kfmmodel.h" +#include "gamemanager.h" + #include #include #include @@ -97,6 +99,9 @@ int main( int argc, char * argv[] ) NifModel::loadXML(); KfmModel::loadXML(); + // Init game manager + auto mgr = Game::GameManager::get(); + int port = NIFSKOPE_IPC_PORT; QStack fnames; diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 011a9f99f..5463388dd 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -71,7 +71,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include #ifdef WIN32 # define WINDOWS_LEAN_AND_MEAN @@ -333,8 +332,6 @@ void NifSkope::exitRequested() // Must disconnect from this signal as it's set once for each widget for some reason disconnect( qApp, &QApplication::lastWindowClosed, this, &NifSkope::exitRequested ); - FSManager::del(); - if ( options ) { delete options; options = nullptr; @@ -1247,40 +1244,6 @@ void NifSkope::migrateSettings() const if ( oldVersion <= NifSkopeVersion( "2.0.dev1" ) ) { qDebug() << "Migrating to new Settings"; - // Sanitize backslashes - auto sanitize = []( QVariant oldVal ) { - QStringList sanitized; - for ( const QString & archive : oldVal.toStringList() ) { - if ( archive == "AUTO" ) { - sanitized.append( FSManager::autodetectArchives() ); - continue; - } - - sanitized.append( QDir::fromNativeSeparators( archive ) ); - } - - return sanitized; - }; - - QVariant foldersVal = settings.value( "Settings/Resources/Folders" ); - if ( foldersVal.toStringList().isEmpty() ) { - QVariant oldVal = settings.value( "Render Settings/Texture Folders" ); - if ( !oldVal.isNull() ) { - settings.setValue( "Settings/Resources/Folders", sanitize( oldVal ) ); - } - } - - QVariant archivesVal = settings.value( "Settings/Resources/Archives" ); - if ( archivesVal.toStringList().isEmpty() ) { - QVariant oldVal = settings.value( "FSEngine/Archives" ); - if ( !oldVal.isNull() ) { - settings.setValue( "Settings/Resources/Archives", sanitize( oldVal ) ); - } - } - - // Update archive handler - FSManager::get()->initialize(); - // Remove old keys settings.remove( "FSEngine" ); diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index 8b03a59c4..ddaf29a80 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -3,6 +3,8 @@ #include "ui_settingsrender.h" #include "ui_settingsresources.h" +#include "gamemanager.h" + #include "ui/widgets/colorwheel.h" #include "ui/widgets/floatslider.h" #include "ui/settingsdialog.h" @@ -10,7 +12,6 @@ #include "nifskope.h" #include -#include #include #include @@ -27,6 +28,7 @@ #include using namespace nstheme; +using Game::GameManager; SettingsPane::SettingsPane( QWidget * parent ) : @@ -469,7 +471,10 @@ bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QStr QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) { QSettings reg( regPath, QSettings::Registry32Format ); - QDir dir( reg.value( regValue ).toString() ); + + QString path = reg.value( regValue ).toString(); + path.remove('"'); + QDir dir( path ); if ( dir.exists() && dir.cd( gameFolder ) ) { gamePaths.append( dir.path() ); @@ -512,6 +517,11 @@ bool regFolderPaths( QStringList & gamePaths, const QStringList & regPaths, cons * Resources */ +static QString lblGame = "lblGame%1"; +static QString chkGame = "chkGame%1"; +static QString edtGame = "edtGame%1"; +static QString btnBrowse = "btnBrowse%1"; + SettingsResources::SettingsResources( QWidget * parent ) : SettingsPane( parent ), ui( new Ui::SettingsResources ) @@ -519,18 +529,57 @@ SettingsResources::SettingsResources( QWidget * parent ) : ui->setupUi( this ); SettingsDialog::registerPage( parent, ui->name->text() ); - archiveMgr = FSManager::get(); - folders = new QStringListModel( this ); archives = new QStringListModel( this ); ui->foldersList->setModel( folders ); ui->archivesList->setModel( archives ); -#ifndef Q_OS_WIN32 - ui->btnArchiveAutoDetect->setHidden( true ); + // TODO: Hide for new asset manager for now ui->btnFolderAutoDetect->setHidden( true ); -#endif + ui->btnAutoDetectGames->setHidden( true ); + + for ( int i = 0; i < Game::NUM_GAMES; i++ ) { + // Get each widget for game slot `i` + auto lbl = findChild(lblGame.arg(i)); + auto chk = findChild(chkGame.arg(i)); + auto edt = findChild(edtGame.arg(i)); + auto btn = findChild(btnBrowse.arg(i)); + + auto game = Game::GameMode(i); + auto game_string = Game::StringForMode(game); + if ( game_string.isEmpty() ) + continue; + + // Rename generic `GAME_N` label to game name + lbl->setText(game_string); + + // Sync line edit with game install path if it exists + auto path_string = GameManager::get_game_path(game); + if ( !path_string.isNull() ) + edt->setText(path_string); + + // Sync Enabled checkbox + chk->setChecked(GameManager::get_game_status(game)); + + auto folder_item = ui->foldersGameList->findItems(QString("GAME_%1").arg(i), Qt::MatchExactly).value(0, nullptr); + auto archive_item = ui->archivesGameList->findItems(QString("GAME_%1").arg(i), Qt::MatchExactly).value(0, nullptr); + //Q_ASSERT(folder_item && archive_item); + folder_item->setText(game_string); + folder_item->setHidden(!chk->isChecked()); + archive_item->setText(game_string); + archive_item->setHidden(!chk->isChecked()); + + connect( btn, &QPushButton::clicked, this, &SettingsResources::onBrowseClicked ); + + connect( chk, &QCheckBox::clicked, this, &SettingsPane::modifyPane ); + + // Hide game items from Folders and Archives lists when game is disabled + connect( chk, &QCheckBox::clicked, [chk, folder_item, archive_item](){ + folder_item->setHidden(!chk->isChecked()); + archive_item->setHidden(!chk->isChecked()); + } ); + } connect( ui->foldersList, &QListView::doubleClicked, this, &SettingsPane::modifyPane ); connect( ui->chkAlternateExt, &QCheckBox::clicked, this, &SettingsPane::modifyPane ); @@ -556,6 +605,15 @@ SettingsResources::SettingsResources( QWidget * parent ) : ui->btnArchiveDown->setEnabled( idx.row() < archives->rowCount() - 1 ); } ); + + connect( ui->archivesGameList->selectionModel(), &QItemSelectionModel::currentChanged, + this, &SettingsResources::setArchiveList + ); + + connect( ui->foldersGameList->selectionModel(), &QItemSelectionModel::currentChanged, + this, &SettingsResources::setFolderList + ); + } SettingsResources::~SettingsResources() @@ -566,14 +624,34 @@ void SettingsResources::read() { QSettings settings; - QVariant foldersVal = settings.value( "Settings/Resources/Folders", QStringList() ); - folders->setStringList( foldersVal.toStringList() ); + auto mgr = GameManager::get(); - QVariant archivesVal = settings.value( "Settings/Resources/Archives", QStringList() ); - archives->setStringList( archivesVal.toStringList() ); + mgr->load(); - ui->foldersList->setCurrentIndex( folders->index( 0, 0 ) ); - ui->archivesList->setCurrentIndex( archives->index( 0, 0 ) ); + for ( int i = 0; i < Game::NUM_GAMES; i++ ) { + // Get each checkbox for game slot `i` + auto chk = findChild(chkGame.arg(i)); + auto game = Game::GameMode(i); + chk->setChecked(mgr->get_game_status(game)); + } + + for ( int i = 0; i < ui->foldersGameList->count(); i++ ) { + auto item = ui->foldersGameList->item(i); + if ( !item->isHidden() ) { + ui->foldersGameList->setCurrentRow(i); + break; + } + } + + for ( int i = 0; i < ui->archivesGameList->count(); i++ ) { + auto item = ui->archivesGameList->item(i); + if ( !item->isHidden() ) { + ui->archivesGameList->setCurrentRow(i); + break; + } + } + setFolderList(); + setArchiveList(); ui->chkAlternateExt->setChecked( settings.value( "Settings/Resources/Alternate Extensions", true ).toBool() ); @@ -585,19 +663,21 @@ void SettingsResources::write() if ( !isModified() ) return; - QSettings settings; - - settings.setValue( "Settings/Resources/Folders", folders->stringList() ); - settings.setValue( "Settings/Resources/Archives", archives->stringList() ); + auto mgr = GameManager::get(); - // Sync FSManager to Archives list - archiveMgr->archives.clear(); - for ( const QString an : archives->stringList() ) { - if ( !archiveMgr->archives.contains( an ) ) - if ( auto a = FSArchiveHandler::openArchive( an ) ) - archiveMgr->archives.insert( an, a ); + for ( int i = 0; i < Game::NUM_GAMES; i++ ) { + // Get each checkbox for game slot `i` + auto chk = findChild(chkGame.arg(i)); + auto game = Game::GameMode(i); + mgr->set_game_status(game, chk->isChecked()); } + mgr->save(); + + // Reload the currently open archive handles + mgr->reset_archive_handles(); + + QSettings settings; settings.setValue( "Settings/Resources/Alternate Extensions", ui->chkAlternateExt->isChecked() ); setModified( false ); @@ -610,6 +690,62 @@ void SettingsResources::setDefault() read(); } +void SettingsResources::setFolderList() +{ + folders->setStringList(GameManager::get_folder_list(currentFolderItem())); + ui->foldersList->setCurrentIndex( folders->index( 0, 0 ) ); +} + +void SettingsResources::setArchiveList() +{ + archives->setStringList(GameManager::get_archive_list(currentArchiveItem())); + ui->archivesList->setCurrentIndex( archives->index( 0, 0 ) ); +} + +void SettingsResources::onBrowseClicked() +{ + QPushButton * btn = qobject_cast(sender()); + if ( !btn ) + return; + + QFileDialog dialog(this); + dialog.setFileMode(QFileDialog::Directory); + + QString path; + if ( dialog.exec() ) + path = dialog.selectedFiles().at(0); + + auto edt = ui->resourcesGames->findChild(btn->objectName().replace("btnBrowse", "edtGame")); + if ( edt ) { + if ( path.isEmpty() ) + edt->clear(); + else + edt->setText(path); + } + + auto lbl = ui->resourcesGames->findChild(btn->objectName().replace("btnBrowse", "lblGame")); + if ( lbl ) { + GameManager::update_game(lbl->text(), path); + } + SettingsPane::modifyPane(); +} + +QString SettingsResources::currentFolderItem() +{ + return ui->foldersGameList->currentItem()->text(); +} + +QString SettingsResources::currentArchiveItem() +{ + return ui->archivesGameList->currentItem()->text(); +} + +void SettingsResources::modifyPane() +{ + GameManager::update_folders(currentFolderItem(), folders->stringList()); + GameManager::update_archives(currentArchiveItem(), archives->stringList()); + SettingsPane::modifyPane(); +} void moveIdxDown( QListView * view ) { @@ -643,6 +779,7 @@ void SettingsResources::on_btnFolderAdd_clicked() { QFileDialog dialog( this ); dialog.setFileMode( QFileDialog::Directory ); + dialog.setDirectory(GameManager::get_data_path(currentFolderItem())); QString path; if ( dialog.exec() ) @@ -674,90 +811,22 @@ void SettingsResources::on_btnFolderUp_clicked() void SettingsResources::on_btnFolderAutoDetect_clicked() { -#ifdef Q_OS_WIN32 - // List to hold all games paths that were detected - QStringList list; - - QString beth = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\%1"; - - // Fallout 4 - { - regFolderPath( list, beth.arg( "Fallout4" ), - "Installed Path", - "Data", - { "/Textures" }, - { ".ba2" } - ); - } - - // Skyrim, Fallout, Oblivion - { - regFolderPaths( list, - { beth.arg( "Skyrim Special Edition" ), beth.arg( "Skyrim" ), beth.arg( "FalloutNV" ), beth.arg( "Fallout3" ), beth.arg( "Oblivion" ) }, - "Installed Path", - "Data", - {}, /* No subdirs */ - { ".bsa" } - ); - } - - // Morrowind - { - regFolderPath( list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Morrowind", - "Installed Path", - "Data", - { "/Textures" }, - { ".bsa" } - ); - } - // CIV IV - { - regFolderPath( list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Firaxis Games\\Sid Meier's Civilization 4", - "INSTALLDIR", - "Assets/Art/shared" - ); - } - - // Freedom Force - { - QString ff = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\%1"; - QStringList ffSubDirs{ "./textures", "./skins/standard" }; - - regFolderPaths( list, - { ff.arg( "FFVTTR" ), ff.arg( "Freedom Force" ) }, - "InstallDir", - "Data/Art/library/area_specific/_textures", - ffSubDirs - ); - } - - QStringList archivesNew = folders->stringList() + list; - archivesNew.removeDuplicates(); - - folders->setStringList( archivesNew ); - ui->foldersList->setCurrentIndex( folders->index( 0, 0, QModelIndex() ) ); - - modifyPane(); -#endif } - void SettingsResources::on_btnArchiveAdd_clicked() { QStringList files = QFileDialog::getOpenFileNames( this, "Select one or more archives", - "", + GameManager::get_data_path(currentArchiveItem()), "Archive (*.bsa *.ba2)" ); QStringList filtered; - - filtered += FSManager::filterArchives( files, "textures" ); - filtered += FSManager::filterArchives( files, "materials" ); + + filtered += GameManager::get_filtered_archives_list( files, "materials" ); + filtered += GameManager::get_filtered_archives_list( files, "textures" ); filtered.removeDuplicates(); for ( int i = 0; i < filtered.count(); i++ ) { @@ -789,21 +858,27 @@ void SettingsResources::on_btnArchiveUp_clicked() void SettingsResources::on_btnArchiveAutoDetect_clicked() { - QStringList archivesNew = archives->stringList(); - - QStringList autoList = FSManager::autodetectArchives( "textures" ); - // Fallout 4 Materials - autoList += FSManager::autodetectArchives( "materials" ); - for ( const QString & archive : autoList ) { - if ( !archivesNew.contains( archive, Qt::CaseInsensitive ) ) { - archivesNew.append( archive ); - } + QStringList archives_list = archives->stringList(); + QStringList data_archives = GameManager::get_archive_file_list(currentArchiveItem()); + + QStringList new_archives; + for ( const auto& a : GameManager::get_filtered_archives_list(data_archives, "materials") ) { + if ( archives_list.contains(a, Qt::CaseInsensitive) ) + continue; + archives_list << a; } - archivesNew.removeDuplicates(); + for ( const auto& a : GameManager::get_filtered_archives_list(data_archives, "textures") ) { + if ( archives_list.contains(a, Qt::CaseInsensitive) ) + continue; + archives_list << a; + } - archives->setStringList( archivesNew ); + archives_list.removeDuplicates(); + + archives->setStringList(archives_list); + + ui->archivesList->setCurrentIndex(archives->index(0, 0)); - ui->archivesList->setCurrentIndex( archives->index( 0, 0 ) ); modifyPane(); } diff --git a/src/ui/settingspane.h b/src/ui/settingspane.h index 4a99fcba1..1d98eaacb 100644 --- a/src/ui/settingspane.h +++ b/src/ui/settingspane.h @@ -93,6 +93,7 @@ class SettingsResources : public SettingsPane void setDefault() override final; public slots: + void modifyPane() override; void on_btnFolderAdd_clicked(); void on_btnFolderRemove_clicked(); void on_btnFolderDown_clicked(); @@ -105,10 +106,16 @@ public slots: void on_btnArchiveUp_clicked(); void on_btnArchiveAutoDetect_clicked(); + void setFolderList(); + void setArchiveList(); + + void onBrowseClicked(); + private: std::unique_ptr ui; - FSManager * archiveMgr; + QString currentFolderItem(); + QString currentArchiveItem(); QStringListModel * folders; QStringListModel * archives; diff --git a/src/ui/settingsresources.ui b/src/ui/settingsresources.ui index 0a6db16c5..1138f6f1c 100644 --- a/src/ui/settingsresources.ui +++ b/src/ui/settingsresources.ui @@ -58,6 +58,787 @@ 0 + + + Games + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + 0 + 0 + 792 + 545 + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_1 + + + edtGame1 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_2 + + + edtGame2 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_3 + + + edtGame3 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_4 + + + edtGame4 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_5 + + + edtGame5 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_6 + + + edtGame6 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_7 + + + edtGame7 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_8 + + + edtGame8 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_0 + + + edtGame0 + + + + + + + false + + + + 0 + 0 + + + + Add resources for other games in Paths and Archives under "Other Games" + + + + + + + + + + false + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Auto-Detect All + + + + + + + + + Paths @@ -75,6 +856,61 @@ 0 + + + + + 160 + 16777215 + + + + + GAME_1 + + + + + GAME_2 + + + + + GAME_3 + + + + + GAME_4 + + + + + GAME_5 + + + + + GAME_6 + + + + + GAME_7 + + + + + GAME_8 + + + + + GAME_0 + + + + @@ -177,6 +1013,61 @@ Game Paths 0 + + + + + 160 + 16777215 + + + + + GAME_1 + + + + + GAME_2 + + + + + GAME_3 + + + + + GAME_4 + + + + + GAME_5 + + + + + GAME_6 + + + + + GAME_7 + + + + + GAME_8 + + + + + GAME_0 + + + + From 0534267c7c431ee82045fa73e7bd1ebbceb57cc4 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 09:43:05 -0400 Subject: [PATCH 019/118] [GL] Support some legacy animations --- src/gl/controllers.cpp | 6 ++++++ src/gl/glcontroller.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 5435a468f..ae5035ea2 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -120,6 +120,9 @@ void ControllerManager::setSequence( const QString & seqname ) if ( nodename.isEmpty() ) { QModelIndex idx = nif->getIndex( iCB, "Node Name Offset" ); nodename = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); + + if ( nodename.isEmpty() ) + nodename = nif->get( iCB, "Target Name" ); } QString proptype = nif->get( iCB, "Property Type" ); @@ -134,6 +137,9 @@ void ControllerManager::setSequence( const QString & seqname ) if ( ctrltype.isEmpty() ) { QModelIndex idx = nif->getIndex( iCB, "Controller Type Offset" ); ctrltype = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); + + if ( ctrltype.isEmpty() && iController.isValid() ) + ctrltype = nif->getBlockName( iController ); } QString var1 = nif->get( iCB, "Controller ID" ); diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 2c84ef2ff..a06bb43a5 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -715,7 +715,7 @@ TransformInterpolator::TransformInterpolator( Controller * owner ) bool TransformInterpolator::update( const NifModel * nif, const QModelIndex & index ) { if ( Interpolator::update( nif, index ) ) { - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTransformData" ); + QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ), "NiKeyframeData" ); iTranslations = nif->getIndex( iData, "Translations" ); iRotations = nif->getIndex( iData, "Rotations" ); From 9a19a89579e9b7902195a30e4ae6d1a4b7cd0f20 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 09:43:17 -0400 Subject: [PATCH 020/118] [Build] Improve sed searching --- NifSkope_functions.pri | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/NifSkope_functions.pri b/NifSkope_functions.pri index ad95f8136..f2b4d9268 100644 --- a/NifSkope_functions.pri +++ b/NifSkope_functions.pri @@ -30,18 +30,21 @@ defineReplace(getSed) { SEDPATH = /sed.exe exists($${GNUWIN32}$${SEDPATH}) { - sedbin = $${GNUWIN32}$${SEDPATH} + sedbin = $${GNUWIN32}$${QMAKE_DIR_SEP} } else:exists($${CYGWIN}$${SEDPATH}) { - sedbin = $${CYGWIN}$${SEDPATH} + sedbin = $${CYGWIN}$${QMAKE_DIR_SEP} } else:exists($${CYGWIN64}$${SEDPATH}) { - sedbin = $${CYGWIN64}$${SEDPATH} + sedbin = $${CYGWIN64}$${QMAKE_DIR_SEP} } else { #message(Neither GnuWin32 or Cygwin were found) - sedbin = $$system(where sed 2> NUL) + SEDSYS = $$system(where sed 2> NUL) + SEDLIST = $$split(SEDSYS, "sed.exe") + SEDBIN = $$member(SEDLIST, 0) + sedbin = $$syspath($${SEDBIN}) } !isEmpty(sedbin) { - sedbin = \"$${sedbin}\" + sedbin = \"$${sedbin}sed.exe\" } } From 4bb811035200c976e407f419192a983620df705e Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 09:45:37 -0400 Subject: [PATCH 021/118] [GL] Fix some issues with sRGB and gl*Color functions The sRGB framebuffer being used for some versions caused glClearColor as well as glColor3/4 etc. to be incorrect. --- src/gl/bsshape.cpp | 1 + src/glview.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 44bb71455..0e8724523 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -515,6 +515,7 @@ void BSShape::drawVerts() const void BSShape::drawSelection() const { + glDisable(GL_FRAMEBUFFER_SRGB); if ( scene->options & Scene::ShowNodes ) Node::drawSelection(); diff --git a/src/glview.cpp b/src/glview.cpp index bfb278871..550bdf3e4 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -220,7 +220,7 @@ GLView::GLView( const QGLFormat & format, QWidget * p, const QGLWidget * shareWi connect(NifSkope::getOptions(), &SettingsDialog::update3D, [this]() { // Calling update() here in a lambda can crash.. //updateSettings(); - qglClearColor(cfg.background); + qglClearColor(clearColor()); //update(); }); connect(NifSkope::getOptions(), &SettingsDialog::update3D, this, static_cast(&GLView::update)); @@ -412,7 +412,8 @@ void GLView::paintGL() if ( scene->visMode & Scene::VisSilhouette ) { qglClearColor( QColor( 255, 255, 255, 255 ) ); } - //glViewport( 0, 0, width(), height() ); + + glDisable(GL_FRAMEBUFFER_SRGB); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); @@ -709,8 +710,9 @@ void GLView::resizeGL( int width, int height ) return; aspect = (GLdouble)width / (GLdouble)height; glViewport( 0, 0, width, height ); - qglClearColor( cfg.background ); + glDisable(GL_FRAMEBUFFER_SRGB); + qglClearColor(clearColor()); update(); } From 14a8d047cf5897339bb8ae7b01e5d3e85e52412a Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 09:48:18 -0400 Subject: [PATCH 022/118] VERSION increment --- README.md | 2 +- build/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c6a6d88e..bfe04002a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NifSkope 2.0.dev7 +# NifSkope 2.0.dev8 NifSkope is a tool for opening and editing the NetImmerse file format (NIF). NIF is used by video games such as Morrowind, Oblivion, Skyrim, Fallout 3, Fallout: New Vegas, Civilization IV, and more. diff --git a/build/VERSION b/build/VERSION index 589a5b993..c1d80d817 100644 --- a/build/VERSION +++ b/build/VERSION @@ -1 +1 @@ -2.0.dev7 +2.0.dev8 From 38bc88a61a0366459ca43f22ce39844667eb1da1 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 10:16:16 -0400 Subject: [PATCH 023/118] [Build] Disable XML copying --- NifSkope.pro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NifSkope.pro b/NifSkope.pro index c97ade11d..33700a6b8 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -499,7 +499,8 @@ win32:contains(QT_ARCH, i386) { copyDirs( $$SHADERS, shaders ) #copyDirs( $$LANG, lang ) - copyFiles( $$XML $$QSS ) + copyFiles( $$QSS ) + #copyFiles( $$XML ) # Copy Readmes and rename to TXT copyFiles( $$READMES,,,, md:txt ) From b411e7ce8f3910fcce6c9a435c87840f90fa92a3 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 11:47:02 -0400 Subject: [PATCH 024/118] [GL] Updated CRC to flag maps Re-scanning the latest files showed a missed `NO_EXPOSURE` usage. Unsure if there since release or if added. `LOD_OBJECTS` was also missing in NifSkope but was already present in nifxml. Outside of BTOs, one single NIF uses `NO_EXPOSURE`. As of August these are the only used shader flag CRCs. Since editing of this version will not be supported, unused flags will continue to be excluded. --- src/gl/glproperty.h | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 55ba519f9..e12330066 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -543,36 +543,43 @@ namespace ShaderFlags enum BSShaderCRC32 : unsigned int { - CAST_SHADOWS = 1563274220, + // Lighting + Effect + // SF1 ZBUFFER_TEST = 1740048692, - ZBUFFER_WRITE = 3166356979, - TWO_SIDED = 759557230, - VERTEXCOLORS = 348504749, - PBR = 731263983, SKINNED = 3744563888, ENVMAP = 2893749418, VERTEX_ALPHA = 2333069810, - FACE = 314919375, GRAYSCALE_TO_PALETTE_COLOR = 442246519, DECAL = 3849131744, DYNAMIC_DECAL = 1576614759, HAIRTINT = 1264105798, SKIN_TINT = 1483897208, EMIT_ENABLED = 2262553490, - GLOWMAP = 2399422528, REFRACTION = 1957349758, REFRACTION_FALLOFF = 902349195, - NOFADE = 2994043788, - INVERTED_FADE_PATTERN = 3030867718, RGB_FALLOFF = 3448946507, EXTERNAL_EMITTANCE = 2150459555, + // SF2 + ZBUFFER_WRITE = 3166356979, + GLOWMAP = 2399422528, + TWO_SIDED = 759557230, + VERTEXCOLORS = 348504749, + NOFADE = 2994043788, + LOD_OBJECTS = 2896726515, + // Lighting only + PBR = 731263983, + FACE = 314919375, + CAST_SHADOWS = 1563274220, MODELSPACENORMALS = 2548465567, TRANSFORM_CHANGED = 3196772338, + INVERTED_FADE_PATTERN = 3030867718, + // Effect only EFFECT_LIGHTING = 3473438218, FALLOFF = 3980660124, SOFT_EFFECT = 3503164976, GRAYSCALE_TO_PALETTE_ALPHA = 2901038324, - WEAPON_BLOOD = 2078326675 + WEAPON_BLOOD = 2078326675, + NO_EXPOSURE = 3707406987 }; static QMap CRC_TO_FLAG = { @@ -604,12 +611,14 @@ namespace ShaderFlags {WEAPON_BLOOD, (uint64_t)SLSF2_Weapon_Blood << 32}, {TRANSFORM_CHANGED, (uint64_t)SLSF2_Assume_Shadowmask << 32}, {EFFECT_LIGHTING, (uint64_t)SLSF2_Effect_Lighting << 32}, + {LOD_OBJECTS, (uint64_t)SLSF2_LOD_Objects << 32}, // TODO {PBR, 0}, {REFRACTION_FALLOFF, 0}, {INVERTED_FADE_PATTERN, 0}, {HAIRTINT, 0}, + {NO_EXPOSURE, 0}, }; } From d071fc5aba60d727d08b0111d333662f080a1c2d Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 13:37:21 -0400 Subject: [PATCH 025/118] [UI] Add signal/slot for scene to disable save --- src/gl/glscene.cpp | 2 ++ src/gl/glscene.h | 1 + src/nifskope_ui.cpp | 7 +++++++ 3 files changed, 10 insertions(+) diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 072de1893..97fda83be 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -216,6 +216,8 @@ void Scene::make( NifModel * nif, bool flushTextures ) return; game = Game::GameManager::GetGame(nif->getVersionNumber(), nif->getUserVersion(), nif->getUserVersion2()); + if ( game == Game::FALLOUT_76 ) + emit disableSave(); update( nif, QModelIndex() ); diff --git a/src/gl/glscene.h b/src/gl/glscene.h index 0f46f8a71..882902725 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -188,6 +188,7 @@ class Scene final : public QObject float timeMax() const; signals: void sceneUpdated(); + void disableSave(); public slots: void updateSceneOptions( bool checked ); diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 91b655ccb..8b8b93338 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -608,6 +608,10 @@ void NifSkope::initToolBars() } } ); + connect ( ogl->scene, &Scene::disableSave, [this]() { + ui->aSave->setDisabled(true); + ui->aSaveAs->setDisabled(true); + } ); // LOD Toolbar QToolBar * tLOD = ui->tLOD; @@ -791,6 +795,9 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) ogl->setEnabled( true ); setEnabled( true ); // IMPORTANT! + ui->aSave->setDisabled(false); + ui->aSaveAs->setDisabled(false); + int timeout = 2500; if ( success ) { // Scroll panel back to top From 6e4d6fa8dd236db488cafcc065ff7e27d4393a9b Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 20 Aug 2019 14:56:31 -0400 Subject: [PATCH 026/118] [UI] Fix string output for BSVertexDesc The automatic stringification for BSVertexDesc got lost in the refactor, when it was made into a bitfield which is treated as an aliased type like enum/bitflags and thus is mostly treated as uint64. --- src/model/nifmodel.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 7a3eedf4a..73ea5063e 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -1302,6 +1302,9 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const { QString optId = NifValue::enumOptionName( item->type(), value.toCount() ); + if ( item->type() == "BSVertexDesc" ) + return BSVertexDesc(value.toCount()).toString(); + if ( optId.isEmpty() ) return value.toString(); From 56c619074ecef61c4709bbcd75ff8a623938a9ee Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 21 Aug 2019 08:48:42 -0400 Subject: [PATCH 027/118] [UI] Message improvements Return pointer from Message helpers when needing to customize the behavior. Added a log message function directly to the model which allows any function with a nif pointer to support both UI messages and the file checker output. For example, functions that are sanitizers (run on save) and used to output information in the file checker. --- src/message.cpp | 8 ++- src/message.h | 4 +- src/model/basemodel.cpp | 22 ++++++- src/model/basemodel.h | 7 ++- src/model/kfmmodel.cpp | 2 +- src/model/nifmodel.cpp | 123 ++++++++-------------------------------- src/nifskope.cpp | 2 +- 7 files changed, 59 insertions(+), 109 deletions(-) diff --git a/src/message.cpp b/src/message.cpp index 5e8c96fed..56dc3ecc0 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -23,7 +23,7 @@ Message::~Message() } //! Static helper for message box without detail text -void Message::message( QWidget * parent, const QString & str, QMessageBox::Icon icon ) +QMessageBox* Message::message( QWidget * parent, const QString & str, QMessageBox::Icon icon ) { auto msgBox = new QMessageBox( parent ); msgBox->setWindowFlags( msgBox->windowFlags() | Qt::Tool ); @@ -36,10 +36,12 @@ void Message::message( QWidget * parent, const QString & str, QMessageBox::Icon msgBox->show(); msgBox->activateWindow(); + + return msgBox; } //! Static helper for message box with detail text -void Message::message( QWidget * parent, const QString & str, const QString & err, QMessageBox::Icon icon ) +QMessageBox* Message::message( QWidget * parent, const QString & str, const QString & err, QMessageBox::Icon icon ) { if ( !parent ) parent = qApp->activeWindow(); @@ -56,6 +58,8 @@ void Message::message( QWidget * parent, const QString & str, const QString & er msgBox->show(); msgBox->activateWindow(); + + return msgBox; } //! Static helper for installed message handler diff --git a/src/message.h b/src/message.h index 68b5a3d51..2cf1b12fa 100644 --- a/src/message.h +++ b/src/message.h @@ -22,8 +22,8 @@ class Message : QObject ~Message(); public: - static void message( QWidget *, const QString &, QMessageBox::Icon ); - static void message( QWidget *, const QString &, const QString &, QMessageBox::Icon ); + static QMessageBox* message( QWidget *, const QString &, QMessageBox::Icon ); + static QMessageBox* message( QWidget *, const QString &, const QString &, QMessageBox::Icon ); static void message( QWidget *, const QString &, const QMessageLogContext *, QMessageBox::Icon ); static void append( const QString &, const QString &, QMessageBox::Icon = QMessageBox::Warning ); diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index be0a2d985..751c404a9 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -33,7 +33,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "basemodel.h" #include "xml/xmlconfig.h" -#include "message.h" #include #include @@ -53,7 +52,7 @@ BaseModel::BaseModel( QObject * p ) : QAbstractItemModel( p ) { root = new NifItem( 0 ); parentWindow = qobject_cast(p); - msgMode = TstMessage; + msgMode = MSG_TEST; } BaseModel::~BaseModel() @@ -76,6 +75,25 @@ void BaseModel::setMessageMode( MsgMode mode ) msgMode = mode; } +BaseModel::MsgMode BaseModel::getMessageMode() const +{ + return msgMode; +} + +void BaseModel::logMessage( const QString & message, const QString & details, QMessageBox::Icon lvl ) const +{ + if ( msgMode == MSG_USER ) { + Message::append( nullptr, message, details, lvl ); + } else { + testMsg( details ); + } +} + +void BaseModel::logWarning( const QString & details ) const +{ + logMessage(tr("Warnings were generated while reading the file."), details); +} + void BaseModel::testMsg( const QString & m ) const { messages.append( TestMessage() << m ); diff --git a/src/model/basemodel.h b/src/model/basemodel.h index 58cd9afa1..14f0fcedd 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define BaseModel_H #include "data/nifitem.h" +#include "message.h" #include // Inherited #include @@ -298,10 +299,14 @@ class BaseModel : public QAbstractItemModel enum MsgMode { - UserMessage, TstMessage + MSG_USER, MSG_TEST }; void setMessageMode( MsgMode mode ); + MsgMode getMessageMode() const; + + void logMessage( const QString & message, const QString & details, QMessageBox::Icon lvl = QMessageBox::Warning ) const; + void logWarning( const QString & details ) const; signals: //! Messaging signal diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index 3ee4fae67..5e23f8377 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -159,7 +159,7 @@ bool KfmModel::updateArrayItem( NifItem * array ) if ( d1 > 1024 * 1024 * 8 ) { auto m = tr( "array %1 much too large. %2 bytes requested" ).arg( array->name() ).arg( d1 ); - if ( msgMode == UserMessage ) { + if ( msgMode == MSG_USER ) { Message::append( nullptr, tr( "Could not update array item." ), m, QMessageBox::Critical ); } else { testMsg( m ); diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 73ea5063e..11aac540a 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -547,20 +547,12 @@ bool NifModel::updateArrayItem( NifItem * array ) if ( rows > 1024 * 1024 * 8 ) { auto m = tr( "[%1] Array %2 much too large. %3 bytes requested" ).arg( getBlockNumber( array ) ) .arg( array->name() ).arg( rows ); - if ( msgMode == UserMessage ) { - Message::append( nullptr, tr( readFail ), m, QMessageBox::Critical ); - } else { - testMsg( m ); - } + logMessage(tr(readFail), m, QMessageBox::Critical); return false; } else if ( rows < 0 ) { auto m = tr( "[%1] Array %2 invalid" ).arg( getBlockNumber( array ) ).arg( array->name() ); - if ( msgMode == UserMessage ) { - Message::append( nullptr, tr( readFail ), m, QMessageBox::Critical ); - } else { - testMsg( m ); - } + logMessage(tr(readFail), m, QMessageBox::Critical); return false; } @@ -690,11 +682,7 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at ) return createIndex( branch->row(), 0, branch ); } - if ( msgMode == UserMessage ) { - Message::critical( nullptr, tr( "Could not insert NiBlock." ), tr( "unknown block %1" ).arg( identifier ) ); - } else { - testMsg( tr( "unknown block %1" ) ); - } + logMessage(tr("Could not insert NiBlock."), tr("Unknown block %1").arg(identifier), QMessageBox::Critical); return QModelIndex(); } @@ -817,11 +805,7 @@ void NifModel::reorderBlocks( const QVector & order ) QString err = tr( "NifModel::reorderBlocks() - invalid argument" ); if ( order.count() != getBlockCount() ) { - if ( msgMode == UserMessage ) { - Message::critical( nullptr, err ); - } else { - testMsg( err ); - } + logMessage(tr("Reorder Blocks error"), err, QMessageBox::Critical); return; } @@ -830,12 +814,7 @@ void NifModel::reorderBlocks( const QVector & order ) for ( qint32 n = 0; n < order.count(); n++ ) { if ( blockMap.contains( order[n] ) || order[n] < 0 || order[n] >= getBlockCount() ) { - if ( msgMode == UserMessage ) { - Message::critical( nullptr, err ); - } else { - testMsg( err ); - } - + logMessage(tr("Reorder Blocks error"), err, QMessageBox::Critical); return; } @@ -1029,12 +1008,7 @@ void NifModel::insertAncestor( NifItem * parent, const QString & identifier, int insertType( parent, data ); } } else { - if ( msgMode == UserMessage ) { - Message::warning( nullptr, tr( "Cannot insert parent." ), tr( "unknown parent %1" ).arg( identifier ) ); - } else { - testMsg( tr( "unknown parent %1" ).arg( identifier ) ); - } - + logMessage(tr("Cannot insert parent."), tr("Unknown parent %1").arg(identifier)); } restoreState(); @@ -1718,11 +1692,7 @@ bool NifModel::setHeaderString( const QString & s ) ) ) { auto m = tr( "Could not open %1 because it is not a supported type." ).arg( fileinfo.fileName() ); - if ( msgMode == UserMessage ) { - Message::critical( nullptr, m ); - } else { - testMsg( m ); - } + logMessage(m, m, QMessageBox::Critical); return false; } @@ -1746,12 +1716,7 @@ bool NifModel::setHeaderString( const QString & s ) if ( !isVersionSupported( version ) ) { auto m = tr( "Version %1 (%2) is not supported." ).arg( version2string( version ), v ); - if ( msgMode == UserMessage ) { - Message::critical( nullptr, m ); - } else { - testMsg( m ); - } - + logMessage(m, m, QMessageBox::Critical); return false; } @@ -1762,12 +1727,9 @@ bool NifModel::setHeaderString( const QString & s ) return true; } - if ( msgMode == UserMessage ) { - Message::critical( nullptr, tr( "Invalid header string" ) ); - } else { - testMsg( tr( "Invalid header string" ) ); - } - + auto m = tr("Invalid header string"); + logMessage(m, m, QMessageBox::Critical); + return false; } @@ -1786,12 +1748,8 @@ bool NifModel::load( QIODevice & device ) // read header NifItem * header = getHeaderItem(); if ( !header || !loadHeader( header, stream ) ) { - auto m = tr( "failed to load file header (version %1, %2)" ).arg( version, 0, 16 ).arg( version2string( version ) ); - if ( msgMode == UserMessage ) { - Message::critical( nullptr, tr( "The file could not be read. See Details for more information." ), m ); - } else { - testMsg( m ); - } + auto m = tr( "Failed to load file header (version %1, %2)" ).arg( version, 0, 16 ).arg( version2string( version ) ); + logMessage(tr(readFail), m, QMessageBox::Critical); resetState(); return false; @@ -1850,12 +1808,7 @@ bool NifModel::load( QIODevice & device ) device.read( (char *)&dummy, 4 ); if ( dummy != 0 ) { - auto m = tr( "non-zero block separator (%1) preceeding block %2" ).arg( dummy ).arg( blktyp ); - if ( msgMode == UserMessage ) { - Message::append( tr( "Warnings were generated while reading NIF file." ), m ); - } else { - testMsg( m ); - } + logWarning(tr("Non-zero block separator (%1) preceding block %2").arg(dummy).arg(blktyp)); } } @@ -1893,14 +1846,9 @@ bool NifModel::load( QIODevice & device ) set( newBlock, "Access", metadata.access ); } } else { - auto m = tr( "warning: block %1 (%2) not inserted!" ).arg( c ).arg( blktyp ); - if ( msgMode == UserMessage ) { - Message::append( tr( "Warnings were generated while reading NIF file." ), m ); - } else { - testMsg( m ); - } + logWarning(tr("Block %1 (%2) not inserted!").arg(c).arg(blktyp)); - throw tr( "encountered unknown block (%1)" ).arg( blktyp ); + throw tr( "Encountered unknown block (%1)" ).arg( blktyp ); } } catch ( QString & err ) @@ -1926,11 +1874,7 @@ bool NifModel::load( QIODevice & device ) .arg( QString::number( curpos + size, 16 ) ); - if ( msgMode == UserMessage ) { - Message::append( tr( "Warnings were generated while reading NIF file." ), m ); - } else { - testMsg( m ); - } + logWarning(m); } else { throw tr( "failed to reposition device at block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( root->child( c )->name() ); @@ -2012,12 +1956,7 @@ bool NifModel::load( QIODevice & device ) } catch ( QString & err ) { - if ( msgMode == UserMessage ) { - Message::append( nullptr, tr( readFail ), QString( "Pos %1: " ).arg( device.pos() ) + err, QMessageBox::Critical ); - } else { - testMsg( err ); - } - + logMessage(tr(readFail), QString("Pos %1: ").arg(device.pos()) + err, QMessageBox::Critical); reset(); return false; } @@ -2143,11 +2082,7 @@ bool NifModel::loadHeaderOnly( const QString & fname ) NifItem * header = getHeaderItem(); if ( !header || !loadHeader( header, stream ) ) { - if ( msgMode == UserMessage ) { - Message::critical( nullptr, tr( "Failed to load file header." ), tr( "Version %1" ).arg( version ) ); - } else { - testMsg( tr( "Failed to load file header version %1" ).arg( version ) ); - } + logMessage(tr(readFail), tr("Failed to load file header version %1").arg(version), QMessageBox::Critical); return false; } @@ -2267,12 +2202,7 @@ int NifModel::blockSize( NifItem * parent, NifSStream & stream ) const if ( child->isBinary() ) { // special byte } else { - auto m = tr( "block %1 %2 array size mismatch" ).arg( getBlockNumber( parent ) ).arg( child->name() ); - if ( msgMode == UserMessage ) { - Message::append( tr( "Warnings were generated while reading the blocks." ), m ); - } else { - testMsg( m ); - } + logWarning(tr("Block %1 %2 array size mismatch").arg(getBlockNumber(parent)).arg(child->name())); } } @@ -2373,9 +2303,7 @@ bool NifModel::saveItem( NifItem * parent, NifOStream & stream ) const if ( child->isBinary() ) { // special byte } else { - Message::append( tr( "Warnings were generated while reading the blocks." ), - tr( "block %1 %2 array size mismatch" ).arg( getBlockNumber( parent ) ).arg( child->name() ) - ); + logWarning(tr("Block %1 %2 array size mismatch").arg(getBlockNumber(parent)).arg(child->name())); } } @@ -2612,12 +2540,7 @@ void NifModel::checkLinks( int block, QStack & parents ) parents.push( block ); foreach ( const auto child, childLinks.value( block ) ) { if ( parents.contains( child ) ) { - auto m = tr( "infinite recursive link construct detected %1 -> %2" ).arg( block ).arg( child ); - if ( msgMode == UserMessage ) { - Message::append( tr( "Warnings were generated while reading NIF file." ), m ); - } else { - testMsg( m ); - } + logWarning(tr("Infinite recursive link detected (%1 -> %2 -> %1)").arg(block).arg(child)); childLinks[block].removeAll( child ); } else { @@ -2988,7 +2911,7 @@ void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & i return; if ( !inherits( btype, identifier ) && !inherits( identifier, btype ) ) { - Message::critical( nullptr, tr( "Cannot convert NiBlock." ), tr( "blocktype %1 and %2 are not related" ).arg( btype, identifier ) ); + logMessage(tr("Cannot convert NiBlock."), tr("Block type %1 and %2 are not related").arg(btype, identifier), QMessageBox::Critical); return; } diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 5463388dd..a08412063 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -163,7 +163,7 @@ NifSkope::NifSkope() nifEmpty = new NifModel( this ); proxyEmpty = new NifProxyModel( this ); - nif->setMessageMode( BaseModel::UserMessage ); + nif->setMessageMode( BaseModel::MSG_USER ); // Setup QUndoStack nif->undoStack = new QUndoStack( this ); From e14f6d638a5ca1e150c6f8087f5135c12a61ec23 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 21 Aug 2019 14:19:26 -0400 Subject: [PATCH 028/118] [UI] Improved file checker Renamed XML Checker to File Checker since apparently nobody realizes the window even exists. Added basic value checking. Currently arrays are not traversable except for the first item of the array. Compounds can be descended through the slash syntax used elsewhere in the program. Added a few basic error checkers that run on Sanitize as well as during file checking. They were only done upon user request so they are not very thorough. Files that error during header read no longer fail silently. Also added an option to only read the header and the BSStream version is now printed with the filename. --- src/message.cpp | 2 +- src/spellbook.cpp | 25 ++++++ src/spellbook.h | 13 ++- src/spells/sanitize.cpp | 146 ++++++++++++++++++++++++++++++ src/spells/sanitize.h | 84 +++++++++++++++++ src/ui/nifskope.ui | 4 +- src/ui/widgets/xmlcheck.cpp | 173 ++++++++++++++++++++++++++++++++---- src/ui/widgets/xmlcheck.h | 54 ++++++++++- 8 files changed, 472 insertions(+), 29 deletions(-) diff --git a/src/message.cpp b/src/message.cpp index 56dc3ecc0..0f4d0abc6 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -200,7 +200,7 @@ template <> TestMessage & TestMessage::operator<<(const char * x) template <> TestMessage & TestMessage::operator<<(QString x) { space( s ); - s += "\"" + x + "\""; + s += x; //"\"" + x + "\""; return *this; } diff --git a/src/spellbook.cpp b/src/spellbook.cpp index 9a02b9e14..44e60a9cf 100644 --- a/src/spellbook.cpp +++ b/src/spellbook.cpp @@ -72,6 +72,12 @@ QList & SpellBook::sanitizers() return _sanitizers; } +QList& SpellBook::checkers() +{ + static QList _checkers = QList(); + return _checkers; +} + SpellBook::SpellBook( NifModel * nif, const QModelIndex & index, QObject * receiver, const char * member ) : QMenu(), Nif( 0 ) { setTitle( "Spells" ); @@ -235,6 +241,9 @@ void SpellBook::registerSpell( SpellPtr spell ) if ( spell->sanity() ) sanitizers().append( spell ); + if ( spell->checker() ) + checkers().append( spell ); + for ( SpellBook * book : books() ) { book->newSpellRegistered( spell ); } @@ -300,6 +309,22 @@ QModelIndex SpellBook::sanitize( NifModel * nif ) return ridx; } +QModelIndex SpellBook::check( NifModel * nif ) +{ + QPersistentModelIndex ridx; + + for ( SpellPtr spell : checkers() ) { + if ( spell->isApplicable(nif, QModelIndex()) ) { + QModelIndex idx = spell->cast(nif, QModelIndex()); + + if ( idx.isValid() && !ridx.isValid() ) + ridx = idx; + } + } + + return ridx; +} + QAction * SpellBook::exec( const QPoint & pos, QAction * act ) { if ( isEnabled() ) diff --git a/src/spellbook.h b/src/spellbook.h index 12a535beb..9ae3ae3d0 100644 --- a/src/spellbook.h +++ b/src/spellbook.h @@ -79,6 +79,8 @@ class Spell virtual bool instant() const { return false; } //! Whether the spell performs a sanitizing function virtual bool sanity() const { return false; } + //! Whether the spell performs an error checking function + virtual bool checker() const { return false; } //! Whether the spell has a high processing cost virtual bool batch() const { return (page() == "Batch") || (page() == "Block") || (page() == "Mesh"); } //! Hotkey sequence @@ -137,6 +139,14 @@ class SpellBook final : public QMenu //! Cast all sanitizing spells static QModelIndex sanitize( NifModel * nif ); + //! Cast all checking spells + static QModelIndex check(NifModel * nif); + + static QList & spells(); + static QList & instants(); + static QList & sanitizers(); + static QList & checkers(); + public slots: void sltNif( NifModel * nif ); @@ -161,11 +171,8 @@ protected slots: void checkActions( QMenu * menu, const QString & page ); private: - static QList & spells(); static QList & books(); static QMultiHash & hash(); - static QList & instants(); - static QList & sanitizers(); }; //! SpellBook manager diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index 5a3735ca4..ce998bde1 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -579,3 +579,149 @@ class spFillBlankControllerTypes final : public Spell }; REGISTER_SPELL( spFillBlankControllerTypes ) + + +/* + * Error Checking + */ + +bool spErrorNoneRefs::isApplicable( const NifModel *, const QModelIndex & index ) +{ + return !index.isValid(); +} + +QModelIndex spErrorNoneRefs::cast( NifModel * nif, const QModelIndex & ) +{ + for ( int i = 0; i < nif->getBlockCount(); i++ ) { + auto iNiAV = nif->getBlock( i, "NiAVObject" ); + if ( iNiAV.isValid() ) + checkArray( nif, iNiAV, {"Properties"} ); // "Children" + + auto iNiNET = nif->getBlock( i, "NiObjectNET" ); + if ( iNiNET.isValid() ) + checkArray( nif, iNiNET, {"Extra Data List"} ); + + auto iBSSI = nif->getBlock( i, "BSSkin::Instance" ); + if ( iBSSI.isValid() ) + checkRef( nif, iBSSI, "Skeleton Root" ); + } + + return QModelIndex(); +} + +void spErrorNoneRefs::checkArray( NifModel * nif, const QModelIndex & idx, QVector rows ) +{ + int i = nif->getBlockNumber( idx ); + for ( const auto & r : rows ) { + auto links = nif->getLinkArray( idx, r ); + auto noneCount = links.count( -1 ); + if ( links.size() > 0 && noneCount > 0 ) { + auto err = tr( "[%1] '%2' link array contains %3 None Refs." ).arg( i ).arg( r ).arg( noneCount ); + nif->logMessage( message(), err, QMessageBox::Critical ); + } + } +} + +void spErrorNoneRefs::checkRef( NifModel * nif, const QModelIndex & idx, const QString & name ) +{ + int i = nif->getBlockNumber( idx ); + if ( nif->getLink( idx, name ) == -1 ) { + nif->logMessage( message(), tr( "[%1] '%2' link is None." ).arg( i ).arg( name ), QMessageBox::Critical ); + } +} + +REGISTER_SPELL( spErrorNoneRefs ) + +bool spErrorInvalidPaths::isApplicable( const NifModel *, const QModelIndex & index ) +{ + return !index.isValid(); +} + +QModelIndex spErrorInvalidPaths::cast( NifModel * nif, const QModelIndex & ) +{ + for ( int i = 0; i < nif->getBlockCount(); i++ ) { + auto iBSSTS = nif->getBlock( i, "BSShaderTextureSet" ); + if ( iBSSTS.isValid() ) + checkPath( nif, iBSSTS, "Textures" ); + + auto iBSSNLP = nif->getBlock( i, "BSShaderNoLightingProperty" ); + if ( iBSSNLP.isValid() ) + checkPath( nif, iBSSNLP, "File Name" ); + + auto iBSLSP = nif->getBlock( i, "BSLightingShaderProperty" ); + if ( iBSLSP.isValid() ) { + checkPath( nif, iBSLSP, "Name", P_NO_EXT ); + checkPath( nif, iBSLSP, "Root Material", P_NO_EXT ); + } + + auto iBSESP = nif->getBlock( i, "BSEffectShaderProperty" ); + if ( iBSESP.isValid() ) { + checkPath( nif, iBSESP, "Source Texture" ); + checkPath( nif, iBSESP, "Greyscale Texture" ); + checkPath( nif, iBSESP, "Env Map Texture" ); + checkPath( nif, iBSESP, "Normal Texture" ); + checkPath( nif, iBSESP, "Env Mask Texture" ); + } + + auto iNiST = nif->getBlock( i, "NiSourceTexture" ); + if ( iNiST.isValid() ) + checkPath( nif, iNiST, "File Name" ); + } + + return QModelIndex(); +} + +void spErrorInvalidPaths::checkPath( NifModel * nif, const QModelIndex & idx, const QString & name, InvalidPath invalid ) +{ + auto iString = nif->getIndex( idx, name ); + if ( !iString.isValid() ) + return; + + int i = nif->getBlockNumber( idx ); + QVector paths; + if ( nif->isArray( iString ) ) + paths << nif->getArray( iString ); + else + paths << nif->get( iString ); + + for ( const auto & path : paths ) { + if ( (invalid & P_EMPTY) && path.isEmpty() ) + nif->logMessage( message(), tr( "[%1] '%2' cannot have empty filepaths." ).arg( i ).arg( name ) ); + else if ( path.isEmpty() ) + return; + + if ( (invalid & P_NO_EXT) && !path.contains( '.' ) ) + nif->logMessage( message(), tr( "[%1] '%2' has a filepath without a file extension." ).arg( i ).arg( name ) ); + else if ( (invalid & P_ABSOLUTE) && path.size() > 2 && path.at( 1 ) == ':' ) + nif->logMessage( message(), tr( "[%1] '%2' has an absolute filepath." ).arg( i ).arg( name ) ); + } +} + +REGISTER_SPELL( spErrorInvalidPaths ) + +bool spWarningEnvironmentMapping::isApplicable(const NifModel * nif, const QModelIndex & index) +{ + return nif->getUserVersion2() > 0 && !index.isValid(); +} + +QModelIndex spWarningEnvironmentMapping::cast(NifModel * nif, const QModelIndex & idx) +{ + for ( int i = 0; i < nif->getBlockCount(); i++ ) { + if ( nif->getUserVersion2() < 83 ) { + auto iBSSP = nif->getBlock(i, "BSShaderPPLightingProperty"); + if ( iBSSP.isValid() ) { + auto sf1 = nif->get(iBSSP, "Shader Flags"); + auto sf2 = nif->get(iBSSP, "Shader Flags 2"); + + if ( sf1 & 0x80 && !(sf2 & 0x8000) ) + nif->logMessage(message(), tr("[%1] Flags lack 'Envmap_Light_Fade' which may be a mistake.").arg(i)); + } + } else { + + } + } + return QModelIndex(); +} + + +REGISTER_SPELL(spWarningEnvironmentMapping) diff --git a/src/spells/sanitize.h b/src/spells/sanitize.h index 69099f610..751e78b65 100644 --- a/src/spells/sanitize.h +++ b/src/spells/sanitize.h @@ -1,6 +1,8 @@ #ifndef SANITIZE_H #define SANITIZE_H +#include "spellbook.h" + //! Reorders blocks class spSanitizeBlockOrder final : public Spell { @@ -23,5 +25,87 @@ class spSanitizeBlockOrder final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & ) override final; }; +// Base class for all warning and error checkers +class spChecker : public Spell +{ +public: + QString name() const override { return {}; } + QString page() const override { return {}; } + bool sanity() const override { return true; } + bool constant() const override { return true; } + bool checker() const override { return true; } + + bool isApplicable(const NifModel *, const QModelIndex & index) override + { + return false; + } + + QModelIndex cast(NifModel * nif, const QModelIndex &) override { return {}; } + + static QString message() { return {}; } +}; + +class spErrorNoneRefs : public spChecker +{ +public: + QString name() const override { return Spell::tr( "None Refs" ); } + QString page() const override final { return Spell::tr( "Error Checking" ); } + + bool isApplicable( const NifModel *, const QModelIndex & index ) override; + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final; + + static QString message() + { + return tr("One or more blocks contain None Refs."); + } + + void checkArray( NifModel * nif, const QModelIndex & idx, QVector rows ); + void checkRef( NifModel * nif, const QModelIndex & idx, const QString & name ); +}; + + +class spErrorInvalidPaths : public spChecker +{ +public: + QString name() const override { return Spell::tr( "Invalid Paths" ); } + QString page() const override final { return Spell::tr( "Error Checking" ); } + + bool isApplicable( const NifModel *, const QModelIndex & index ) override; + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final; + + typedef enum + { + P_EMPTY, + P_ABSOLUTE, + P_NO_EXT, + P_DEFAULT = (P_ABSOLUTE | P_NO_EXT) + } InvalidPath; + + static QString message() + { + return tr("One or more asset path strings contain an invalid path."); + } + + void checkPath( NifModel * nif, const QModelIndex & idx, const QString & name, InvalidPath invalid = P_DEFAULT ); +}; + + +class spWarningEnvironmentMapping : public spChecker +{ +public: + QString name() const override { return Spell::tr("Environment Mapping Flags"); } + QString page() const override final { return Spell::tr("Error Checking"); } + + bool isApplicable(const NifModel *, const QModelIndex & index) override; + + QModelIndex cast(NifModel * nif, const QModelIndex &) override final; + + static QString message() + { + return tr("Potential problems detected with Shader Flags."); + } +}; #endif // SANITIZE_H diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 2ecad1d36..083166a1a 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -239,8 +239,8 @@ - + @@ -1055,7 +1055,7 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - XML Checker + File Checker diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index aaf4d54f1..8c582cce9 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -5,6 +5,8 @@ #include "model/nifmodel.h" #include "ui/widgets/fileselect.h" +#include "spells/sanitize.h" + #include #include #include @@ -21,9 +23,10 @@ #include #include #include +#include #include -#define NUM_THREADS 2 +#define NUM_THREADS 4 TestShredder * TestShredder::create() @@ -66,12 +69,29 @@ TestShredder::TestShredder() btChoose->setDefaultAction( aChoose ); blockMatch = new QLineEdit( this ); + valueName = new QLineEdit( this ); + valueMatch = new QLineEdit( this ); + valueOps = new QComboBox( this ); + for ( const auto & o : ops_ord ) + valueOps->insertItem(ops[o].first, o ); + valueOps->setCurrentIndex(0); + + QString op_tip; + for ( const auto t : ops_ord ) + op_tip += QString("%1\t%2\r\n").arg(t).arg(ops[t].second); + valueOps->setToolTip(op_tip); + + chkCheckErrors = new QCheckBox(tr("Error Checking"), this); + chkCheckErrors->setChecked(settings.value("Error Checking", true).toBool()); - repErr = new QCheckBox( tr( "report errors only" ), this ); - repErr->setChecked( settings.value( "Report Errors Only", true ).toBool() ); + hdrOnly = new QCheckBox( tr( "Header Only" ), this ); + hdrOnly->setChecked( settings.value( "Header Only", false ).toBool() ); + + repErr = new QCheckBox( tr( "List Matches Only" ), this ); + repErr->setChecked( settings.value( "List Matches Only", true ).toBool() ); count = new QSpinBox(); - count->setRange( 1, 8 ); + count->setRange( 1, 16 ); count->setValue( settings.value( "Threads", NUM_THREADS ).toInt() ); connect( count, static_cast(&QSpinBox::valueChanged), this, &TestShredder::renumberThreads ); @@ -88,7 +108,7 @@ TestShredder::TestShredder() label = new QLabel( this ); label->setHidden( true ); - btRun = new QPushButton( tr( "run" ), this ); + btRun = new QPushButton( tr( "Run" ), this ); btRun->setCheckable( true ); connect( btRun, &QPushButton::clicked, this, &TestShredder::run ); @@ -112,10 +132,20 @@ TestShredder::TestShredder() lay->addLayout( hbox = new QHBoxLayout() ); hbox->addWidget( btChoose ); hbox->addWidget( blockMatch ); - hbox->addWidget( repErr ); hbox->addWidget( new QLabel( tr( "Threads:" ) ) ); hbox->addWidget( count ); + lay->addLayout( hbox = new QHBoxLayout() ); + hbox->addWidget( new QLabel( tr( "Value:" ) ) ); + hbox->addWidget( valueName ); + hbox->addWidget( valueOps ); + hbox->addWidget( valueMatch ); + + lay->addLayout( hbox = new QHBoxLayout() ); + hbox->addWidget( chkCheckErrors ); + hbox->addWidget( repErr ); + hbox->addWidget( hdrOnly ); + lay->addLayout( hbox = new QHBoxLayout() ); hbox->addWidget( new QLabel( tr( "Version Match:" ) ) ); hbox->addWidget( verMatch ); @@ -146,7 +176,9 @@ TestShredder::~TestShredder() settings.setValue( "Check NIF", chkNif->isChecked() ); settings.setValue( "Check KF", chkKf->isChecked() ); settings.setValue( "Check KFM", chkKfm->isChecked() ); - settings.setValue( "Report Errors Only", repErr->isChecked() ); + settings.setValue( "List Matches Only", repErr->isChecked() ); + settings.setValue( "Header Only", hdrOnly->isChecked() ); + settings.setValue( "Error Checking", chkCheckErrors->isChecked() ); settings.setValue( "Threads", count->value() ); settings.endGroup(); @@ -176,6 +208,12 @@ void TestShredder::renumberThreads( int num ) thread->blockMatch = blockMatch->text(); thread->verMatch = NifModel::version2number( verMatch->text() ); thread->reportAll = !repErr->isChecked(); + thread->headerOnly = hdrOnly->isChecked(); + thread->checkFile = chkCheckErrors->isChecked(); + + thread->valueName = valueName->text(); + thread->valueMatch = valueMatch->text(); + thread->op = OpType(valueOps->currentIndex()); if ( btRun->isChecked() ) { thread->start(); @@ -206,7 +244,7 @@ void TestShredder::run() QStringList extensions; if ( chkNif->isChecked() ) - extensions << "*.nif" << "*.nifcache" << "*.texcache" << "*.pcpatch" << "*.bto" << "*.btr"; + extensions << "*.nif" << "*.nifcache" << "*.texcache" << "*.pcpatch" << "*.bto" << "*.btr" << "*.item" << "*.nif_wii"; if ( chkKf->isChecked() ) extensions << "*.kf" << "*.kfa"; if ( chkKfm->isChecked() ) @@ -223,6 +261,12 @@ void TestShredder::run() thread->verMatch = NifModel::version2number( verMatch->text() ); thread->blockMatch = blockMatch->text(); thread->reportAll = !repErr->isChecked(); + thread->headerOnly = hdrOnly->isChecked(); + thread->checkFile = chkCheckErrors->isChecked(); + thread->valueName = valueName->text(); + thread->valueMatch = valueMatch->text(); + thread->op = OpType(valueOps->currentIndex()); + thread->start(); } } @@ -356,7 +400,6 @@ void FileQueue::clear() TestThread::TestThread( QObject * o, FileQueue * q ) : QThread( o ), queue( q ) { - reportAll = true; } TestThread::~TestThread() @@ -392,29 +435,117 @@ void TestThread::run() // lock the XML lock QReadLocker lck( lock ); + QString result; if ( model == &nif && nif.earlyRejection( filepath, blockMatch, verMatch ) ) { - bool loaded = model->loadFromFile( filepath ); + bool loaded = (headerOnly) ? nif.loadHeaderOnly(filepath) : model->loadFromFile(filepath); - QString result = QString( "%1 (%2)" ).arg( filepath, model->getVersion() ); + result = QString( "%1 (%2, %3, %4)" ) + .arg( filepath, model->getVersion() ).arg( nif.getUserVersion() ).arg( nif.getUserVersion2() ); QList messages = model->getMessages(); bool blk_match = false; + bool val_match = false; - if ( loaded && model == &nif ) + if ( !headerOnly && loaded && model == &nif ) { for ( int b = 0; b < nif.getBlockCount(); b++ ) { - // In case early rejection failed, such as if this is an older file without the block types in the header - // note if any of these blocks types match the specified one. - if ( blockMatch.isEmpty() == false && nif.inherits( nif.getBlockName( nif.getBlock( b ) ), blockMatch ) ) { - blk_match = true; + auto blk = nif.getBlock( b ); + bool current_match = !blockMatch.isEmpty() && nif.inherits(nif.getBlockName(blk), blockMatch); + blk_match |= current_match; + + NifValue value; + if ( (blockMatch.isEmpty() || current_match) && !valueName.isEmpty() && !valueMatch.isEmpty() ) { + auto nameIdx = nif.getIndex(blk, valueName); + bool hasName = nameIdx.isValid(); + if ( hasName ) { + value = nif.getValue(nameIdx); + + bool isInt = value.isCount() && !value.isFloat(); + bool isStr = value.isString() || value.type() == NifValue::tStringIndex || value.isFloat(); + + auto asInt = value.toCount(); + auto asStr = value.toString(); + if ( value.type() == NifValue::tStringIndex ) + asStr = nif.string(nameIdx); + + bool current_match = false; + + switch ( op ) { + case OP_EQ: + if ( isInt ) + current_match = (asInt == valueMatch.toInt(nullptr, 0)); + else if ( isStr ) + current_match = (asStr == valueMatch); + break; + case OP_NEQ: + if ( isInt ) + current_match = (asInt != valueMatch.toInt(nullptr, 0)); + else if ( isStr ) + current_match = (asStr != valueMatch); + break; + case OP_AND: + if ( !isInt ) + break; + current_match = (asInt & valueMatch.toInt(nullptr, 0)); + break; + case OP_AND_S: + if ( !isInt ) + break; + current_match = (asInt & (1 << valueMatch.toInt(nullptr, 0))); + break; + case OP_NAND: + if ( !isInt ) + break; + current_match = !(asInt & valueMatch.toInt(nullptr, 0)); + break; + case OP_STR_S: + current_match = asStr.startsWith(valueMatch, Qt::CaseInsensitive); + break; + case OP_STR_E: + current_match = asStr.endsWith(valueMatch, Qt::CaseInsensitive); + break; + case OP_STR_NS: + current_match = !asStr.startsWith(valueMatch, Qt::CaseInsensitive); + break; + case OP_STR_NE: + current_match = !asStr.endsWith(valueMatch, Qt::CaseInsensitive); + break; + case OP_CONT: + current_match = asStr.contains(valueMatch, Qt::CaseInsensitive); + break; + default: + current_match = false; + break; + } + + if ( current_match ) + messages += TestMessage( QtInfoMsg ) << + QString( "[%1] Found Match: %2 %3 %4 | Value: %5" ).arg(b) + .arg(valueName).arg(ops_ord[int(op)].toHtmlEscaped()) + .arg(valueMatch).arg(asStr); + } else { + current_match = false; + } + + val_match |= current_match; + } + + if ( checkFile ) { + messages += checkLinks(&nif, blk, kf); } + } - messages += checkLinks( &nif, nif.getBlock( b ), kf ); + if ( checkFile ) { + for ( auto checker : SpellBook::checkers() ) + checker->castIfApplicable(&nif, {}); + messages += nif.getMessages(); } - bool rep = reportAll; + } + + bool rep = reportAll || (blk_match && valueMatch.isEmpty()); // Don't show anything if block match is on but the requested type wasn't found & we're in block match mode - if ( blockMatch.isEmpty() == true || blk_match == true ) { + if ( blockMatch.isEmpty() == true || blk_match == true || val_match == true ) { for ( const TestMessage& msg : messages ) { if ( msg.type() != QtDebugMsg ) { result += "
" + msg; @@ -425,6 +556,10 @@ void TestThread::run() if ( rep ) emit sigReady( result ); } + } else if ( !blockMatch.isEmpty() && !verMatch ) { + // Do not silently fail on unrecognized NIFs + result += QString("Did not recognize file as a NIF: %1").arg(filepath); + emit sigReady(result); } } diff --git a/src/ui/widgets/xmlcheck.h b/src/ui/widgets/xmlcheck.h index ff3a96037..32026b02e 100644 --- a/src/ui/widgets/xmlcheck.h +++ b/src/ui/widgets/xmlcheck.h @@ -1,4 +1,4 @@ -#ifndef SPELL_DEBUG_H +#ifndef SPELL_DEBUG_H #define SPELL_DEBUG_H @@ -9,6 +9,9 @@ #include #include +#include +#include + class QCheckBox; class QLabel; @@ -16,11 +19,46 @@ class QLineEdit; class QProgressBar; class QPushButton; class QSpinBox; +class QComboBox; class QTextBrowser; class TestMessage; class FileSelector; + +enum OpType +{ + OP_EQ, + OP_NEQ, + OP_AND, + OP_AND_S, + OP_NAND, + OP_STR_S, + OP_STR_E, + OP_STR_NS, + OP_STR_NE, + OP_CONT + +}; + +static std::array ops_ord = { + // EQ, NEQ, AND, AND_S, NAND, STR_E, STR_S, STR_NS, STR_NE, CONT + "==", "!=", "&", "& 1<<", "!&", "^", "$", "!^", "!$", u8"⊂" +}; + +static std::map> ops = { + { ops_ord[OP_EQ], {OP_EQ, "Equality"} }, + { ops_ord[OP_NEQ], {OP_NEQ, "Inequality"} }, + { ops_ord[OP_AND], {OP_AND, "Bitwise AND"} }, + { ops_ord[OP_AND_S], {OP_AND_S, "Bitwise AND (Shifted)"} }, + { ops_ord[OP_NAND], {OP_NAND, "Bitwise NAND"} }, + { ops_ord[OP_STR_S], {OP_STR_S, "Starts With"} }, + { ops_ord[OP_STR_E], {OP_STR_E, "Ends With"} }, + { ops_ord[OP_STR_NS], {OP_STR_NS, "Does not start with"} }, + { ops_ord[OP_STR_NE], {OP_STR_NE, "Does not end with"} }, + { ops_ord[OP_CONT], {OP_CONT, "Contains"} } +}; + class FileQueue final { public: @@ -50,8 +88,13 @@ class TestThread final : public QThread ~TestThread(); QString blockMatch; + QString valueName; + QString valueMatch; + OpType op; quint32 verMatch = 0; - bool reportAll = false; + bool reportAll = true; + bool headerOnly = false; + bool checkFile = true; signals: void sigStart( const QString & file ); @@ -93,9 +136,12 @@ protected slots: FileSelector * directory; QLineEdit * blockMatch; + QLineEdit * valueName; + QLineEdit * valueMatch; + QComboBox * valueOps; QCheckBox * recursive; - QCheckBox * chkNif, * chkKf, * chkKfm; - QCheckBox * repErr; + QCheckBox * chkNif, * chkKf, * chkKfm, *chkCheckErrors; + QCheckBox * repErr, * hdrOnly; QSpinBox * count; QLineEdit * verMatch; QTextBrowser * text; From 99eca0e0fa40c041cabfcda2089a549fea011281 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 21 Aug 2019 15:48:10 -0400 Subject: [PATCH 029/118] Import OBJ as bhkNiTriStripsShape Older Bethesda NIF versions (Oblivion, FO3) can use bhkNiTriStripsShape as a Havok shape which is relatively easy to import to from a file. --- src/lib/importex/importex.cpp | 16 ++++--- src/lib/importex/obj.cpp | 83 ++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/lib/importex/importex.cpp b/src/lib/importex/importex.cpp index a253c9c34..d362a0a96 100644 --- a/src/lib/importex/importex.cpp +++ b/src/lib/importex/importex.cpp @@ -43,7 +43,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. void exportObj( const NifModel * nif, const QModelIndex & index ); void exportCol( const NifModel * nif, QFileInfo ); -void importObj( NifModel * nif, const QModelIndex & index ); +void importObj( NifModel * nif, const QModelIndex & index, bool collision = false ); void import3ds( NifModel * nif, const QModelIndex & index ); @@ -53,6 +53,7 @@ void NifSkope::fillImportExportMenus() //mExport->addAction( tr( "Export .DAE" ) ); //mImport->addAction( tr( "Import .3DS" ) ); mImport->addAction( tr( "Import .OBJ" ) ); + mImport->addAction( tr( "Import .OBJ as Collision" ) ); } void NifSkope::sltImportExport( QAction * a ) @@ -80,18 +81,21 @@ void NifSkope::sltImportExport( QAction * a ) mImport->setDisabled( true ); return; } else { - if ( nif->getUserVersion2() >= 100 ) - mImport->setDisabled( true ); - else - mImport->setDisabled( false ); - + mImport->setDisabled( false ); mExport->setDisabled( false ); + + if ( nif->getUserVersion2() >= 100 ) + mImport->actions().at(0)->setDisabled( true ); + else if ( nif->getUserVersion2() == 0 ) + mImport->actions().at(1)->setDisabled( true ); } if ( a->text() == tr( "Export .OBJ" ) ) exportObj( nif, index ); else if ( a->text() == tr( "Import .OBJ" ) ) importObj( nif, index ); + else if ( a->text() == tr( "Import .OBJ as Collision" ) ) + importObj( nif, index, true ); //else if ( a->text() == tr( "Import .3DS" ) ) // import3ds( nif, index ); //else if ( a->text() == tr( "Export .DAE" ) ) diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index 5e3b1515b..beb923feb 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -562,12 +562,12 @@ static void addLink( NifModel * nif, const QModelIndex & iBlock, const QString & nif->setLink( iArray.child( numIndices, 0 ), link ); } -void importObj( NifModel * nif, const QModelIndex & index ) +void importObj( NifModel * nif, const QModelIndex & index, bool collision ) { //--Determine how the file will import, and be sure the user wants to continue--// // If no existing node is selected, create a group node. Otherwise use selected node - QPersistentModelIndex iNode, iShape, iMaterial, iData, iTexProp, iTexSource; + QPersistentModelIndex iNode, iShape, iStripsShape, iMaterial, iData, iTexProp, iTexSource; QModelIndex iBlock = nif->getBlock( index ); bool cBSShaderPPLightingProperty = false; @@ -579,7 +579,9 @@ void importObj( NifModel * nif, const QModelIndex & index ) if ( iBlock.isValid() && nif->inherits( iBlock, "NiNode" ) ) { iNode = iBlock; - } else if ( iBlock.isValid() && nif->itemName( iBlock ) == "NiTriShape" ) { + } else if ( iBlock.isValid() + && (nif->itemName( iBlock ) == "NiTriShape" + || (collision && nif->inherits( iBlock, "BSTriShape" ))) ) { iShape = iBlock; //Find parent of NiTriShape int par_num = nif->getParent( nif->getBlockNumber( iBlock ) ); @@ -625,14 +627,21 @@ void importObj( NifModel * nif, const QModelIndex & index ) QString question; - if ( iNode.isValid() == true ) { - if ( iShape.isValid() == true ) { - question = tr( "NiTriShape selected. The first imported mesh will replace the selected one." ); + if ( !collision ) { + if ( iNode.isValid() ) { + if ( iShape.isValid() ) { + question = tr( "NiTriShape selected. The first imported mesh will replace the selected one." ); + } else { + question = tr( "NiNode selected. Meshes will be attached to the selected node." ); + } } else { - question = tr( "NiNode selected. Meshes will be attached to the selected node." ); + question = tr( "No NiNode or NiTriShape selected. Meshes will be imported to the root of the file." ); } } else { - question = tr( "No NiNode or NiTriShape selected. Meshes will be imported to the root of the file." ); + if ( iNode.isValid() || iShape.isValid() ) { + question = tr( "The Havok collision will be added to this object." ); + } + question = tr( "The Havok collision will be added to the root of the file." ); } int result = QMessageBox::question( 0, tr( "Import OBJ" ), question, QMessageBox::Ok, QMessageBox::Cancel ); @@ -749,13 +758,15 @@ void importObj( NifModel * nif, const QModelIndex & index ) bool first_tri_shape = true; QMapIterator *> it( ofaces ); + nif->holdUpdates( true ); + while ( it.hasNext() ) { it.next(); if ( !it.value()->count() ) continue; - if ( it.key() != "collision" ) { + if ( !collision ) { //If we are on the first shape, and one was selected in the 3D view, use the existing one bool newiShape = false; @@ -986,7 +997,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->set( iData, "Radius", radius ); nif->set( iData, "Unknown Short 2", 0x4000 ); - } else if ( nif->getVersionNumber() == 0x14000005 ) { + } else if ( nif->getUserVersion2() > 0 ) { // create experimental havok collision mesh QVector verts; QVector norms; @@ -994,7 +1005,9 @@ void importObj( NifModel * nif, const QModelIndex & index ) QVector points; - foreach ( ObjFace oface, *( it.value() ) ) { + shapecount++; + + for ( const ObjFace & oface : *( it.value() ) ) { Triangle tri; for ( int t = 0; t < 3; t++ ) { @@ -1029,7 +1042,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->setArray( iData, "Normals", norms ); Vector3 center; - foreach ( Vector3 v, verts ) { + for ( const Vector3 & v : verts ) { center += v; } @@ -1038,7 +1051,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->set( iData, "Center", center ); float radius = 0; - foreach ( Vector3 v, verts ) { + for ( const Vector3 & v : verts ) { float d = ( center - v ).length(); if ( d > radius ) @@ -1047,7 +1060,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->set( iData, "Radius", radius ); // do not stitch, because it looks better in the cs - QVector > strips = stripify( triangles, false ); + QVector > strips = stripify( triangles ); nif->set( iData, "Num Strips", strips.count() ); nif->set( iData, "Has Points", 1 ); @@ -1060,7 +1073,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->updateArray( iPoints ); int x = 0; int z = 0; - foreach ( QVector strip, strips ) { + for ( const QVector & strip : strips ) { nif->set( iLengths.child( x, 0 ), strip.count() ); QModelIndex iStrip = iPoints.child( x, 0 ); nif->updateArray( iStrip ); @@ -1071,25 +1084,34 @@ void importObj( NifModel * nif, const QModelIndex & index ) nif->set( iData, "Num Triangles", z ); } - QPersistentModelIndex iShape = nif->insertNiBlock( "bhkNiTriStripsShape" ); + if ( shapecount == 1 ) { + iStripsShape = nif->insertNiBlock( "bhkNiTriStripsShape" ); + + // For some reason need to update all the fixed arrays... + nif->updateArray( iStripsShape, "Unused" ); - nif->setArray( iShape, "Unknown Floats 1", { 0.1f, 0.0f } ); - nif->setArray( iShape, "Unknown Ints 1", { 0, 0, 0, 0, 1 } ); - nif->set( iShape, "Scale", { 1.0, 1.0, 1.0 } ); - addLink( nif, iShape, "Strips Data", nif->getBlockNumber( iData ) ); - nif->set( iShape, "Num Data Layers", 1 ); - nif->updateArray( iShape, "Data Layers" ); - nif->setArray( iShape, "Data Layers", { 1 } ); + QPersistentModelIndex iBody = nif->insertNiBlock( "bhkRigidBody" ); + nif->setLink( iBody, "Shape", nif->getBlockNumber( iStripsShape ) ); + for( int i = 0; i < nif->rowCount( iBody ); i++ ) { + auto iChild = iBody.child( i, 0 ); + if ( nif->isArray( iChild ) ) + nif->updateArray( iChild ); + } - QPersistentModelIndex iBody = nif->insertNiBlock( "bhkRigidBody" ); - nif->setLink( iBody, "Shape", nif->getBlockNumber( iShape ) ); + QPersistentModelIndex iObject = nif->insertNiBlock( "bhkCollisionObject" ); - QPersistentModelIndex iObject = nif->insertNiBlock( "bhkCollisionObject" ); - nif->setLink( iObject, "Parent", nif->getBlockNumber( iNode ) ); - nif->set( iObject, "Unknown Short", 1 ); - nif->setLink( iObject, "Body", nif->getBlockNumber( iBody ) ); + QPersistentModelIndex iParent = (iShape.isValid()) ? iShape : iNode; + nif->setLink( iObject, "Parent", nif->getBlockNumber( iParent ) ); + nif->setLink( iObject, "Body", nif->getBlockNumber( iBody ) ); - nif->setLink( iNode, "Collision Object", nif->getBlockNumber( iObject ) ); + nif->setLink( iParent, "Collision Object", nif->getBlockNumber( iObject ) ); + } + + if ( shapecount >= 1 ) { + addLink( nif, iStripsShape, "Strips Data", nif->getBlockNumber( iData ) ); + nif->set( iStripsShape, "Num Filters", shapecount ); + nif->updateArray( iStripsShape, "Filters" ); + } } spTangentSpace TSpacer; @@ -1098,6 +1120,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) //Finished with the first shape which is the only one that can import over the top of existing data first_tri_shape = false; } + nif->holdUpdates( false ); qDeleteAll( ofaces ); From c119f8129f7fb9e974aea370f7dbc1749bc4aa16 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 22 Aug 2019 03:37:57 -0400 Subject: [PATCH 030/118] Add missing changes for 99eca0e --- src/nifskope_ui.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 8b8b93338..2816855fe 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -779,10 +779,11 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) mImport->setDisabled( true ); } else { mExport->setDisabled( false ); + mImport->setDisabled( false ); if ( nif->getUserVersion2() >= 100 ) - mImport->setDisabled( true ); - else - mImport->setDisabled( false ); + mImport->actions().at(0)->setDisabled(true); + else if ( nif->getUserVersion2() == 0 ) + mImport->actions().at(1)->setDisabled(true); } // Reconnect the models to the views From d96a7d40dd9e5f442e6763b8875d85f5946c0d4b Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 22 Aug 2019 09:33:50 -0400 Subject: [PATCH 031/118] Game Manager update Switched back to absolute paths for game folders, fixed auto detection in the UI and at initializing of said paths. The idea was to show only relative paths for both folders and archives if they were under the install path as this would tidy up the UI. In practice, supporting both relative and absolute (outside of game install) paths in the same list in the UI and registry is not worth the effort. --- src/gamemanager.cpp | 114 ++++++++++++++++++++++++++++++++-------- src/gamemanager.h | 14 ++++- src/ui/settingspane.cpp | 17 +++++- 3 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/gamemanager.cpp b/src/gamemanager.cpp index 0bf55a896..c85c6cc05 100644 --- a/src/gamemanager.cpp +++ b/src/gamemanager.cpp @@ -55,6 +55,31 @@ QStringList archives_list( const QString& path, const QString& data_dir, const Q return list_filtered; } +QStringList existing_folders(GameMode game, QString path) +{ + // TODO: Restore the minimal previous support for detecting Civ IV, etc. + if ( game == OTHER ) + return {}; + + QStringList folders; + for ( const auto& f : FOLDERS.value(game, {}) ) { + QDir dir(QString("%1/%2").arg(path).arg(DATA.value(game, ""))); + if ( dir.exists(f) ) + folders << QFileInfo(dir, f).absoluteFilePath(); + } + + return folders; +} + +GameManager::GameInfo get_game_info(GameMode game) +{ + GameManager::GameInfo info; + info.id = game; + info.name = StringForMode(game); + info.path = registry_game_path(KEY.value(game, {})); + return info; +} + QString StringForMode( GameMode game ) { if ( game >= NUM_GAMES ) @@ -68,20 +93,37 @@ GameMode ModeForString( QString game ) return STRING.key(game, OTHER); } +QProgressDialog* prog_dialog( QString title ) +{ + QProgressDialog* dlg = new QProgressDialog(title, {}, 0, NUM_GAMES); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->show(); + return dlg; +} + +void process( QProgressDialog* dlg, int i ) +{ + if ( dlg ) { + dlg->setValue(i); + QCoreApplication::processEvents(); + } +} + GameManager::GameManager() { QSettings settings; int manager_version = settings.value("Game Manager Version", 0).toInt(); if ( manager_version == 0 ) { - manager_version++; - QProgressDialog* dlg = new QProgressDialog( "Initializing the Game Manager", {}, 0, NUM_GAMES ); - dlg->setAttribute(Qt::WA_DeleteOnClose); - dlg->show(); + auto dlg = prog_dialog("Initializing the Game Manager"); // Initial game manager settings initialize(manager_version, dlg); dlg->close(); } + if ( manager_version == 1 ) { + update(manager_version); + } + load(); reset_archive_handles(); } @@ -152,44 +194,62 @@ void GameManager::del() } } -void GameManager::initialize( int manager_version, QProgressDialog* dlg ) +void GameManager::initialize( int& manager_version, QProgressDialog* dlg ) { QSettings settings; QMap paths; QMap folders; QMap archives; QMap status; - for ( const auto & key : KEY.toStdMap() ) { - // Progress Bar - if ( dlg ) { - dlg->setValue(key.first); - QCoreApplication::processEvents(); - } - if ( key.second.isEmpty() ) + + for ( int g = 0; g < NUM_GAMES; g++ ) { + process(dlg, g); + auto game = get_game_info(GameMode(g)); + if ( game.path.isEmpty() ) continue; - auto game = key.first; - auto game_str = StringForMode(game); - auto path = registry_game_path(key.second); - paths.insert(game_str, path); - folders.insert(game_str, FOLDERS.value(game, {})); + paths.insert(game.name, game.path); + folders.insert(game.name, existing_folders(game.id, game.path)); // Filter and insert archives QStringList filtered; - if ( game == FALLOUT_4 || game == FALLOUT_76 ) - filtered.append(archives_list(path, DATA.value(game, {}), "materials")); - filtered.append(archives_list(path, DATA.value(game, {}), "textures")); + if ( game.id == FALLOUT_4 || game.id == FALLOUT_76 ) + filtered.append(archives_list(game.path, DATA.value(game.id, {}), "materials")); + filtered.append(archives_list(game.path, DATA.value(game.id, {}), "textures")); filtered.removeDuplicates(); - archives.insert(game_str, filtered); + archives.insert(game.name, filtered); // Game Enabled Status - status.insert(game_str, !path.isEmpty()); + status.insert(game.name, !game.path.isEmpty()); } settings.setValue("Game Paths", paths); settings.setValue("Game Folders", folders); settings.setValue("Game Archives", archives); settings.setValue("Game Status", status); + settings.setValue("Game Manager Version", ++manager_version); +} + +void GameManager::update( int& manager_version, QProgressDialog * dlg ) +{ + QSettings settings; + QMap folders; + + for ( int g = 0; g < NUM_GAMES; g++ ) { + process(dlg, g); + auto game = get_game_info(GameMode(g)); + if ( game.path.isEmpty() ) + continue; + + if ( manager_version == 1 ) + folders.insert(game.name, existing_folders(game.id, game.path)); + } + + if ( manager_version == 1 ) { + settings.setValue("Game Folders", folders); + manager_version++; + } + settings.setValue("Game Manager Version", manager_version); } @@ -280,6 +340,16 @@ QStringList GameManager::get_archive_list( const QString& game ) return get_archive_list(ModeForString(game)); } +QStringList GameManager::get_existing_folders_list(const GameMode game) +{ + return existing_folders(game, get()->game_paths.value(game, {})); +} + +QStringList GameManager::get_existing_folders_list(const QString & game) +{ + return get_existing_folders_list(ModeForString(game)); +} + QStringList GameManager::get_archive_file_list( const GameMode game ) { QDir data_dir = QDir(GameManager::get_data_path(game)); diff --git a/src/gamemanager.h b/src/gamemanager.h index 559629728..d289a7be6 100644 --- a/src/gamemanager.h +++ b/src/gamemanager.h @@ -224,6 +224,8 @@ class GameManager static QStringList get_archive_list(const GameMode game); static QStringList get_archive_list(const QString& game); + static QStringList get_existing_folders_list(const GameMode game); + static QStringList get_existing_folders_list(const QString& game); static QStringList get_archive_file_list(const GameMode game); static QStringList get_archive_file_list(const QString& game); static QStringList get_filtered_archives_list(const QStringList& list, const QString& folder); @@ -237,11 +239,21 @@ class GameManager static void update_folders(const QString& game, const QStringList& list); static void update_archives(const QString& game, const QStringList& list); - void initialize(int manager_version, QProgressDialog* dlg = nullptr); + void initialize(int& manager_version, QProgressDialog* dlg = nullptr); + void update(int& manager_version, QProgressDialog* dlg = nullptr); + void save(); void load(); void reset_archive_handles(); + struct GameInfo + { + GameMode id = OTHER; + QString name; + QString path; + bool status = false; + }; + private: QMutex mutex; diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index ddaf29a80..ac5f6de2a 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -536,7 +536,6 @@ SettingsResources::SettingsResources( QWidget * parent ) : ui->archivesList->setModel( archives ); // TODO: Hide for new asset manager for now - ui->btnFolderAutoDetect->setHidden( true ); ui->btnAutoDetectGames->setHidden( true ); for ( int i = 0; i < Game::NUM_GAMES; i++ ) { @@ -785,6 +784,9 @@ void SettingsResources::on_btnFolderAdd_clicked() if ( dialog.exec() ) path = dialog.selectedFiles().at( 0 ); + if ( path.isEmpty() ) + return; + folders->insertRow( 0 ); folders->setData( folders->index( 0, 0 ), path ); ui->foldersList->setCurrentIndex( folders->index( 0, 0 ) ); @@ -811,7 +813,20 @@ void SettingsResources::on_btnFolderUp_clicked() void SettingsResources::on_btnFolderAutoDetect_clicked() { + QStringList folders_list = folders->stringList(); + for ( const auto& f : GameManager::get_existing_folders_list(currentFolderItem()) ) { + if ( folders_list.contains(f, Qt::CaseInsensitive) ) + continue; + folders_list << f; + } + folders_list.removeDuplicates(); + + folders->setStringList(folders_list); + + ui->foldersList->setCurrentIndex(folders->index(0, 0)); + + modifyPane(); } void SettingsResources::on_btnArchiveAdd_clicked() From b35cae54e2d96c1309f972d2ed507e985e6b8c50 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Wed, 28 Aug 2019 19:29:26 -0400 Subject: [PATCH 032/118] Prevent Settings crash when no games were found --- src/ui/settingspane.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index ac5f6de2a..d4041a3e8 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -731,11 +731,15 @@ void SettingsResources::onBrowseClicked() QString SettingsResources::currentFolderItem() { + if ( !ui->foldersGameList->currentItem() ) + return {}; return ui->foldersGameList->currentItem()->text(); } QString SettingsResources::currentArchiveItem() { + if ( !ui->archivesGameList->currentItem() ) + return {}; return ui->archivesGameList->currentItem()->text(); } From a76b206cb65bfb991222bc1f3804e11cec35b79d Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 3 Sep 2019 08:23:52 -0400 Subject: [PATCH 033/118] Update Game Manager Name simplification, general cleanup. --- src/gamemanager.cpp | 288 ++++++++++++++++------------------------ src/gamemanager.h | 191 ++++++++++++++++++++------ src/gl/glscene.cpp | 2 +- src/gl/gltex.cpp | 4 +- src/io/material.cpp | 4 +- src/ui/settingspane.cpp | 166 +++++++++++------------ src/ui/settingspane.h | 4 + 7 files changed, 354 insertions(+), 305 deletions(-) diff --git a/src/gamemanager.cpp b/src/gamemanager.cpp index c85c6cc05..34f339993 100644 --- a/src/gamemanager.cpp +++ b/src/gamemanager.cpp @@ -11,6 +11,14 @@ namespace Game { +static const auto GAME_PATHS = QString("Game Paths"); +static const auto GAME_FOLDERS = QString("Game Folders"); +static const auto GAME_ARCHIVES = QString("Game Archives"); +static const auto GAME_STATUS = QString("Game Status"); +static const auto GAME_MGR_VER = QString("Game Manager Version"); + +static const QStringList ARCHIVE_EXT{"*.bsa", "*.ba2"}; + QString registry_game_path( const QString& key ) { #ifdef Q_OS_WIN32 @@ -41,8 +49,8 @@ QStringList archives_list( const QString& path, const QString& data_dir, const Q QDir path_dir(path); if ( path_dir.exists(data_dir) ) path_dir.cd(data_dir); - for ( const auto& finfo : path_dir.entryInfoList({"*.bsa", "*.ba2"}, QDir::Files) ) - list << finfo.absoluteFilePath(); + for ( const auto& finfo : path_dir.entryInfoList(ARCHIVE_EXT, QDir::Files) ) + list.append(finfo.absoluteFilePath()); if ( folder.isEmpty() ) return list; @@ -65,7 +73,7 @@ QStringList existing_folders(GameMode game, QString path) for ( const auto& f : FOLDERS.value(game, {}) ) { QDir dir(QString("%1/%2").arg(path).arg(DATA.value(game, ""))); if ( dir.exists(f) ) - folders << QFileInfo(dir, f).absoluteFilePath(); + folders.append(QFileInfo(dir, f).absoluteFilePath()); } return folders; @@ -112,23 +120,23 @@ void process( QProgressDialog* dlg, int i ) GameManager::GameManager() { QSettings settings; - int manager_version = settings.value("Game Manager Version", 0).toInt(); + int manager_version = settings.value(GAME_MGR_VER, 0).toInt(); if ( manager_version == 0 ) { auto dlg = prog_dialog("Initializing the Game Manager"); // Initial game manager settings - initialize(manager_version, dlg); + init_settings(manager_version, dlg); dlg->close(); } if ( manager_version == 1 ) { - update(manager_version); + update_settings(manager_version); } load(); - reset_archive_handles(); + load_archives(); } -GameMode GameManager::GetGame( uint32_t version, uint32_t user, uint32_t bsver ) +GameMode GameManager::get_game( uint32_t version, uint32_t user, uint32_t bsver ) { switch ( bsver ) { case 0: @@ -177,31 +185,16 @@ GameMode GameManager::GetGame( uint32_t version, uint32_t user, uint32_t bsver ) return OTHER; } -static GameManager * g_game_manager = nullptr; - -GameManager * GameManager::get() +GameManager* GameManager::get() { - if ( !g_game_manager ) - g_game_manager = new GameManager(); - return g_game_manager; -} - -void GameManager::del() -{ - if ( g_game_manager ) { - delete g_game_manager; - g_game_manager = nullptr; - } + static auto instance{new GameManager{}}; + return instance; } -void GameManager::initialize( int& manager_version, QProgressDialog* dlg ) +void GameManager::init_settings( int& manager_version, QProgressDialog* dlg ) const { QSettings settings; - QMap paths; - QMap folders; - QMap archives; - QMap status; - + QVariantMap paths, folders, archives, status; for ( int g = 0; g < NUM_GAMES; g++ ) { process(dlg, g); auto game = get_game_info(GameMode(g)); @@ -223,17 +216,17 @@ void GameManager::initialize( int& manager_version, QProgressDialog* dlg ) status.insert(game.name, !game.path.isEmpty()); } - settings.setValue("Game Paths", paths); - settings.setValue("Game Folders", folders); - settings.setValue("Game Archives", archives); - settings.setValue("Game Status", status); - settings.setValue("Game Manager Version", ++manager_version); + settings.setValue(GAME_PATHS, paths); + settings.setValue(GAME_FOLDERS, folders); + settings.setValue(GAME_ARCHIVES, archives); + settings.setValue(GAME_STATUS, status); + settings.setValue(GAME_MGR_VER, ++manager_version); } -void GameManager::update( int& manager_version, QProgressDialog * dlg ) +void GameManager::update_settings( int& manager_version, QProgressDialog * dlg ) const { QSettings settings; - QMap folders; + QVariantMap folders; for ( int g = 0; g < NUM_GAMES; g++ ) { process(dlg, g); @@ -246,20 +239,20 @@ void GameManager::update( int& manager_version, QProgressDialog * dlg ) } if ( manager_version == 1 ) { - settings.setValue("Game Folders", folders); + settings.setValue(GAME_FOLDERS, folders); manager_version++; } - settings.setValue("Game Manager Version", manager_version); + settings.setValue(GAME_MGR_VER, manager_version); } -QList GameManager::get_archive_handles( Game::GameMode game ) +QList GameManager::opened_archives( const GameMode game ) { - if ( get()->get_game_status(game) == false ) + if ( !status(game) ) return {}; QList archives; - for ( std::shared_ptr an : get()->handles.value(game) ) { + for ( const auto an : get()->handles.value(game) ) { archives.append(an->getArchive()); } return archives; @@ -267,110 +260,68 @@ QList GameManager::get_archive_handles( Game::GameMode game ) bool GameManager::archive_contains_folder( const QString& archive, const QString& folder ) { - bool contains = false; if ( BSA::canOpen(archive) ) { - BSA * bsa = new BSA(archive); - if ( bsa && bsa->open() ) { - contains = bsa->getRootFolder()->children.contains(folder.toLower()); - } - delete bsa; + auto bsa = std::make_unique(archive); + if ( bsa && bsa->open() ) + return bsa->getRootFolder()->children.contains(folder.toLower()); } - return contains; + return false; } -void GameManager::reset_archive_handles() -{ - handles.clear(); - for ( const auto ar : game_archives.toStdMap() ) { - for ( const auto an : ar.second ) { - // Skip loading of archives for disabled games - if ( game_status.value(ar.first, false) == false ) - continue; - if ( auto a = FSArchiveHandler::openArchive(an) ) - handles[ar.first].append(a); - } - } -} - -QString GameManager::get_game_path( const GameMode game ) +QString GameManager::path( const GameMode game ) { return get()->game_paths.value(game, {}); } -QString GameManager::get_game_path( const QString& game ) -{ - return get()->game_paths.value(ModeForString(game), {}); -} - -QString GameManager::get_data_path( const GameMode game ) -{ - return get_game_path(game) + "/" + DATA[game]; -} - -QString GameManager::get_data_path( const QString& game ) +QString GameManager::data( const GameMode game ) { - return get_data_path(ModeForString(game)); + return path(game) + "/" + DATA[game]; } -QStringList GameManager::get_folder_list( const GameMode game ) +QStringList GameManager::folders( const GameMode game ) { if ( game == FALLOUT_3NV ) - return get_folder_list(FALLOUT_NV) + get_folder_list(FALLOUT_3); - if ( get_game_status(game) ) + return folders(FALLOUT_NV) + folders(FALLOUT_3); + if ( status(game) ) return get()->game_folders.value(game, {}); return {}; } -QStringList GameManager::get_folder_list( const QString& game ) -{ - return get_folder_list(ModeForString(game)); -} -QStringList GameManager::get_archive_list( const GameMode game ) +QStringList GameManager::archives( const GameMode game ) { if ( game == FALLOUT_3NV ) - return get_archive_list(FALLOUT_NV) + get_archive_list(FALLOUT_3); - if ( get_game_status(game) ) + return archives(FALLOUT_NV) + archives(FALLOUT_3); + if ( status(game) ) return get()->game_archives.value(game, {}); return {}; } - -QStringList GameManager::get_archive_list( const QString& game ) +bool GameManager::status(const GameMode game) { - return get_archive_list(ModeForString(game)); + if ( game == FALLOUT_3NV ) + return status(FALLOUT_3) || status(FALLOUT_NV); + return get()->game_status.value(game, false); } -QStringList GameManager::get_existing_folders_list(const GameMode game) +QStringList GameManager::find_folders(const GameMode game) { return existing_folders(game, get()->game_paths.value(game, {})); } -QStringList GameManager::get_existing_folders_list(const QString & game) -{ - return get_existing_folders_list(ModeForString(game)); -} - -QStringList GameManager::get_archive_file_list( const GameMode game ) +QStringList GameManager::find_archives( const GameMode game ) { - QDir data_dir = QDir(GameManager::get_data_path(game)); + QDir data_dir = QDir(GameManager::data(game)); if ( !data_dir.exists() ) return {}; - QStringList data_archives = data_dir.entryList({"*.bsa", "*.ba2"}, QDir::Files); QStringList archive_paths; - for ( const auto& a : data_archives ) { - archive_paths << data_dir.absoluteFilePath(a); - } + for ( const auto& a : data_dir.entryList(ARCHIVE_EXT, QDir::Files) ) + archive_paths.append(data_dir.absoluteFilePath(a)); return archive_paths; } -QStringList GameManager::get_archive_file_list( const QString& game ) -{ - return get_archive_file_list(ModeForString(game)); -} - -QStringList GameManager::get_filtered_archives_list( const QStringList& list, const QString& folder ) +QStringList GameManager::filter_archives( const QStringList& list, const QString& folder ) { if ( folder.isEmpty() ) return list; @@ -383,52 +334,10 @@ QStringList GameManager::get_filtered_archives_list( const QStringList& list, co return filtered; } -void GameManager::set_game_status( const GameMode game, bool status ) -{ - get()->game_status[game] = status; -} - -void GameManager::set_game_status( const QString& game, bool status ) -{ - set_game_status(ModeForString(game), status); -} - -bool GameManager::get_game_status( const GameMode game ) -{ - if ( game == FALLOUT_3NV ) - return get_game_status(FALLOUT_3) || get_game_status(FALLOUT_NV); - return get()->game_status.value(game, false); -} - -bool GameManager::get_game_status( const QString& game ) -{ - return get_game_status(ModeForString(game)); -} - -void GameManager::update_game( const QString& game, const QString& path ) -{ - get()->game_paths.insert(ModeForString(game), path); -} - -void GameManager::update_folders( const QString& game, const QStringList& list ) -{ - get()->game_folders.insert(ModeForString(game), list); -} - -void GameManager::update_archives( const QString& game, const QStringList& list ) -{ - get()->game_archives.insert(ModeForString(game), list); -} - -void GameManager::save() +void GameManager::save() const { QSettings settings; - - QMap paths; - QMap folders; - QMap archives; - QMap status; - + QVariantMap paths, folders, archives, status; for ( const auto& p : game_paths.toStdMap() ) paths.insert(StringForMode(p.first), p.second); @@ -441,39 +350,76 @@ void GameManager::save() for ( const auto& s : game_status.toStdMap() ) status.insert(StringForMode(s.first), s.second); - settings.setValue("Game Paths", paths); - settings.setValue("Game Folders", folders); - settings.setValue("Game Archives", archives); - settings.setValue("Game Status", status); + settings.setValue(GAME_PATHS, paths); + settings.setValue(GAME_FOLDERS, folders); + settings.setValue(GAME_ARCHIVES, archives); + settings.setValue(GAME_STATUS, status); } void GameManager::load() { + QMutexLocker locker(&mutex); QSettings settings; - - game_paths.clear(); - auto paths = settings.value("Game Paths").toMap().toStdMap(); - for ( const auto& p : paths ) { + for ( const auto& p : settings.value(GAME_PATHS).toMap().toStdMap() ) game_paths[ModeForString(p.first)] = p.second.toString(); - } - - game_folders.clear(); - auto fol = settings.value("Game Folders").toMap().toStdMap(); - for ( const auto& f : fol ) { + + for ( const auto& f : settings.value(GAME_FOLDERS).toMap().toStdMap() ) game_folders[ModeForString(f.first)] = f.second.toStringList(); - } - game_archives.clear(); - auto arc = settings.value("Game Archives").toMap().toStdMap(); - for ( const auto& a : arc ) { + for ( const auto& a : settings.value(GAME_ARCHIVES).toMap().toStdMap() ) game_archives[ModeForString(a.first)] = a.second.toStringList(); - } - - game_status.clear(); - auto stat = settings.value("Game Status").toMap().toStdMap(); - for ( const auto& s : stat ) { + + for ( const auto& s : settings.value(GAME_STATUS).toMap().toStdMap() ) game_status[ModeForString(s.first)] = s.second.toBool(); +} + +void GameManager::load_archives() +{ + QMutexLocker locker(&mutex); + // Reset the currently open archive handles + handles.clear(); + for ( const auto ar : game_archives.toStdMap() ) { + for ( const auto an : ar.second ) { + // Skip loading of archives for disabled games + if ( game_status.value(ar.first, false) == false ) + continue; + if ( auto a = FSArchiveHandler::openArchive(an) ) + handles[ar.first].append(a); + } } } +void GameManager::clear() +{ + QMutexLocker locker(&mutex); + game_paths.clear(); + game_folders.clear(); + game_archives.clear(); + game_status.clear(); +} + +void GameManager::insert_game( const GameMode game, const QString& path ) +{ + QMutexLocker locker(&mutex); + game_paths.insert(game, path); +} + +void GameManager::insert_folders( const GameMode game, const QStringList& list ) +{ + QMutexLocker locker(&mutex); + game_folders.insert(game, list); +} + +void GameManager::insert_archives( const GameMode game, const QStringList& list ) +{ + QMutexLocker locker(&mutex); + game_archives.insert(game, list); +} + +void GameManager::insert_status( const GameMode game, bool status ) +{ + QMutexLocker locker(&mutex); + game_status.insert(game, status); +} + } // end namespace Game diff --git a/src/gamemanager.h b/src/gamemanager.h index d289a7be6..54b69d787 100644 --- a/src/gamemanager.h +++ b/src/gamemanager.h @@ -38,7 +38,7 @@ constexpr uint32_t VersionInt( uint64_t version ) return (uint32_t)version; } -enum GameMode +enum GameMode : int { OTHER, MORROWIND, @@ -53,7 +53,8 @@ enum GameMode NUM_GAMES, // Not a physical game, exclude from NUM_GAMES count - FALLOUT_3NV, + // Fallout 3 and Fallout NV cannot be differentiated by version + FALLOUT_3NV, }; using GameMap = QMap; @@ -62,11 +63,11 @@ using ResourceListMap = QMap; using namespace std::string_literals; -static auto beth = QString("HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\%1"); -static auto msft = QString("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1"); +static const auto beth = QString("HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\%1"); +static const auto msft = QString("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1"); -static GameMap STRING = { +static const GameMap STRING = { {MORROWIND, "Morrowind"}, {OBLIVION, "Oblivion"}, {FALLOUT_3, "Fallout 3"}, @@ -78,7 +79,7 @@ static GameMap STRING = { {OTHER, "Other Games"} }; -static GameMap KEY = { +static const GameMap KEY = { {MORROWIND, beth.arg("Morrowind")}, {OBLIVION, beth.arg("Oblivion")}, {FALLOUT_3, beth.arg("Fallout3")}, @@ -90,7 +91,7 @@ static GameMap KEY = { {OTHER, ""} }; -static GameMap DATA = { +static const GameMap DATA = { {MORROWIND, "Data Files"}, {OBLIVION, "Data"}, {FALLOUT_3, "Data"}, @@ -102,7 +103,7 @@ static GameMap DATA = { {OTHER, ""} }; -static ResourceListMap FOLDERS = { +static const ResourceListMap FOLDERS = { {MORROWIND, {"."}}, {OBLIVION, {"."}}, {FALLOUT_3, {"."}}, @@ -204,47 +205,71 @@ GameMode ModeForString(QString game); class GameManager { -public: GameManager(); +public: + GameManager(const GameManager&) = delete; + GameManager& operator= (const GameManager) = delete; - static GameMode GetGame(uint32_t version, uint32_t user, uint32_t bsver); + static GameMode get_game(uint32_t version, uint32_t user, uint32_t bsver); - static GameManager * get(); - static void del(); + static GameManager* get(); - static QList get_archive_handles(Game::GameMode game); + static QList opened_archives(const GameMode game); static bool archive_contains_folder(const QString& archive, const QString& folder); - static QString get_game_path(const GameMode game); - static QString get_game_path(const QString& game); - static QString get_data_path(const GameMode game); - static QString get_data_path(const QString& game); - static QStringList get_folder_list(const GameMode game); - static QStringList get_folder_list(const QString& game); - static QStringList get_archive_list(const GameMode game); - static QStringList get_archive_list(const QString& game); - - static QStringList get_existing_folders_list(const GameMode game); - static QStringList get_existing_folders_list(const QString& game); - static QStringList get_archive_file_list(const GameMode game); - static QStringList get_archive_file_list(const QString& game); - static QStringList get_filtered_archives_list(const QStringList& list, const QString& folder); - - static void set_game_status(const GameMode game, bool status); - static void set_game_status(const QString& game, bool status); - static bool get_game_status(const GameMode game); - static bool get_game_status(const QString& game); - - static void update_game(const QString& game, const QString& path); - static void update_folders(const QString& game, const QStringList& list); - static void update_archives(const QString& game, const QStringList& list); - - void initialize(int& manager_version, QProgressDialog* dlg = nullptr); - void update(int& manager_version, QProgressDialog* dlg = nullptr); - - void save(); + //! Game installation path + static QString path(const GameMode game); + //! Game data path + static QString data(const GameMode game); + //! Game folders managed by the GameManager + static QStringList folders(const GameMode game); + //! Game archives managed by the GameManager + static QStringList archives(const GameMode game); + //! Game enabled status in the GameManager + static bool status(const GameMode game); + + //! Find applicable data folders at the game installation path + static QStringList find_folders(const GameMode game); + //! Find applicable archives at the game installation path + static QStringList find_archives(const GameMode game); + //! Given an archive list, returns a sublist of archives that contain the given folder + static QStringList filter_archives(const QStringList& list, const QString& folder); + + //! Game installation path + static inline QString path(const QString& game); + //! Game data path + static inline QString data(const QString& game); + //! Game folders managed by the GameManager + static inline QStringList folders(const QString& game); + //! Game archives managed by the GameManager + static inline QStringList archives(const QString& game); + //! Game enabled status in the GameManager + static inline bool status(const QString& game); + //! Find applicable data folders at the game installation path + static inline QStringList find_folders(const QString& game); + //! Find applicable archives at the game installation path + static inline QStringList find_archives(const QString& game); + + static inline void update_game(const GameMode game, const QString& path); + static inline void update_game(const QString& game, const QString& path); + static inline void update_folders(const GameMode game, const QStringList& list); + static inline void update_folders(const QString& game, const QStringList& list); + static inline void update_archives(const GameMode game, const QStringList& list); + static inline void update_archives(const QString& game, const QStringList& list); + static inline void update_status(const GameMode game, bool status); + static inline void update_status(const QString& game, bool status); + + void init_settings(int& manager_version, QProgressDialog* dlg = nullptr) const; + void update_settings(int& manager_version, QProgressDialog* dlg = nullptr) const; + + //! Save the manager to settings + void save() const; + //! Load the manager from settings void load(); - void reset_archive_handles(); + //! Load the managed archives + void load_archives(); + //! Reset the manager + void clear(); struct GameInfo { @@ -255,7 +280,12 @@ class GameManager }; private: - QMutex mutex; + void insert_game(const GameMode game, const QString& path); + void insert_folders(const GameMode game, const QStringList& list); + void insert_archives(const GameMode game, const QStringList& list); + void insert_status(const GameMode game, bool status); + + mutable QMutex mutex; GameMap game_paths; GameEnabledMap game_status; @@ -265,6 +295,81 @@ class GameManager QMap>> handles; }; +QString GameManager::path(const QString& game) +{ + return path(ModeForString(game)); +} + +QString GameManager::data(const QString& game) +{ + return data(ModeForString(game)); +} + +QStringList GameManager::folders(const QString& game) +{ + return folders(ModeForString(game)); +} + +QStringList GameManager::archives(const QString& game) +{ + return archives(ModeForString(game)); +} + +bool GameManager::status(const QString& game) +{ + return status(ModeForString(game)); +} + +QStringList GameManager::find_folders(const QString & game) +{ + return find_folders(ModeForString(game)); +} + +QStringList GameManager::find_archives(const QString& game) +{ + return find_archives(ModeForString(game)); +} + +void GameManager::update_game(const GameMode game, const QString & path) +{ + get()->insert_game(game, path); +} + +void GameManager::update_game(const QString& game, const QString& path) +{ + update_game(ModeForString(game), path); +} + +void GameManager::update_folders(const GameMode game, const QStringList & list) +{ + get()->insert_folders(game, list); +} + +void GameManager::update_folders(const QString& game, const QStringList& list) +{ + update_folders(ModeForString(game), list); +} + +void GameManager::update_archives(const GameMode game, const QStringList & list) +{ + get()->insert_archives(game, list); +} + +void GameManager::update_archives(const QString& game, const QStringList& list) +{ + update_archives(ModeForString(game), list); +} + +void GameManager::update_status(const GameMode game, bool status) +{ + get()->insert_status(game, status); +} + +void GameManager::update_status(const QString& game, bool status) +{ + update_status(ModeForString(game), status); +} + } // end namespace Game #endif // GAMEMANAGER_H diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 97fda83be..72c4517ec 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -215,7 +215,7 @@ void Scene::make( NifModel * nif, bool flushTextures ) if ( !nif ) return; - game = Game::GameManager::GetGame(nif->getVersionNumber(), nif->getUserVersion(), nif->getUserVersion2()); + game = Game::GameManager::get_game(nif->getVersionNumber(), nif->getUserVersion(), nif->getUserVersion2()); if ( game == Game::FALLOUT_76 ) emit disableSave(); diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 372ffeb53..0c58187f0 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -213,7 +213,7 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray return dir.filePath( filename ); } - for ( QString folder : Game::GameManager::get_folder_list(game) ) { + for ( QString folder : Game::GameManager::folders(game) ) { // TODO: Always search nifdir without requiring a relative entry // in folders? Not too intuitive to require ".\" in your texture folder list // even if it is added by default. @@ -231,7 +231,7 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray } // Search through archives last, and load any requested textures into memory. - for ( FSArchiveFile * archive : Game::GameManager::get_archive_handles(game) ) { + for ( FSArchiveFile * archive : Game::GameManager::opened_archives(game) ) { if ( archive ) { filename = QDir::fromNativeSeparators( filename.toLower() ); if ( archive->hasFile( filename ) ) { diff --git a/src/io/material.cpp b/src/io/material.cpp index e8426e493..9e097a9ac 100644 --- a/src/io/material.cpp +++ b/src/io/material.cpp @@ -114,7 +114,7 @@ QByteArray Material::find( QString path, Game::GameMode game ) QString filename; QDir dir; - for ( QString folder : Game::GameManager::get_folder_list(game) ) { + for ( QString folder : Game::GameManager::folders(game) ) { dir.setPath( folder ); if ( dir.exists( path ) ) { @@ -126,7 +126,7 @@ QByteArray Material::find( QString path, Game::GameMode game ) } } - for ( FSArchiveFile * archive : Game::GameManager::get_archive_handles(game) ) { + for ( FSArchiveFile * archive : Game::GameManager::opened_archives(game) ) { if ( archive ) { filename = QDir::fromNativeSeparators( path.toLower() ); if ( archive->hasFile( filename ) ) { diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index d4041a3e8..da2ae3e5a 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -538,47 +538,8 @@ SettingsResources::SettingsResources( QWidget * parent ) : // TODO: Hide for new asset manager for now ui->btnAutoDetectGames->setHidden( true ); - for ( int i = 0; i < Game::NUM_GAMES; i++ ) { - // Get each widget for game slot `i` - auto lbl = findChild(lblGame.arg(i)); - auto chk = findChild(chkGame.arg(i)); - auto edt = findChild(edtGame.arg(i)); - auto btn = findChild(btnBrowse.arg(i)); - - auto game = Game::GameMode(i); - auto game_string = Game::StringForMode(game); - if ( game_string.isEmpty() ) - continue; - - // Rename generic `GAME_N` label to game name - lbl->setText(game_string); - - // Sync line edit with game install path if it exists - auto path_string = GameManager::get_game_path(game); - if ( !path_string.isNull() ) - edt->setText(path_string); - - // Sync Enabled checkbox - chk->setChecked(GameManager::get_game_status(game)); - - auto folder_item = ui->foldersGameList->findItems(QString("GAME_%1").arg(i), Qt::MatchExactly).value(0, nullptr); - auto archive_item = ui->archivesGameList->findItems(QString("GAME_%1").arg(i), Qt::MatchExactly).value(0, nullptr); - //Q_ASSERT(folder_item && archive_item); - folder_item->setText(game_string); - folder_item->setHidden(!chk->isChecked()); - archive_item->setText(game_string); - archive_item->setHidden(!chk->isChecked()); - - connect( btn, &QPushButton::clicked, this, &SettingsResources::onBrowseClicked ); - - connect( chk, &QCheckBox::clicked, this, &SettingsPane::modifyPane ); - - // Hide game items from Folders and Archives lists when game is disabled - connect( chk, &QCheckBox::clicked, [chk, folder_item, archive_item](){ - folder_item->setHidden(!chk->isChecked()); - archive_item->setHidden(!chk->isChecked()); - } ); - } + // Populate UI from the GameManager + manager_sync(true); connect( ui->foldersList, &QListView::doubleClicked, this, &SettingsPane::modifyPane ); connect( ui->chkAlternateExt, &QCheckBox::clicked, this, &SettingsPane::modifyPane ); @@ -623,32 +584,11 @@ void SettingsResources::read() { QSettings settings; - auto mgr = GameManager::get(); - - mgr->load(); - - for ( int i = 0; i < Game::NUM_GAMES; i++ ) { - // Get each checkbox for game slot `i` - auto chk = findChild(chkGame.arg(i)); - auto game = Game::GameMode(i); - chk->setChecked(mgr->get_game_status(game)); - } + GameManager::get()->load(); - for ( int i = 0; i < ui->foldersGameList->count(); i++ ) { - auto item = ui->foldersGameList->item(i); - if ( !item->isHidden() ) { - ui->foldersGameList->setCurrentRow(i); - break; - } - } + select_first(ui->foldersGameList); + select_first(ui->archivesGameList); - for ( int i = 0; i < ui->archivesGameList->count(); i++ ) { - auto item = ui->archivesGameList->item(i); - if ( !item->isHidden() ) { - ui->archivesGameList->setCurrentRow(i); - break; - } - } setFolderList(); setArchiveList(); @@ -663,18 +603,8 @@ void SettingsResources::write() return; auto mgr = GameManager::get(); - - for ( int i = 0; i < Game::NUM_GAMES; i++ ) { - // Get each checkbox for game slot `i` - auto chk = findChild(chkGame.arg(i)); - auto game = Game::GameMode(i); - mgr->set_game_status(game, chk->isChecked()); - } - mgr->save(); - - // Reload the currently open archive handles - mgr->reset_archive_handles(); + mgr->load_archives(); QSettings settings; settings.setValue( "Settings/Resources/Alternate Extensions", ui->chkAlternateExt->isChecked() ); @@ -684,6 +614,59 @@ void SettingsResources::write() emit dlg->flush3D(); } +void SettingsResources::manager_sync( bool make_connections ) +{ + for ( int i = 0; i < Game::NUM_GAMES; i++ ) { + // Get each widget for game slot `i` + auto lbl = findChild(lblGame.arg(i)); + auto chk = findChild(chkGame.arg(i)); + auto edt = findChild(edtGame.arg(i)); + auto btn = findChild(btnBrowse.arg(i)); + + auto game = Game::GameMode(i); + auto game_string = Game::StringForMode(game); + if ( game_string.isEmpty() || !lbl || !chk || !edt || !btn ) + continue; + + // Rename generic `GAME_N` label to game name + lbl->setText(game_string); + + // Sync line edit with game install path if it exists + auto path_string = GameManager::path(game); + if ( !path_string.isNull() ) + edt->setText(path_string); + + // Sync Enabled checkbox + chk->setChecked(GameManager::status(game)); + + // Rename all GAME_%1 list items to the supported game at that position + auto game_txt = QString("GAME_%1"); + auto folder_item = ui->foldersGameList->findItems(game_txt.arg(i), Qt::MatchExactly).value(0, nullptr); + auto archive_item = ui->archivesGameList->findItems(game_txt.arg(i), Qt::MatchExactly).value(0, nullptr); + if ( folder_item && archive_item ) { + folder_item->setText(game_string); + archive_item->setText(game_string); + // Hide game items from Folders and Archives lists when game is disabled + folder_item->setHidden(!chk->isChecked()); + archive_item->setHidden(!chk->isChecked()); + if ( make_connections ) { + connect(chk, &QCheckBox::clicked, [chk, folder_item, archive_item]() { + folder_item->setHidden(!chk->isChecked()); + archive_item->setHidden(!chk->isChecked()); + }); + } + } + + if ( make_connections ) { + connect(btn, &QPushButton::clicked, this, &SettingsResources::onBrowseClicked); + connect(chk, &QCheckBox::clicked, this, &SettingsPane::modifyPane); + connect(chk, &QCheckBox::clicked, [chk, game]() { + GameManager::update_status(game, chk->isChecked()); + }); + } + } +} + void SettingsResources::setDefault() { read(); @@ -691,16 +674,27 @@ void SettingsResources::setDefault() void SettingsResources::setFolderList() { - folders->setStringList(GameManager::get_folder_list(currentFolderItem())); + folders->setStringList(GameManager::folders(currentFolderItem())); ui->foldersList->setCurrentIndex( folders->index( 0, 0 ) ); } void SettingsResources::setArchiveList() { - archives->setStringList(GameManager::get_archive_list(currentArchiveItem())); + archives->setStringList(GameManager::archives(currentArchiveItem())); ui->archivesList->setCurrentIndex( archives->index( 0, 0 ) ); } +void SettingsResources::select_first( QListWidget * list ) +{ + for ( int i = 0; i < list->count(); i++ ) { + // Select the first visible item + if ( !list->item(i)->isHidden() ) { + list->setCurrentRow(i); + break; + } + } +} + void SettingsResources::onBrowseClicked() { QPushButton * btn = qobject_cast(sender()); @@ -782,7 +776,7 @@ void SettingsResources::on_btnFolderAdd_clicked() { QFileDialog dialog( this ); dialog.setFileMode( QFileDialog::Directory ); - dialog.setDirectory(GameManager::get_data_path(currentFolderItem())); + dialog.setDirectory(GameManager::data(currentFolderItem())); QString path; if ( dialog.exec() ) @@ -818,7 +812,7 @@ void SettingsResources::on_btnFolderUp_clicked() void SettingsResources::on_btnFolderAutoDetect_clicked() { QStringList folders_list = folders->stringList(); - for ( const auto& f : GameManager::get_existing_folders_list(currentFolderItem()) ) { + for ( const auto& f : GameManager::find_folders(currentFolderItem()) ) { if ( folders_list.contains(f, Qt::CaseInsensitive) ) continue; folders_list << f; @@ -838,14 +832,14 @@ void SettingsResources::on_btnArchiveAdd_clicked() QStringList files = QFileDialog::getOpenFileNames( this, "Select one or more archives", - GameManager::get_data_path(currentArchiveItem()), + GameManager::data(currentArchiveItem()), "Archive (*.bsa *.ba2)" ); QStringList filtered; - filtered += GameManager::get_filtered_archives_list( files, "materials" ); - filtered += GameManager::get_filtered_archives_list( files, "textures" ); + filtered += GameManager::filter_archives( files, "materials" ); + filtered += GameManager::filter_archives( files, "textures" ); filtered.removeDuplicates(); for ( int i = 0; i < filtered.count(); i++ ) { @@ -878,16 +872,16 @@ void SettingsResources::on_btnArchiveUp_clicked() void SettingsResources::on_btnArchiveAutoDetect_clicked() { QStringList archives_list = archives->stringList(); - QStringList data_archives = GameManager::get_archive_file_list(currentArchiveItem()); + QStringList data_archives = GameManager::find_archives(currentArchiveItem()); QStringList new_archives; - for ( const auto& a : GameManager::get_filtered_archives_list(data_archives, "materials") ) { + for ( const auto& a : GameManager::filter_archives(data_archives, "materials") ) { if ( archives_list.contains(a, Qt::CaseInsensitive) ) continue; archives_list << a; } - for ( const auto& a : GameManager::get_filtered_archives_list(data_archives, "textures") ) { + for ( const auto& a : GameManager::filter_archives(data_archives, "textures") ) { if ( archives_list.contains(a, Qt::CaseInsensitive) ) continue; archives_list << a; diff --git a/src/ui/settingspane.h b/src/ui/settingspane.h index 1d98eaacb..b06485b5c 100644 --- a/src/ui/settingspane.h +++ b/src/ui/settingspane.h @@ -12,6 +12,7 @@ class FSManager; class SettingsDialog; class QStringListModel; +class QListWidget; namespace Ui { class SettingsGeneral; @@ -91,6 +92,9 @@ class SettingsResources : public SettingsPane void read() override final; void write() override final; void setDefault() override final; + + void select_first(QListWidget* list); + void manager_sync(bool make_connections = false); public slots: void modifyPane() override; From 62926abd99a9c2cfd028d9e1ddd2b09dd58800c6 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Tue, 3 Sep 2019 08:24:39 -0400 Subject: [PATCH 034/118] Update UV Editor for Game Manager --- src/ui/widgets/uvedit.cpp | 25 +++++++++++++++---------- src/ui/widgets/uvedit.h | 11 +++++++++-- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 55d0142e4..d6df9591d 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -32,6 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "uvedit.h" +#include "gamemanager.h" #include "message.h" #include "nifskope.h" #include "gl/gltex.h" @@ -124,6 +125,8 @@ UVWidget::UVWidget( QWidget * parent ) mousePos = QPoint( -1000, -1000 ); + game = Game::OTHER; + setCursor( QCursor( Qt::CrossCursor ) ); setMouseTracking( true ); @@ -224,9 +227,9 @@ void UVWidget::initializeGL() qglClearColor( cfg.background ); if ( !texfile.isEmpty() ) - bindTexture( texfile ); + bindTexture( texfile, game ); else - bindTexture( texsource ); + bindTexture( texsource, game ); glEnableClientState( GL_VERTEX_ARRAY ); glVertexPointer( 2, GL_SHORT, 0, vertArray ); @@ -280,9 +283,9 @@ void UVWidget::paintGL() glDisable( GL_BLEND ); if ( !texfile.isEmpty() ) - bindTexture( texfile ); + bindTexture( texfile, game ); else - bindTexture( texsource ); + bindTexture( texsource, game ); glTranslatef( -0.5f, -0.5f, 0.0f ); @@ -528,10 +531,10 @@ QVector UVWidget::indices( const QRegion & region ) const return hits.toVector(); } -bool UVWidget::bindTexture( const QString & filename ) +bool UVWidget::bindTexture( const QString & filename, const Game::GameMode game ) { GLuint mipmaps = 0; - mipmaps = textures->bind( filename ); + mipmaps = textures->bind( filename, game ); if ( mipmaps ) { glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); @@ -550,10 +553,10 @@ bool UVWidget::bindTexture( const QString & filename ) return false; } -bool UVWidget::bindTexture( const QModelIndex & iSource ) +bool UVWidget::bindTexture( const QModelIndex & iSource, const Game::GameMode game ) { GLuint mipmaps = 0; - mipmaps = textures->bind( iSource ); + mipmaps = textures->bind( iSource, game ); if ( mipmaps ) { glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); @@ -800,6 +803,8 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) iShape = nifIndex; isDataOnSkin = false; + game = Game::GameManager::get_game(nif->getVersionNumber(), nif->getUserVersion(), nif->getUserVersion2()); + // Version dependent actions if ( nif && nif->getVersionNumber() != 0x14020007 ) { coordSetGroup = new QActionGroup( this ); @@ -929,7 +934,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) QModelIndex iTextures = nif->getIndex( iTexSource, "Textures" ); if ( iTextures.isValid() ) { - texfile = TexCache::find( nif->get( iTextures.child( 0, 0 ) ), nif->getFolder() ); + texfile = TexCache::find( nif->get( iTextures.child( 0, 0 ) ), nif->getFolder(), game ); return true; } } @@ -940,7 +945,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) QString texture = nif->get( iTexProp, "Source Texture" ); if ( !texture.isEmpty() ) { - texfile = TexCache::find( texture, nif->getFolder() ); + texfile = TexCache::find( texture, nif->getFolder(), game ); return true; } } diff --git a/src/ui/widgets/uvedit.h b/src/ui/widgets/uvedit.h index b7e31e926..e108cc204 100644 --- a/src/ui/widgets/uvedit.h +++ b/src/ui/widgets/uvedit.h @@ -53,6 +53,11 @@ class QGridLayout; class QMenu; class QUndoStack; +namespace Game +{ + enum GameMode : int; +} + #undef None // conflicts with Qt //! Displays and allows editing of UV coordinate data @@ -177,8 +182,8 @@ protected slots: void setupViewport( int width, int height ); void updateViewRect( int width, int height ); - bool bindTexture( const QString & filename ); - bool bindTexture( const QModelIndex & iSource ); + bool bindTexture( const QString & filename, const Game::GameMode game ); + bool bindTexture( const QModelIndex & iSource, const Game::GameMode game ); QVector indices( const QPoint & p ) const; QVector indices( const QRegion & r ) const; @@ -242,6 +247,8 @@ protected slots: QColor highlight; QColor wireframe; } cfg; + + Game::GameMode game; }; //! Dialog for getting scaling factors From 1f7da90954602cb4c6283212be5c307b11560e79 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 9 Jul 2020 11:24:37 -0400 Subject: [PATCH 035/118] nifxml rename compound to struct --- src/xml/nifxml.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index f4aadeba2..4f68c9371 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -99,7 +99,7 @@ class NifXmlHandler final : public QXmlDefaultHandler tags.insert( "niftoolsxml", tagFile ); tags.insert( "version", tagVersion ); tags.insert( "module", tagModule ); - tags.insert( "compound", tagCompound ); + tags.insert( "struct", tagCompound ); tags.insert( "niobject", tagBlock ); tags.insert( "field", tagAdd ); tags.insert( "default", tagAddDefault ); @@ -221,10 +221,10 @@ class NifXmlHandler final : public QXmlDefaultHandler QString id = name; if ( x == tagCompound && NifValue::isValid( NifValue::type( id ) ) ) - err( tr( "compound %1 is already registered as internal type" ).arg( list.value( "name" ) ) ); + err( tr( "struct %1 is already registered as internal type" ).arg( list.value( "name" ) ) ); if ( id.isEmpty() ) - err( tr( "compound and niblocks must have a name" ) ); + err( tr( "struct and niblocks must have a name" ) ); if ( NifModel::compounds.contains( id ) || NifModel::blocks.contains( id ) ) err( tr( "multiple declarations of %1" ).arg( id ) ); @@ -310,7 +310,7 @@ class NifXmlHandler final : public QXmlDefaultHandler case tagModule: break; default: - err( tr( "expected basic, enum, compound, niobject or version got %1 instead" ).arg( tagid ) ); + err( tr( "expected basic, enum, struct, niobject or version got %1 instead" ).arg( tagid ) ); } break; @@ -320,7 +320,7 @@ class NifXmlHandler final : public QXmlDefaultHandler case tagCompound: if ( x != tagAdd ) - err( tr( "only field tags allowed in compound type declaration" ) ); + err( tr( "only field tags allowed in struct type declaration" ) ); case tagBlock: push( x ); @@ -649,13 +649,13 @@ class NifXmlHandler final : public QXmlDefaultHandler NifBlockPtr c = NifModel::compounds.value( key ); for ( NifData data :c->types ) { if ( !checkType( data ) ) - err( tr( "compound type %1 refers to unknown type %2" ).arg( key, data.type() ) ); + err( tr( "struct type %1 refers to unknown type %2" ).arg( key, data.type() ) ); if ( !checkTemp( data ) ) - err( tr( "compound type %1 refers to unknown template type %2" ).arg( key, data.temp() ) ); + err( tr( "struct type %1 refers to unknown template type %2" ).arg( key, data.temp() ) ); if ( data.type() == key ) - err( tr( "compound type %1 contains itself" ).arg( key ) ); + err( tr( "struct type %1 contains itself" ).arg( key ) ); } } From bd174eb104c7b3c552bf95c61430ae0a57879748 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 9 Jul 2020 11:26:00 -0400 Subject: [PATCH 036/118] Game Manager fixes Closes #30 --- src/gamemanager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gamemanager.cpp b/src/gamemanager.cpp index 34f339993..9095ac335 100644 --- a/src/gamemanager.cpp +++ b/src/gamemanager.cpp @@ -151,7 +151,7 @@ GameMode GameManager::get_game( uint32_t version, uint32_t user, uint32_t bsver case BSSTREAM_9: return OBLIVION; case BSSTREAM_11: - if ( user == 10 ) // TODO: Enumeration + if ( user == 10 || version == 0x14000005 ) // TODO: Enumeration return OBLIVION; else if ( user == 11 ) return FALLOUT_3NV; @@ -182,6 +182,10 @@ GameMode GameManager::get_game( uint32_t version, uint32_t user, uint32_t bsver break; }; + // NOTE: Morrowind shares a version with other games (Freedom Force, etc.) + if ( version == 0x04000002 ) + return MORROWIND; + return OTHER; } From 859aa66bcca4539aac9bf90305996dbc10a44179 Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 9 Jul 2020 11:31:02 -0400 Subject: [PATCH 037/118] [UI] Add Other Games fallback search Also, move alternative file extensions checkbox to main Games tab. Other Games fallback search will look a final time in all listed resources in Other Games if no file is found for the game's own resources. --- src/gl/gltex.cpp | 6 +++++- src/ui/settingspane.cpp | 3 +++ src/ui/settingsresources.ui | 21 ++++++++++++++------- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 0c58187f0..f861dd288 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -179,7 +179,7 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray bool textureAlternatives = settings.value( "Settings/Resources/Alternate Extensions", false ).toBool(); if ( textureAlternatives ) { - extensions << ".tga" << ".bmp" << ".nif" << ".texcache"; + extensions << ".tga" << ".png" << ".bmp" << ".nif" << ".texcache"; for ( const QString ext : QStringList{ extensions } ) { if ( filename.endsWith( ext, Qt::CaseInsensitive ) ) { @@ -271,6 +271,10 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray filename = filename.left( filename.length() - ext.length() ); } + bool searchFallback = settings.value("Settings/Resources/Other Games Fallback", true).toBool(); + if ( searchFallback && game != Game::OTHER ) + return find(file, nifdir, data, Game::OTHER); + // Fix separators filename = QDir::toNativeSeparators( filename ); diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index da2ae3e5a..d346cb4d7 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -543,6 +543,7 @@ SettingsResources::SettingsResources( QWidget * parent ) : connect( ui->foldersList, &QListView::doubleClicked, this, &SettingsPane::modifyPane ); connect( ui->chkAlternateExt, &QCheckBox::clicked, this, &SettingsPane::modifyPane ); + connect( ui->chkOtherGamesFallback, &QCheckBox::clicked, this, &SettingsPane::modifyPane ); // Move Up / Move Down Behavior connect( ui->foldersList->selectionModel(), &QItemSelectionModel::currentChanged, @@ -593,6 +594,7 @@ void SettingsResources::read() setArchiveList(); ui->chkAlternateExt->setChecked( settings.value( "Settings/Resources/Alternate Extensions", true ).toBool() ); + ui->chkOtherGamesFallback->setChecked( settings.value("Settings/Resources/Other Games Fallback", true).toBool() ); setModified( false ); } @@ -608,6 +610,7 @@ void SettingsResources::write() QSettings settings; settings.setValue( "Settings/Resources/Alternate Extensions", ui->chkAlternateExt->isChecked() ); + settings.setValue( "Settings/Resources/Other Games Fallback", ui->chkOtherGamesFallback->isChecked() ); setModified( false ); diff --git a/src/ui/settingsresources.ui b/src/ui/settingsresources.ui index 1138f6f1c..5917d40dd 100644 --- a/src/ui/settingsresources.ui +++ b/src/ui/settingsresources.ui @@ -833,6 +833,20 @@ + + + + Search alternative file extensions (TGA, PNG) + + + + + + + Search Other Games if no match + + + @@ -987,13 +1001,6 @@ Game Paths - - - - Load alternate file extensions - - - From 7771c8bb4bf835fd821d08a9adebede8a0b3059f Mon Sep 17 00:00:00 2001 From: jonwd7 Date: Thu, 9 Jul 2020 11:37:06 -0400 Subject: [PATCH 038/118] [GL] Correct Bitangent X for BSDynamicTriShape --- src/gl/bsshape.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 0e8724523..efc447bc6 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -135,6 +135,12 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) // For compatibility with coords list TexCoords coordset; + auto dynVerts = nif->getArray(iBlock, "Vertices"); + if ( isDynamic ) { + for ( const auto & v : dynVerts ) + verts << Vector3(v); + } + for ( int i = 0; i < numVerts; i++ ) { auto idx = nif->index( i, 0, iVertData ); @@ -145,6 +151,9 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) // Bitangent X auto bitX = nif->getValue( nif->getIndex( idx, "Bitangent X" ) ).toFloat(); + if ( isDynamic ) { + bitX = dynVerts.at(i)[3]; + } // Bitangent Y/Z auto bitYi = nif->getValue( nif->getIndex( idx, "Bitangent Y" ) ).toCount(); auto bitZi = nif->getValue( nif->getIndex( idx, "Bitangent Z" ) ).toCount(); @@ -161,11 +170,6 @@ void BSShape::update( const NifModel * nif, const QModelIndex & index ) } } - if ( isDynamic ) { - auto dynVerts = nif->getArray( iBlock, "Vertices" ); - for ( const auto & v : dynVerts ) - verts << Vector3( v ); - } // Add coords as first set of QList coords.append( coordset ); From 9b639e058a1198aea31540880c7be53c3ceb0be8 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Sun, 20 Feb 2022 15:42:55 -0500 Subject: [PATCH 039/118] Replace u8 string for C++20 --- src/ui/widgets/xmlcheck.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/widgets/xmlcheck.h b/src/ui/widgets/xmlcheck.h index 32026b02e..554af3cfa 100644 --- a/src/ui/widgets/xmlcheck.h +++ b/src/ui/widgets/xmlcheck.h @@ -43,7 +43,7 @@ enum OpType static std::array ops_ord = { // EQ, NEQ, AND, AND_S, NAND, STR_E, STR_S, STR_NS, STR_NE, CONT - "==", "!=", "&", "& 1<<", "!&", "^", "$", "!^", "!$", u8"⊂" + "==", "!=", "&", "& 1<<", "!&", "^", "$", "!^", "!$", QChar(0x2282) // ⊂ }; static std::map> ops = { From 6d49d311e0c5c2ff3d891b0bf725c739f6270d44 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Sun, 20 Feb 2022 15:43:34 -0500 Subject: [PATCH 040/118] Fix resource path for sizeGrip --- src/ui/nifskope.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 083166a1a..6c31f1d77 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -413,7 +413,7 @@ margin-top: -2px; } #statusbar QSizeGrip { -background: url(":/img/sizeGrip") no-repeat; +background: url(":/wnd/sizeGrip") no-repeat; width: 10px; height: 10px; } From 34045fe9f6846d924d2b60e3f4c6bfd63d24338a Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Sun, 20 Feb 2022 15:51:25 -0500 Subject: [PATCH 041/118] Move AA_UseDesktopOpenGL before app creation --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 1c40b4fec..04db24cb4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,6 +50,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. QCoreApplication * createApplication( int &argc, char *argv[] ) { + QApplication::setAttribute(Qt::AA_UseDesktopOpenGL); // Iterate over args for ( int i = 1; i < argc; ++i ) { // -no-gui: start as core app without all the GUI overhead @@ -71,7 +72,6 @@ int main( int argc, char * argv[] ) QScopedPointer app( createApplication( argc, argv ) ); if ( auto a = qobject_cast(app.data()) ) { - QApplication::setAttribute( Qt::AA_UseDesktopOpenGL ); a->setOrganizationName( "NifTools" ); a->setOrganizationDomain( "niftools.org" ); a->setApplicationName( "NifSkope " + NifSkopeVersion::rawToMajMin( NIFSKOPE_VERSION ) ); From 2fd1ccb010b4bd2226ae936308e9e10a8a0a4450 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 21 Feb 2022 17:32:05 -0500 Subject: [PATCH 042/118] Support NIF Versions without "Version" in string Also early accept --- src/io/nifstream.cpp | 16 +++++++++++++++- src/io/nifstream.h | 3 +++ src/model/basemodel.h | 2 +- src/model/kfmmodel.cpp | 2 +- src/model/kfmmodel.h | 2 +- src/model/nifmodel.cpp | 13 +++++++------ src/model/nifmodel.h | 2 +- 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp index c4db148ee..7bdb26309 100644 --- a/src/io/nifstream.cpp +++ b/src/io/nifstream.cpp @@ -356,8 +356,22 @@ bool NifIStream::read( NifValue & val ) if ( c >= 80 ) return false; + quint32 version = 0; + // Support NIF versions without "Version" in header string + // Do for all files for now + //if ( c == GAMEBRYO_FF || c == NETIMMERSE_FF || c == NEOSTEAM_FF ) { + device->peek((char *)&version, 4); + // NeoSteam Hack + if (version == 0x08F35232) + version = 0x0A010000; + // Version didn't exist until NetImmerse 4.0 + else if (version < 0x04000000) + version = 0; + //} + *static_cast(val.val.data) = QString( string ); - bool x = model->setHeaderString( QString( string ) ); + bool x = model->setHeaderString( QString( string ), version ); + init(); return x; } diff --git a/src/io/nifstream.h b/src/io/nifstream.h index c394d3a2d..b897db142 100644 --- a/src/io/nifstream.h +++ b/src/io/nifstream.h @@ -45,6 +45,9 @@ class BaseModel; class QDataStream; class QIODevice; +constexpr int NEOSTEAM_FF = 3; +constexpr int GAMEBRYO_FF = 21; +constexpr int NETIMMERSE_FF = 23; //! An input stream that reads a file into a model. class NifIStream final diff --git a/src/model/basemodel.h b/src/model/basemodel.h index 14f0fcedd..fa0d50e6b 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -332,7 +332,7 @@ class BaseModel : public QAbstractItemModel virtual bool evalVersion( NifItem * item, bool chkParents = false ) const = 0; //! Set the header string - virtual bool setHeaderString( const QString & ) = 0; + virtual bool setHeaderString( const QString &, uint ver = 0 ) = 0; //! Get an item by name template T get( NifItem * parent, const QString & name ) const; diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index 5e23f8377..09dc078d8 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -277,7 +277,7 @@ bool KfmModel::setItemValue( NifItem * item, const NifValue & val ) * load and save */ -bool KfmModel::setHeaderString( const QString & s ) +bool KfmModel::setHeaderString( const QString & s, uint ver ) { if ( s.startsWith( ";Gamebryo KFM File Version " ) ) { version = version2number( s.right( s.length() - 27 ) ); diff --git a/src/model/kfmmodel.h b/src/model/kfmmodel.h index 9d2770aac..b111cbe3e 100644 --- a/src/model/kfmmodel.h +++ b/src/model/kfmmodel.h @@ -96,7 +96,7 @@ class KfmModel final : public BaseModel bool setItemValue( NifItem * item, const NifValue & v ) override final; - bool setHeaderString( const QString & ) override final; + bool setHeaderString( const QString &, uint ver = 0 ) override final; bool evalVersion( NifItem * item, bool chkParents = false ) const override final; diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 11aac540a..1fa85146f 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -1683,7 +1683,7 @@ QModelIndex NifModel::buddy( const QModelIndex & index ) const * load and save */ -bool NifModel::setHeaderString( const QString & s ) +bool NifModel::setHeaderString( const QString & s, uint ver ) { if ( !( s.startsWith( "NetImmerse File Format" ) || s.startsWith( "Gamebryo" ) // official || s.startsWith( "NDSNIF" ) // altantica @@ -1697,8 +1697,13 @@ bool NifModel::setHeaderString( const QString & s ) return false; } - int p = s.indexOf( "Version", 0, Qt::CaseInsensitive ); + // Early Accept + if ( isVersionSupported(ver) ) { + version = ver; + return true; + } + int p = s.indexOf( "Version", 0, Qt::CaseInsensitive ); if ( p >= 0 ) { QString v = s; @@ -1720,10 +1725,6 @@ bool NifModel::setHeaderString( const QString & s ) return false; } - return true; - } else if ( s.startsWith( "NS" ) ) { - // Dodgy version for NeoSteam - version = 0x0a010000; return true; } diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 0c6fdb603..708c7aef0 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -319,7 +319,7 @@ public slots: bool evalVersion( NifItem * item, bool chkParents = false ) const override final; - bool setHeaderString( const QString & ) override final; + bool setHeaderString( const QString &, uint ver = 0 ) override final; template T get( NifItem * parent, const QString & name ) const; template T get( NifItem * item ) const; From f6fb0e538164e06f2fb5ade9004678732447e119 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Tue, 22 Feb 2022 08:44:26 -0500 Subject: [PATCH 043/118] Fix Reorder Link Arrays Only run for Bethesda NIFs as described in the class comment. Support BSTriShape based shapes. --- src/model/nifmodel.cpp | 9 +++++++++ src/model/nifmodel.h | 1 + src/spells/sanitize.cpp | 34 ++++++++++++++++++++++++---------- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 1fa85146f..c1783e441 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -1047,6 +1047,15 @@ bool NifModel::inherits( const QModelIndex & idx, const QString & aunty ) const return inherits( itemName( index( x + 1, 0 ) ), aunty ); } +bool NifModel::inherits( const QModelIndex& index, const QStringList& ancestors ) const +{ + for ( const auto & a : ancestors ) { + if ( inherits( index, a ) ) + return true; + } + + return false; +} /* * basic and compound type functions diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 708c7aef0..a7086507b 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -272,6 +272,7 @@ class NifModel final : public BaseModel bool inherits( const QString & name, const QStringList & ancestors ) const; // returns true if the block containing index inherits ancestor bool inherits( const QModelIndex & index, const QString & ancestor ) const; + bool inherits( const QModelIndex & index, const QStringList & ancestors ) const; //! Is this version supported? static bool isVersionSupported( quint32 ); diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index ce998bde1..2b7b6d083 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -17,16 +17,16 @@ */ //! Reorders blocks to put shapes before nodes (for Oblivion / FO3) -class spReorderLinks final : public Spell +class spReorderLinks : public Spell { public: - QString name() const override final { return Spell::tr( "Reorder Link Arrays" ); } - QString page() const override final { return Spell::tr( "Sanitize" ); } - bool sanity() const override final { return true; } + QString name() const override { return Spell::tr( "Reorder Link Arrays" ); } + QString page() const override { return Spell::tr( "Sanitize" ); } + bool sanity() const override { return true; } - bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override { - return ( !index.isValid() && ( nif->getVersionNumber() >= 0x14000004 ) ); + return ( !index.isValid() && ( nif->getVersionNumber() >= 0x14000004 && nif->getUserVersion2() > 0 ) ); } //! Comparator for link sort. @@ -36,11 +36,21 @@ class spReorderLinks final : public Spell * in the children links array. */ static bool compareChildLinks( const QPair & a, const QPair & b ) + { + return a.first < b.first; + } + + static bool compareChildLinksShapeTop(const QPair& a, const QPair& b) { return a.second != b.second ? a.second : a.first < b.first; } - QModelIndex cast( NifModel * nif, const QModelIndex & ) override final + static bool compareChildLinksShapeBtm(const QPair& a, const QPair& b) + { + return a.second != b.second ? !a.second : a.first < b.first; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override { for ( int n = 0; n < nif->getBlockCount(); n++ ) { QModelIndex iBlock = nif->getBlock( n ); @@ -49,17 +59,21 @@ class spReorderLinks final : public Spell QModelIndex iChildren = nif->getIndex( iBlock, "Children" ); if ( iNumChildren.isValid() && iChildren.isValid() ) { - QList > links; + QList> links; for ( int r = 0; r < nif->rowCount( iChildren ); r++ ) { qint32 l = nif->getLink( iChildren.child( r, 0 ) ); if ( l >= 0 ) { - links.append( QPair( l, nif->inherits( nif->getBlock( l ), "NiTriBasedGeom" ) ) ); + links.append( QPair( l, nif->inherits(nif->getBlock(l), {"NiTriBasedGeom", "BSTriShape"}) ) ); } } - std::stable_sort( links.begin(), links.end(), compareChildLinks ); + auto compareFn = compareChildLinksShapeBtm; + if ( nif->getUserVersion2() < 83 ) + compareFn = compareChildLinksShapeTop; + + std::stable_sort( links.begin(), links.end(), compareFn ); for ( int r = 0; r < links.count(); r++ ) { if ( links[r].first != nif->getLink( iChildren.child( r, 0 ) ) ) { From 005d4b6268967177109c5f55595e6eeba57425ef Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:44:50 -0500 Subject: [PATCH 044/118] Support BSDynamicTriShape for Normals functions --- src/spells/normals.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index 1bca55271..35362152b 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -33,7 +33,7 @@ class spFaceNormals final : public Spell if ( nif->isNiBlock( iData, { "NiTriShapeData", "NiTriStripsData" } ) ) return iData; - if ( nif->isNiBlock( index, { "BSTriShape", "BSMeshLODTriShape", "BSSubIndexTriShape" } ) ) { + if ( nif->isNiBlock( index, { "BSTriShape", "BSMeshLODTriShape", "BSSubIndexTriShape", "BSDynamicTriShape" } ) ) { auto vf = nif->get( index, "Vertex Desc" ); if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { // Skinned SSE @@ -123,10 +123,18 @@ class spFaceNormals final : public Spell verts.reserve( numVerts ); QVector norms( numVerts ); - for ( int i = 0; i < numVerts; i++ ) { - auto idx = nif->index( i, 0, iData ); + if ( nif->isNiBlock(index, "BSDynamicTriShape") ) { + auto dynVerts = nif->getArray(index, "Vertices"); + verts.clear(); + verts.reserve(numVerts); + for ( const auto & v : dynVerts ) + verts << Vector3(v); + } else { + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index(i, 0, iData); - verts += nif->get( idx, "Vertex" ); + verts += nif->get(idx, "Vertex"); + } } faceNormals( verts, triangles, norms ); @@ -221,6 +229,14 @@ class spSmoothNormals final : public Spell } } + if ( nif->isNiBlock(index, "BSDynamicTriShape") ) { + auto dynVerts = nif->getArray(index, "Vertices"); + verts.clear(); + verts.reserve( numVerts ); + for ( const auto & v : dynVerts ) + verts << Vector3(v); + } + if ( verts.isEmpty() || verts.count() != norms.count() ) return index; From 50a8066fa681e3c00df3b50b0a95145e12d5e853 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:51:42 -0500 Subject: [PATCH 045/118] [Build] Linux compilation fix --- src/ui/widgets/lightingwidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/widgets/lightingwidget.cpp b/src/ui/widgets/lightingwidget.cpp index 5ccdbd993..7add83687 100644 --- a/src/ui/widgets/lightingwidget.cpp +++ b/src/ui/widgets/lightingwidget.cpp @@ -3,6 +3,8 @@ #include "glview.h" +#include + // Slider lambda auto sld = []( QSlider * slider, int min, int max, int val ) { From faa45e3d2a74a64f6f7a3abf5b8eb6f4704bd412 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Tue, 22 Feb 2022 12:40:16 -0500 Subject: [PATCH 046/118] [Build] MacOS Fixes --- NifSkope.pro | 3 +++ src/xml/kfmxml.cpp | 3 +++ src/xml/nifxml.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/NifSkope.pro b/NifSkope.pro index 33700a6b8..9b531ec60 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -347,6 +347,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/src/xml/kfmxml.cpp b/src/xml/kfmxml.cpp index decd454a0..573b7d108 100644 --- a/src/xml/kfmxml.cpp +++ b/src/xml/kfmxml.cpp @@ -267,6 +267,9 @@ bool KfmModel::loadXML() << "kfm.xml" #ifdef Q_OS_LINUX << "/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 4f68c9371..00233f9c8 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -705,6 +705,9 @@ bool NifModel::loadXML() << "nif.xml" #ifdef Q_OS_LINUX << "/usr/share/nifskope/nif.xml" +#endif +#ifdef Q_OS_MACX + << "../../../nif.xml" #endif ); for ( const QString& str : xmlList ) { From 3bc0c27544477a3d90cdd56e2d9243dc5fc9afc1 Mon Sep 17 00:00:00 2001 From: gavrant Date: Wed, 17 May 2023 23:24:27 +0300 Subject: [PATCH 047/118] Correct nif.xml (see https://github.com/hexabits/nifskope/issues/33 ) --- build/docsys | 2 +- build/nif.xml | 8427 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 8428 insertions(+), 1 deletion(-) create mode 100644 build/nif.xml diff --git a/build/docsys b/build/docsys index 07dc05fe2..0ffd95a50 160000 --- a/build/docsys +++ b/build/docsys @@ -1 +1 @@ -Subproject commit 07dc05fe2c09ac8f1defd4e94c90fe60202cb191 +Subproject commit 0ffd95a50c277e761ab664b68a827502d163c083 diff --git a/build/nif.xml b/build/nif.xml new file mode 100644 index 000000000..832a539e7 --- /dev/null +++ b/build/nif.xml @@ -0,0 +1,8427 @@ + + + + + + Dark Age of Camelot + Star Trek: Bridge Commander + Dark Age of Camelot + Dark Age of Camelot, Star Trek: Bridge Commander + {{Munch's Oddysee}}, Oblivion + Freedom Force + {{Morrowind}}, {{Freedom Force}} + Dark Age of Camelot + Civilization IV + Dark Age of Camelot, Civilization IV + {{Culpa Innata}}, Civilization IV, Dark Age of Camelot, Empire Earth II + {{Zoo Tycoon 2}}, Civilization IV, Oblivion + Oblivion + {{Freedom Force vs. the 3rd Reich}}, {{Axis and Allies}}, {{Empire Earth II}}, {{Kohan 2}}, {{Sid Meier's Pirates!}}, Dark Age of Camelot, Civilization IV, Wildlife Park 2, The Guild 2, NeoSteam + Oblivion + Oblivion + {{Pro Cycling Manager}}, {{Prison Tycoon}}, {{Red Ocean}}, {{Wildlife Park 2}}, Civilization IV, Loki + {{Blood Bowl}} + Oblivion + WorldShift + WorldShift + {{WorldShift}} + {{Civilization IV}}, {{Sid Meier's Railroads}}, Florensia, Ragnarok Online 2, IRIS Online + {{Oblivion KF}} + Fallout 3 + {{Oblivion}} + {{Shin Megami Tensei: Imagine}} + {{Florensia}}, Empire Earth III, Atlantica Online, IRIS Online, Wizard101 + Fallout 3, Fallout NV + Fallout 3 + Fallout 3, Fallout NV + Fallout 3, Fallout NV + Fallout 3, Fallout NV + Fallout 3, Fallout NV + Fallout 3, Fallout NV + Fallout 3, Fallout NV + {{Fallout 3}}, {{Fallout NV}} + {{Skyrim}} + {{Skyrim SE}} + {{Fallout 4}} + Fallout 4 (LS_Mirelurk.nif, Screen.nif) + {{Fallout 76}} + {{Empire Earth III}}, {{FFT Online}}, Atlantica Online, IRIS Online, Wizard101 + Emerge + Emerge + Emerge + Emerge + {{Bully SE}}, Warhammer, Lazeska, Howling Sword, Ragnarok Online 2, Divinity 2 (0x10000) + {{Divinity 2}} + MicroVolts, KrazyRain + {{MicroVolts}}, {{IRIS Online}}, {{Ragnarok Online 2}}, KrazyRain, Atlantica Online, Wizard101 + Epic Mickey + Epic Mickey + Epic Mickey 2 + Emerge + Emerge + Rocksmith, Rocksmith 2014 + Ghost In The Shell: First Assault, MapleStory 2 + + + + + + + + + + + + + + + + + + Commonly used version expressions. + NOTE: `string` should be wrapped in parentheses for string subsitutions in larger expressions. + NOTE: BSVER 'Greater Than' Expressions only ever apply to BS i.e. BSVER GT 0. + WARNING: BSVER 'Less Than' Expressions also apply to NI i.e. BSVER EQ 0. + NiStream that are not Bethesda. + NiStream that are Bethesda. + All NI + BS until BSVER 16. + All NI + BS before Fallout 3. + All NI + BS until Fallout 3. + All NI + BS before SSE. + All NI + BS before Fallout 4. + All NI + BS until Fallout 4. + Skyrim, SSE, and Fallout 4 + FO3 and later. + Skyrim and later. + SSE and later. + SSE only. + Fallout 4 strictly, excluding stream 132 and 139 in dev files. + Fallout 4/76 including dev files. + Later than Bethesda 130. + Bethesda 130 and later. + Bethesda 132 and later. + Bethesda 152 and later. + Fallout 76 stream 155 only. + Fallout 76 stream 152 and higher + Bethesda 20.2 only. + Divinity 2 + + + + + + + + + The set of versions that any Basic, Compound, NiObject, Enum, or Bitflags is restricted to. + + + + + + + + + + + + Commonly used default values. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Commonly used range values. + + + + + + + + + + + + + + + + + + + + + + Global Tokens. + NOTE: These must be listed after the above tokens so that they replace last. For example, `verexpr` uses these tokens. + + + + + + + All Operators except for unary not (!), parentheses, and member of (\) + NOTE: These can be ignored entirely by string substitution and dealt with directly. + NOTE: These must be listed after the above tokens so that they replace last. For example, `verexpr` uses these tokens. + + + + + + + + + + + + + + + + + + + + + + An unsigned 64-bit integer. + + + + A signed 64-bit integer. + + + + A little-endian unsigned 32-bit integer. + + + + An unsigned 32-bit integer. + + + + A signed 32-bit integer. + + + + An unsigned 16-bit integer. + + + + A signed 16-bit integer. + + + + An 8-bit character. + + + + An unsigned 8-bit integer. + + + + A boolean; 32-bit from 4.0.0.2, and 8-bit from 4.1.0.1 on. + + + + A 16-bit (signed?) integer, which is used in the header to refer to a particular object type in a object type string array. + The upper bit appears to be a flag used for PhysX block types. + + + + A 32-bit integer that stores the version in hexadecimal format with each byte representing a number in the version string. + + Some widely-used versions and their hex representation: + 4.0.0.2: 0x04000002 + 4.1.0.12: 0x0401000C + 4.2.0.2: 0x04020002 + 4.2.1.0: 0x04020100 + 4.2.2.0: 0x04020200 + 10.0.1.0: 0x0A000100 + 10.1.0.0: 0x0A010000 + 10.2.0.0: 0x0A020000 + 20.0.0.4: 0x14000004 + 20.0.0.5: 0x14000005 + + + + A standard 32-bit floating point number. + + + + A 16-bit floating point number. + + + + A variable length string that ends with a newline character (0x0A). The string starts as follows depending on the version: + + Version <= 10.0.1.0: 'NetImmerse File Format' + Version >= 10.1.0.0: 'Gamebryo File Format' + + + + A variable length string that ends with a newline character (0x0A). + + + + A signed 32-bit integer, referring to a object before this one in the hierarchy. Examples: Bones, gravity objects. + + + + A signed 32-bit integer, used to refer to another object; -1 means no reference. These should always point down the hierarchy. Other types are used for indexes that point to objects higher up. + + + + A 32-bit unsigned integer, used to refer to strings in a NiStringPalette. + + + + A 32-bit unsigned integer, used to refer to strings in the header. + + + + Describes the options for the accum root on NiControllerSequence. + + + + + + + + + + + + + + Describes how the vertex colors are blended with the filtered texture color. + + + + + + + + + The type of texture. + + + + + + + + + + + + + + + + The type of animation interpolation (blending) that will be used on the associated key frames. + + + + + + + + + Bethesda Havok. Material descriptor for a Havok shape in Oblivion. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bethesda Havok. Material descriptor for a Havok shape in Fallout 3 and Fallout NV. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bethesda Havok. Material descriptor for a Havok shape in Skyrim. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bethesda Havok. Describes the collision layer a body belongs to in Oblivion. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bethesda Havok. Describes the collision layer a body belongs to in Fallout 3 and Fallout NV. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bethesda Havok. Describes the collision layer a body belongs to in Skyrim. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hkpMoppCode::BuildType + A byte describing if MOPP Data is organized into chunks (PS3) or not (PC) + + + + + + + Target platform for NiPersistentSrcTextureRendererData (later than 30.1). + + + + Target renderer for NiPersistentSrcTextureRendererData (until 30.1). + + + + Describes the pixel format used by the NiPixelData object to store a texture. + + + + + + + + + + + + + + + + + + + + Describes whether pixels have been tiled from their standard row-major format to a format optimized for a particular platform. + + + + Describes the pixel format used by the NiPixelData object to store a texture. + + + + Describes how each pixel should be accessed on NiPixelFormat. + + + + Describes the color depth in an NiTexture. + + + + + + + + + + + Describes how mipmaps are handled in an NiTexture. + + + + + + + Describes how transparency is handled in an NiTexture. + + + + + + + + Describes the availiable texture clamp modes, i.e. the behavior of UV mapping outside the [0,1] range. + + + + + + + + Describes the availiable texture filter modes, i.e. the way the pixels in a texture are displayed on screen. + + + + + + + + + + + Describes how to apply vertex colors for NiVertexColorProperty. + + + + + + + Describes which lighting equation components influence the final vertex color for NiVertexColorProperty. + + + + + + The animation cyle behavior. + + + + + + + The force field type. + + + + + + Determines the way the billboard will react to the camera. + Billboard mode is stored in lowest 3 bits although Oblivion vanilla nifs uses values higher than 7. + + + + + + + + + + + + + + + Describes stencil buffer test modes for NiStencilProperty. + + + + + + + + + + + + Describes the actions which can occur as a result of tests for NiStencilProperty. + + + + + + + + + + Describes the face culling options for NiStencilProperty. + + + + + + + + Describes Z-buffer test modes for NiZBufferProperty. + "Less than" = closer to camera, "Greater than" = further from camera. + + + + + + + + + + + + Describes alpha blend modes for NiAlphaProperty. + + + + hkpMotion::MotionType. Motion type of a rigid body determines what happens when it is simulated. + + + + + + + + + + + + + + hkpRigidBodyDeactivator::DeactivatorType. Deactivator Type determines which mechanism Havok will use to classify the body as deactivated. + + + + + + + hkpRigidBodyCinfo::SolverDeactivation. + A list of possible solver deactivation settings. This value defines how aggressively the solver deactivates objects. + Note: Solver deactivation does not save CPU, but reduces creeping of movable objects in a pile quite dramatically. + + + + + + + + + + hkpCollidableQualityType. Describes the priority and quality of collisions for a body, + e.g. you may expect critical game play objects to have solid high-priority collisions so that they never sink into ground, + or may allow penetrations for visual debris objects. + Notes: + - Fixed and keyframed objects cannot interact with each other. + - Debris can interpenetrate but still responds to Bullet hits. + - Critical objects are forced to not interpenetrate. + - Moving objects can interpenetrate slightly with other Moving or Debris objects but nothing else. + + + + + + + + + + + + + + Describes the type of gravitational force. + + + + + + + Describes which aspect of the NiTextureTransform the NiTextureTransformController will modify. + + + + + + + + + Describes the decay function of bomb forces. + + + + + + + Describes the symmetry type of bomb forces. + + + + + + + Controls the way the a particle mesh emitter determines the starting speed and direction of the particles that are emitted. + + + + + + + Controls which parts of the mesh that the particles are emitted from. + + + + + + + + + The type of information that is stored in a texture used by an NiTextureEffect. + + + + + + + + Determines the way that UV texture coordinates are generated. + + + + + + + + + + + + + + Used by NiMaterialColorControllers to select which type of color in the controlled object that will be animated. + + + + + + + + Used by NiLightColorControllers to select which type of color in the controlled object that will be animated. + + + + + + Used by NiGeometryData to control the volatility of the mesh. + Consistency Type is masked to only the upper 4 bits (0xF000). Dirty mask is the lower 12 (0x0FFF) but only used at runtime. + + + + + + + Describes the way that NiSortAdjustNode modifies the sorting behavior for the subtree below it. + + + + + + The propagation mode controls scene graph traversal during collision detection operations for NiCollisionData. + + + + + + + + + The collision mode controls the type of collision operation that is to take place for NiCollisionData. + + + + + + + + + + + + + + + + + + hkpMaterial::ResponseType + + + + + + + + Biped bodypart data used for visibility control of triangles. Options are Fallout 3, except where marked for Skyrim (uses SBP prefix) + Skyrim BP names are listed only for vanilla names, different creatures have different defnitions for naming. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Values for configuring the shader type in a BSLightingShaderProperty + + + + + + + + + + + + + + Values for configuring the shader type in a BSLightingShaderProperty + + + + An unsigned 32-bit integer, describing which float variable in BSEffectShaderProperty to animate. + + + + + + + + + + + + + + An unsigned 32-bit integer, describing which color in BSEffectShaderProperty to animate. + + + + + An unsigned 32-bit integer, describing which float variable in BSLightingShaderProperty to animate. + + + + + + + + + + + + + + An unsigned 32-bit integer, describing which integral value in BSLightingShaderProperty to animate. + + + + An unsigned 32-bit integer, describing which color in BSLightingShaderProperty to animate. + + + + + + hkpConstraintData::ConstraintType. Describes the type of bhkConstraint. + + + + + + + + + + + + + + + + + Flags for NiDitherProperty + + + + Flags for NiShadeProperty + + + + Flags for NiSpecularProperty + + + + Flags for NiWireframeProperty + + + + Flags for NiGeomMorpherController + + + + + + Flags for NiTimeController + + + + + + + + + + + Flags for NiAlphaProperty + + + + + + + Bethesda-only. Always true for weapon blood after FO3. + Bethesda-only. True if the Alpha Threshold is externally controlled. + + + + Flags for NiFogProperty + + + + + + Flags for NiStencilProperty + + + + + + + + + + Flags for NiTexturingProperty + + + + + + + Flags for NiTexturingProperty + + + + + + + Flags for NiVertexColorProperty + + + + + + + Flags for NiZBufferProperty + + + + + + + + + + Flags for NiAGDDataStream + + + + + + + + + + Unused for non-Bethesda, non-bhk NiGeometryData. + + + + + + + + + + + Used only if the Layer is 8 (or 32/33 for Skyrim and later). + + + If Layer is CHARCONTROLLER (CC), true means "CC Trigger Only". + + + + A string of given length. + The string length. + The string itself. + + + + A string of given length, using a ushort to store string length. + The string length. + The string itself. + + + + A string type. + The normal string. + The string index. + + + + Currently, #T# must be a basic type due to nif.xml restrictions. + + + + + + A mapping or hash table between NiFixedString keys and a generic value. + Currently, #T# must be a basic type due to nif.xml restrictions. + + + + + + An array of bytes. + The number of bytes in this array + The bytes which make up the array + + + + An array of bytes. + The number of bytes in this array + The number of bytes in this array + The bytes which make up the array + + + + A color without alpha (red, green, blue). + Red color component. + Green color component. + Blue color component. + + + + A color without alpha (red, green, blue). + Red color component. + Green color component. + Blue color component. + + + + A color with alpha (red, green, blue, alpha). + Red component. + Green component. + Blue component. + Alpha. + + + + A color with alpha (red, green, blue, alpha). + Red color component. + Green color component. + Blue color component. + Alpha color component. + + + + A string that contains the path to a file. + The normal string. + The string index. + + + + The NIF file footer. + The number of root references. + List of root NIF objects. If there is a camera, for 1st person view, then this NIF object is referred to as well in this list, even if it is not a root object (usually we want the camera to be attached to the Bip Head node). + + + + The distance range where a specific level of detail applies. + Begining of range. + End of Range. + + + + + Group of vertex indices of vertices that match. + Number of vertices in this group. + The vertex indices. + + + + A vector in 3D space (x,y,z). + First coordinate. + Second coordinate. + Third coordinate. + + + + A vector in 3D space (x,y,z). + First coordinate. + Second coordinate. + Third coordinate. + + + + A vector in 3D space (x,y,z). + First coordinate. + Second coordinate. + Third coordinate. + + + + A 4-dimensional vector. + First coordinate. + Second coordinate. + Third coordinate. + Fourth coordinate. + + + + A quaternion. + The w-coordinate. + The x-coordinate. + The y-coordinate. + The z-coordinate. + + + + A quaternion as it appears in the havok objects. + The x-coordinate. + The y-coordinate. + The z-coordinate. + The w-coordinate. + + + + A 2x2 matrix of float values. Stored in OpenGL column-major format. + Member 1,1 (top left) + Member 2,1 (bottom left) + Member 1,2 (top right) + Member 2,2 (bottom right) + + + + A 3x3 rotation matrix; M^T M=identity, det(M)=1. Stored in OpenGL column-major format. + Member 1,1 (top left) + Member 2,1 + Member 3,1 (bottom left) + Member 1,2 + Member 2,2 + Member 3,2 + Member 1,3 (top right) + Member 2,3 + Member 3,3 (bottom left) + + + + A 3x4 transformation matrix. + The (1,1) element. + The (2,1) element. + The (3,1) element. + The (1,2) element. + The (2,2) element. + The (3,2) element. + The (1,3) element. + The (2,3) element. + The (3,3) element. + The (1,4) element. + The (2,4) element. + The (3,4) element. + + + + A 4x4 transformation matrix. + The (1,1) element. + The (2,1) element. + The (3,1) element. + The (4,1) element. + The (1,2) element. + The (2,2) element. + The (3,2) element. + The (4,2) element. + The (1,3) element. + The (2,3) element. + The (3,3) element. + The (4,3) element. + The (1,4) element. + The (2,4) element. + The (3,4) element. + The (4,4) element. + + + + A 3x3 Havok matrix stored in 4x3 due to memory alignment. + + + + Unused + + + + Unused + + + + Unused + + + + Description of a mipmap within an NiPixelData object. + Width of the mipmap image. + Height of the mipmap image. + Offset into the pixel data array where this mipmap starts. + + + + A set of NiNode references. + Number of node references that follow. + The list of NiNode references. + + + + Specific to Bethesda-specific header export strings. + The string length. + The string itself, null terminated (the null terminator is taken into account in the length byte). + + + + NiBoneLODController::SkinInfo. Reference to shape and skin instance. + + + + + + A set of NiBoneLODController::SkinInfo. + + + + + + NiSkinData::BoneVertData. A vertex and its weight. + The vertex index, in the mesh. + The vertex weight - between 0.0 and 1.0 + + + + Used in NiDefaultAVObjectPalette. + Object name. + Object reference. + + + + In a .kf file, this links to a controllable object, via its name (or for version 10.2.0.0 and up, a link and offset to a NiStringPalette that contains the name), and a sequence of interpolators that apply to this controllable object, via links. + For Controller ID, NiInterpController::GetCtlrID() virtual function returns a string formatted specifically for the derived type. + For Interpolator ID, NiInterpController::GetInterpolatorID() virtual function returns a string formatted specifically for the derived type. + The string formats are documented on the relevant niobject blocks. + Name of a controllable object in another NIF file. + + + + + + Idle animations tend to have low values for this, and high values tend to correspond with the important parts of the animations. + + The name of the animated NiAVObject. + The RTTI type of the NiProperty the controller is attached to, if applicable. + The RTTI type of the NiTimeController. + An ID that can uniquely identify the controller among others of the same type on the same NiObjectNET. + An ID that can uniquely identify the interpolator among others of the same type on the same NiObjectNET. + + Refers to the NiStringPalette which contains the name of the controlled NIF object. + Offset in NiStringPalette to the name of the animated NiAVObject. + Offset in NiStringPalette to the RTTI type of the NiProperty the controller is attached to, if applicable. + Offset in NiStringPalette to the RTTI type of the NiTimeController. + Offset in NiStringPalette to an ID that can uniquely identify the controller among others of the same type on the same NiObjectNET. + Offset in NiStringPalette to an ID that can uniquely identify the interpolator among others of the same type on the same NiObjectNET. + + The name of the animated NiAVObject. + The RTTI type of the NiProperty the controller is attached to, if applicable. + The RTTI type of the NiTimeController. + An ID that can uniquely identify the controller among others of the same type on the same NiObjectNET. + An ID that can uniquely identify the interpolator among others of the same type on the same NiObjectNET. + + + + Information about how the file was exported + + + + + + + + + + + The NIF file header. + 'NetImmerse File Format x.x.x.x' (versions <= 10.0.1.2) or 'Gamebryo File Format x.x.x.x' (versions >= 10.1.0.0), with x.x.x.x the version written out. Ends with a newline character (0x0A). + + The NIF version, in hexadecimal notation: 0x04000002, 0x0401000C, 0x04020002, 0x04020100, 0x04020200, 0x0A000100, 0x0A010000, 0x0A020000, 0x14000004, ... + Determines the endianness of the data in the file. + An extra version number, for companies that decide to modify the file format. + Number of file objects. + + + Number of object types in this NIF file. + List of all object types used in this NIF file. + List of all object types used in this NIF file. + Maps file objects on their corresponding type: first file object is of type object_types[object_type_index[0]], the second of object_types[object_type_index[1]], etc. + Array of block sizes + Number of strings. + Maximum string length. + Strings. + + + + + + A list of \\0 terminated strings. + A bunch of 0x00 seperated strings. + Length of the palette string is repeated here. + + + + Tension, bias, continuity. + Tension. + Bias. + Continuity. + + + + A generic key with support for interpolation. Type 1 is normal linear interpolation, type 2 has forward and backward tangents, and type 3 has tension, bias and continuity arguments. Note that color4 and byte always seem to be of type 1. + Time of the key. + The key value. + Key forward tangent. + The key backward tangent. + The TBC of the key. + + + + Array of vector keys (anything that can be interpolated, except rotations). + Number of keys in the array. + The key type. + The keys. + + + + A special version of the key type used for quaternions. Never has tangents. #T# should always be Quaternion. + Time the key applies. + Time the key applies. + Value of the key. + The TBC of the key. + + + + Texture coordinates (u,v). As in OpenGL; image origin is in the lower left corner. + First coordinate. + Second coordinate. + + + + Texture coordinates (u,v). + First coordinate. + Second coordinate. + + + + Describes the order of scaling and rotation matrices. Translate, Scale, Rotation, Center are from TexDesc. + Back = inverse of Center. FromMaya = inverse of the V axis with a positive translation along V of 1 unit. + + + + + + + NiTexturingProperty::Map. Texture description. + Link to the texture image. + NiSourceTexture object index. + 0=clamp S clamp T, 1=clamp S wrap T, 2=wrap S clamp T, 3=wrap S wrap T + 0=nearest, 1=bilinear, 2=trilinear, 3=..., 4=..., 5=... + Texture mode flags; clamp and filter mode stored in upper byte with 0xYZ00 = clamp mode Y, filter mode Z. + + The texture coordinate set in NiGeometryData that this texture slot will use. + L can range from 0 to 3 and are used to specify how fast a texture gets blurry. + K is used as an offset into the mipmap levels and can range from -2047 to 2047. Positive values push the mipmap towards being blurry and negative values make the mipmap sharper. + + + Whether or not the texture coordinates are transformed. + The UV translation. + The UV scale. + The W axis rotation in texture space. + Depending on the source, scaling can occur before or after rotation. + The origin around which the texture rotates. + + + + NiTexturingProperty::ShaderMap. Shader texture description. + + + Unique identifier for the Gamebryo shader system. + + + + List of three vertex indices. + First vertex index. + Second vertex index. + Third vertex index. + + + + The bits of BSVertexDesc that describe the enabled vertex attributes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skinning data for a submesh, optimized for hardware skinning. Part of NiSkinPartition. + Number of vertices in this submesh. + Number of triangles in this submesh. + Number of bones influencing this submesh. + Number of strips in this submesh (zero if not stripped). + Number of weight coefficients per vertex. The Gamebryo engine seems to work well only if this number is equal to 4, even if there are less than 4 influences per vertex. + List of bones. + Do we have a vertex map? + Maps the weight/influence lists in this submesh to the vertices in the shape being skinned. + Maps the weight/influence lists in this submesh to the vertices in the shape being skinned. + Do we have vertex weights? + The vertex weights. + The vertex weights. + The strip lengths. + Do we have triangle or strip data? + The strips. + The strips. + The triangles. + The triangles. + Do we have bone indices? + Bone indices, they index into 'Bones'. + + + + + + + + A plane. + The plane normal. + The plane constant. + + + + Divinity 2 specific NiBound extension. + + Corners are only non-zero if Num Corners is 2. Hardcoded to 2. + + + + A sphere. + The sphere's center. + The sphere's radius. + + + + + A 3D curve made up of control points and knots. + + + + + + + + + + + + Whether each transform component is valid. + + + + The rotation part of the transformation matrix. + The translation vector. + Scaling part (only uniform scaling is supported). + + + + Bethesda Animation. Furniture entry points. It specifies the direction(s) from where the actor is able to enter (and leave) the position. + + + + + + + + + Bethesda Animation. Animation type used on this position. This specifies the function of this position. + + + + + + + Bethesda Animation. Describes a furniture position? + Offset of furniture marker. + Furniture marker orientation. + Refers to a furnituremarkerxx.nif file. Always seems to be the same as Position Ref 2. + Refers to a furnituremarkerxx.nif file. Always seems to be the same as Position Ref 1. + Similar to Orientation, in float form. + + + + + + Bethesda Havok. A triangle with extra data used for physics. + The triangle. + Additional havok information on how triangles are welded. + This is the triangle's normal. + + + + Geometry morphing data component. + Name of the frame. + The number of morph keys that follow. + Unlike most objects, the presense of this value is not conditional on there being keys. + The morph key frames. + + Morph vectors. + + + + Called NiPerParticleData in NiOldParticles. + Holds the state of a particle at the time the system was saved. + Particle direction and speed. + + + + Timestamp of the last update. + + Usually matches array index + + + + NiSkinData::BoneData. Skinning data component. + Offset of the skin from this bone in bind position. + Note that its a Sphere Containing Axis Aligned Box not a minimum volume Sphere + Number of weighted vertices. + The vertex weights. + The vertex weights. + + + + Bethesda Havok. Collision filter info representing Layer, Flags, Part Number, and Group all combined into one uint. + The layer the collision belongs to. + The layer the collision belongs to. + The layer the collision belongs to. + + + + + + Bethesda Havok. Material wrapper for varying material enums by game. + + The material of the shape. + The material of the shape. + The material of the shape. + + + + Bethesda Havok. Havok Information for packed TriStrip shapes. + + The number of vertices that form this sub shape. + The material of the subshape. + + + + Havok AABB using min/max coordinates instead of center/half extents. + Coordinates of the corner with the lowest numerical values. + Coordinates of the corner with the highest numerical values. + + + + hkpConstraintInstance::ConstraintPriority. Priority used for the constraint. + Values 2, 4, and 5 are unused or internal use only. + + + + + + Bethesda extension of hkpConstraintInstance. + Always 2 (Hardcoded). Number of bodies affected by this constraint. + The entity affected by this constraint. + The entity affected by this constraint. + Either PSI or TOI priority. TOI is higher priority. + + + + Bethesda extension of hkpConstraintChainInstance. + + + + + + + Bethesda extension of hkpPositionConstraintMotor. + A motor which tries to reach a desired position/angle given a max force and recovery speed. + This motor is a good choice for driving a ragdoll to a given pose. + Minimum motor force + Maximum motor force + Relative stiffness + Motor damping value + A factor of the current error to calculate the recovery velocity + A constant velocity which is used to recover from errors + Is Motor enabled + + + + Bethesda extension of hkpVelocityConstraintMotor. Tries to reach and keep a desired target velocity. + Minimum motor force + Maximum motor force + Relative stiffness + + + Is Motor enabled + + + + Bethesda extension of hkpSpringDamperConstraintMotor. + Tries to reach a given target position using an angular spring which has a spring constant. + Minimum motor force + Maximum motor force + The spring constant in N/m + The spring damping in Nsec/m + Is Motor enabled + + + + + + + hkConstraintCinfo::SaveMotor(). Not a Bethesda extension of hkpConstraintMotor, but a wrapper for its serialization function. + + + + + + + + Serialization data for bhkRagdollConstraint. + The area of movement can be represented as a main cone + 2 orthogonal cones which may subtract from the main cone volume depending on limits. + + The point where the constraint is attached to its parent rigidbody. + Defines the orthogonal plane in which the body can move, the orthogonal directions in which the shape can be controlled (the direction orthogonal on this one and Twist A). + Central directed axis of the cone in which the object can rotate. Orthogonal on Plane A. + The point where the constraint is attached to the other rigidbody. + Defines the orthogonal plane in which the shape can be controlled (the direction orthogonal on this one and Twist B). + Central directed axis of the cone in which the object can rotate. Orthogonal on Plane B. + + + Central directed axis of the cone in which the object can rotate. Orthogonal on Plane A. + Defines the orthogonal plane in which the body can move, the orthogonal directions in which the shape can be controlled (the direction orthogonal on this one and Twist A). + Defines the orthogonal directions in which the shape can be controlled (namely in this direction, and in the direction orthogonal on this one and Twist A). + Point around which the object will rotate. Defines the orthogonal directions in which the shape can be controlled (namely in this direction, and in the direction orthogonal on this one and Twist A). + Central directed axis of the cone in which the object can rotate. Orthogonal on Plane B. + Defines the orthogonal plane in which the body can move, the orthogonal directions in which the shape can be controlled (the direction orthogonal on this one and Twist A). + Defines the orthogonal directions in which the shape can be controlled (namely in this direction, and in the direction orthogonal on this one and Twist A). + Defines the orthogonal directions in which the shape can be controlled (namely in this direction, and in the direction orthogonal on this one and Twist A). + + Maximum angle the object can rotate around the vector orthogonal on Plane A and Twist A relative to the Twist A vector. Note that Cone Min Angle is not stored, but is simply minus this angle. + Minimum angle the object can rotate around Plane A, relative to Twist A. + Maximum angle the object can rotate around Plane A, relative to Twist A. + Minimum angle the object can rotate around Twist A, relative to Plane A. + Maximum angle the object can rotate around Twist A, relative to Plane A. + Maximum friction, typically 0 or 10. In Fallout 3, typically 100. + + + + + + Serialization data for bhkLimitedHingeConstraint. + This constraint allows rotation about a specified axis, limited by specified boundaries. + + + Pivot point around which the object will rotate. + Axis of rotation. + Vector in the rotation plane which defines the zero angle. + Vector in the rotation plane, orthogonal on the previous one, which defines the positive direction of rotation. This is always the vector product of Axis A and Perp Axis In A1. + Pivot A in second entity coordinate system. + Axis A in second entity coordinate system. + Perp Axis In A2 in second entity coordinate system. + + + Axis of rotation. + Vector in the rotation plane which defines the zero angle. + Vector in the rotation plane, orthogonal on the previous one, which defines the positive direction of rotation. This is always the vector product of Axis A and Perp Axis In A1. + Pivot point around which the object will rotate. + Axis A in second entity coordinate system. + Perp Axis In A1 in second entity coordinate system. + Perp Axis In A2 in second entity coordinate system. + Pivot A in second entity coordinate system. + + Minimum rotation angle. + Maximum rotation angle. + Maximum friction, typically either 0 or 10. In Fallout 3, typically 100. + + + + + + Serialization data for bhkHingeConstraint. A basic hinge with no angular limits or motor. + + Pivot point around which the object will rotate. + Vector in the rotation plane which defines the zero angle. + Vector in the rotation plane, orthogonal on the previous one, which defines the positive direction of rotation. + Pivot A in second entity coordinate system. + Axis A (vector orthogonal on Perp Axes) in second entity coordinate system. + + + Axis of rotation. + Vector in the rotation plane which defines the zero angle. + Vector in the rotation plane, orthogonal on the previous one, which defines the positive direction of rotation. This is always the vector product of Axis A and Perp Axis In A1. + Pivot point around which the object will rotate. + Axis A in second entity coordinate system. + Perp Axis In A1 in second entity coordinate system. + Perp Axis In A2 in second entity coordinate system. + Pivot A in second entity coordinate system. + + + + Serialization data for bhkBallAndSocketConstraint. + Point-to-point constraint that attempts to keep the pivot point of two bodies in the same space. + Constraint pivot in Entity A space. + Constraint pivot in Entity B space. + + + + Serialization data for bhkPrismaticConstraint. + Creates a rail between two bodies that allows translation along a single axis with linear limits and a motor. + All three rotation axes and the remaining two translation axes are fixed. + + Pivot. + Rotation axis. + Plane normal. Describes the plane the object is able to move on. + Describes the axis the object is able to travel along. Unit vector. + Describes the axis the object is able to travel along in B coordinates. Unit vector. + Pivot in B coordinates. + Rotation axis. + Plane normal. Describes the plane the object is able to move on in B coordinates. + + + Describes the axis the object is able to travel along. Unit vector. + Rotation axis. + Plane normal. Describes the plane the object is able to move on. + Pivot. + Describes the axis the object is able to travel along in B coordinates. Unit vector. + Rotation axis. + Plane normal. Describes the plane the object is able to move on in B coordinates. + Pivot in B coordinates. + + Describe the min distance the object is able to travel. + Describe the max distance the object is able to travel. + Friction. + + + + + + bhkStiffSpringConstraint serialization data. Holds two bodies at a specified distance from one another. + + + + + + + Used to store skin weights in NiTriShapeSkinController. + The amount that this bone affects the vertex. + The index of the vertex that this weight applies to. + + + + + Determines how the raw image data is stored in NiRawImageData. + + + + + + Box Bounding Volume + + + + + + + Capsule Bounding Volume + + + + + + + + + + + + + Type of collision data. + + + + + + + + + + + + + + + + + + + Transformation data for the bone at this index in bhkPoseArray. + + + + + + + A list of transforms for each bone in bhkPoseArray. + + + + + + Array of Vectors for Decal placement in BSDecalPlacementVectorExtraData. + + Vector XYZ coords + Vector Normals + + + + Editor flags for the Body Partitions. + + + + + + Body part list for DismemberSkinInstance + Flags related to the Body Partition + Body Part Index + + + + Stores Bone Level of Detail info in a BSBoneLODExtraData + + + + + + hkpBSMaterial, a subclass of hkpMeshMaterial. hkpMeshMaterial is a base class for material info for hkMeshShapes. + + + + + + Bethesda extension of hkpCompressedMeshShape::BigTriangle. Triangles that don't fit the maximum size. + + + + + + + Bethesda extension of hkQsTransform. The scale vector is not serialized. + A vector that moves the chunk by the specified amount. W is not used. + Rotation. Reference point for rotation is bhkRigidBody translation. + + + + Bethesda extension of hkpCompressedMeshShape::Chunk. A compressed chunk of hkpCompressedMeshShape geometry. + + Index of material in bhkCompressedMeshShapeData::Chunk Materials + Index of another chunk in the chunks list. + Index of transformation in bhkCompressedMeshShapeData::Chunk Transforms + + + + + + + + + + + + bhkMalleableConstraint serialization data. A constraint wrapper used to soften or harden constraints. + Type of constraint. + + + + + + + + + + + + + + A constraint wrapper for polymorphic hkpConstraintData serialization. + Type of constraint. + + + + + + + + + + + + Abstract object type. + + + + Unknown. + + + + + + + + + Unknown. Only found in 2.3 nifs. + Name of this object. + + + + + + A count. + + + + + Unknown! + + + + + Unknown! + + + + + Unknown! + + + + + Unknown! + + + + + + LEGACY (pre-10.1). Abstract base class for particle system modifiers. + Next particle modifier. + Points to the particle system controller parent. + + + + Particle system collider. + Amount of bounce for the collider. + Spawn particles on impact? + Kill particles on impact? + Spawner to use for the collider. + Link to parent. + The next collider. + The object whose position and orientation are the basis of the collider. + + + + + + + Bethesda extension of hkReferencedObject, the base for all classes in the Havok SDK. + + + + Bethesda class to combine NiObject and hkReferencedObject so that Havok classes can be read/written with NiStream. + + + + hkWorldObjectCinfo::Property struct + + + + + + + + + + + + + + Bethesda extension of hkpWorldObject, the base class for hkpEntity and hkpPhantom. + The shape for this collision object. + + + + + + + Bethesda extension of hkpPhantom. A Phantom is the core non-physical object in the system. + Receives events when an overlap with another phantom or an entity begins or ends. + + + + Bethesda extension of hkpAabbPhantom. A non-physical object made up of only an AABB. + - Very fast as they use only broadphase collision detection. + - Used for triggers/regions where a shape is not necessary. + + + + + + Bethesda extension of hkpShapePhantom, a base for hkpSimpleShapePhantom and hkpCachingShapePhantom. + + + + Bethesda extension of hkpSimpleShapePhantom. A Phantom with arbitrary shape and transform. + Does not do any narrowphase caching, in contrast to hkpCachingShapePhantom. + + + + + + How the body reacts to collisions. See hkResponseType for hkpWorld default implementations. + + Lowers the frequency for processContactCallbacks. A value of 5 means that a callback is raised every 5th frame. The default is once every 65535 frames. + + + + Bethesda extension of hkpEntity. An Entity is the core physical object in the system. + + + + + + + + + + + + A vector that moves the body by the specified amount. Only enabled in bhkRigidBodyT objects. + The rotation Yaw/Pitch/Roll to apply to the body. Only enabled in bhkRigidBodyT objects. + Linear velocity. + Angular velocity. + Defines how the mass is distributed among the body, i.e. how difficult it is to rotate around any given axis. + The body's center of mass. + The body's mass in kg. A mass of zero represents an immovable object. + Reduces the movement of the body over time. A value of 0.1 will remove 10% of the linear velocity every second. + Reduces the movement of the body over time. A value of 0.05 will remove 5% of the angular velocity every second. + How smooth its surfaces is and how easily it will slide along other bodies. + + How "bouncy" the body is, i.e. how much energy it has after colliding. Less than 1.0 loses energy, greater than 1.0 gains energy. + If the restitution is not 0.0 the object will need extra CPU for all new collisions. + + Maximal linear velocity. + Maximal angular velocity. + + The maximum allowed penetration for this object. + This is a hint to the engine to see how much CPU the engine should invest to keep this object from penetrating. + A good choice is 5% - 20% of the smallest diameter of the object. + + Motion system? Overrides Quality when on Keyframed? + The initial deactivator type of the body. + How aggressively the engine will try to zero the velocity for slow objects. This does not save CPU. + The type of interaction with other objects. + + + + + + + + + + + + A vector that moves the body by the specified amount. Only enabled in bhkRigidBodyT objects. + The rotation Yaw/Pitch/Roll to apply to the body. Only enabled in bhkRigidBodyT objects. + Linear velocity. + Angular velocity. + Defines how the mass is distributed among the body, i.e. how difficult it is to rotate around any given axis. + The body's center of mass. + The body's mass in kg. A mass of zero represents an immovable object. + Reduces the movement of the body over time. A value of 0.1 will remove 10% of the linear velocity every second. + Reduces the movement of the body over time. A value of 0.05 will remove 5% of the angular velocity every second. + + + How smooth its surfaces is and how easily it will slide along other bodies. + + + How "bouncy" the body is, i.e. how much energy it has after colliding. Less than 1.0 loses energy, greater than 1.0 gains energy. + If the restitution is not 0.0 the object will need extra CPU for all new collisions. + + Maximal linear velocity. + Maximal angular velocity. + + The maximum allowed penetration for this object. + This is a hint to the engine to see how much CPU the engine should invest to keep this object from penetrating. + A good choice is 5% - 20% of the smallest diameter of the object. + + Motion system? Overrides Quality when on Keyframed? + + How aggressively the engine will try to zero the velocity for slow objects. This does not save CPU. + The type of interaction with other objects. + + + + + + + + + + + + A vector that moves the body by the specified amount. Only enabled in bhkRigidBodyT objects. + The rotation Yaw/Pitch/Roll to apply to the body. Only enabled in bhkRigidBodyT objects. + Linear velocity. + Angular velocity. + Defines how the mass is distributed among the body, i.e. how difficult it is to rotate around any given axis. + The body's center of mass. + The body's mass in kg. A mass of zero represents an immovable object. + Reduces the movement of the body over time. A value of 0.1 will remove 10% of the linear velocity every second. + Reduces the movement of the body over time. A value of 0.05 will remove 5% of the angular velocity every second. + + How smooth its surfaces is and how easily it will slide along other bodies. + + + How "bouncy" the body is, i.e. how much energy it has after colliding. Less than 1.0 loses energy, greater than 1.0 gains energy. + If the restitution is not 0.0 the object will need extra CPU for all new collisions. + + Maximal linear velocity. + Maximal angular velocity. + Motion system? Overrides Quality when on Keyframed? + + How aggressively the engine will try to zero the velocity for slow objects. This does not save CPU. + + + The maximum allowed penetration for this object. + This is a hint to the engine to see how much CPU the engine should invest to keep this object from penetrating. + A good choice is 5% - 20% of the smallest diameter of the object. + + + + + + + + + + + + + + + + This is the default body type for all "normal" usable and static world objects. The "T" suffix + marks this body as active for translation and rotation, a normal bhkRigidBody ignores those + properties. Because the properties are equal, a bhkRigidBody may be renamed into a bhkRigidBodyT and vice-versa. + + + + + + 1 = respond to wind + 1 = respond to wind + + + + The "T" suffix marks this body as active for translation and rotation. + + + + + Bethesda extension of hkpAction. hkpAction performs some action on a body or bodies during every simulation step. + + + + Bethesda extension of hkpUnaryAction. hkpUnaryAction performs an action on one body. + + + + + + Bethesda extension of hkpBinaryAction. hkpBinaryAction performs an action on two bodies. + + + + + + + Bethesda extension of hkpConstraintData. Base class for all constraints. + + + + + Bethesda extension of hkpLimitedHingeConstraintData. Hinge constraint with limits and a motor. + Enabling the motor will remove any friction. + + + + + Bethesda extension of hkpMalleableConstraintData. Constraint wrapper used to soften or harden constraints. + Does not affect angular limits or angular motors. + + + + + Bethesda extension of hkpStiffSpringConstraintData. Holds two bodies at a specified distance from one another. + + + + + Bethesda extension of hkpRagdollConstraintData. Creates a joint between two bodies with 3 degrees of freedom and a motor. + Enabling the motor will remove any friction. + The area of movement can be represented as a main cone + 2 orthogonal cones which may subtract from the main cone volume depending on limits. + + + + + Bethesda extension of hkpPrismaticConstraintData. + Creates a rail between two bodies that allows translation along a single axis with linear limits and a motor. + All three rotation axes and the remaining two translation axes are fixed. + + + + + Bethesda extension of hkpHingeConstraintData. A basic hinge with no angular limits or motor. + + + + + Bethesda extension of hkpBallAndSocketConstraintData. + Point-to-point constraint that attempts to keep the pivot point of two bodies in the same space. + + + + + Bethesda extension of hkpBallSocketChainData. A chain of ball and socket constraints. + Should equal (Num Chained Entities - 1) * 2 + Two pivot points A and B for each constraint. + High values are harder and more reactive, lower values are smoother. + Defines damping strength for the current velocity. + Restitution (amount of elasticity) of constraints. Added to the diagonal of the constraint matrix. A value of 0.0 can result in a division by zero with some chain configurations. + Maximum distance error in constraints allowed before stabilization algorithm kicks in. A smaller distance causes more resistance. + + + + + The base class for narrowphase collision detection objects. + All narrowphase collision detection is performed between pairs of bhkShape objects by creating appropriate collision agents. + + + + Contains a bhkShape and an additional transform for that shape. + The shape that this object transforms. + The material of the shape. + + + A transform matrix. + + + + An imaginary abstract base to solve inheritance issues in nif.xml with Havok serialization not matching class hierarchy. + + + + An interface that produces a set of spheres that represent a simplified version of the shape. + The material of the shape. + + + + An interface that allows testing convex sets using the GJK algorithm. Also holds a radius value for creating a shell. + The radius is used to create a thin shell that is used as the shape surface. + + + + An interface for a shape which can collide with an array of spheres. + The material of the shape. + + + + Contains a normal and distance from the origin, bounded by a given AABB. + + + Distance from the origin to the plane. + + + + + + A sphere. + + + + A cylinder. + + + + + + + + + A capsule. + + First point on the capsule's axis. + Matches first capsule radius. + Second point on the capsule's axis. + Matches second capsule radius. + + + + A box. + + A cube stored in Half Extents. A unit cube (1.0, 1.0, 1.0) would be stored as 0.5, 0.5, 0.5. + Unused as Havok stores the Half Extents as hkVector4 with the W component unused. + + + + A convex shape built from vertices. Note that if the shape is used in + a non-static object (such as clutter), then they will simply fall + through ground when they are under a bhkListShape. + + + Number of vertices. + Vertices. Fourth component is 0. Lexicographically sorted. + The number of half spaces. + Half spaces as determined by the set of vertices above. First three components define the normal pointing to the exterior, fourth component is the signed distance of the separating plane to the origin: it is minus the dot product of v and n, where v is any vertex on the separating plane, and n is the normal. Lexicographically sorted. + + + + Contains a bhkConvexShape and an additional transform for that shape. + The advantage of using bhkConvexTransformShape over bhkTransformShape is that it does not require additional agents to be created as it is itself convex. + The shape that this object transforms. + The material of the shape. + + + A transform matrix. + + + + + + + + + + + A compound shape made up of spheres. This is useful as an approximation for complex shapes, as collision detection for spheres is very fast. + However, if two bhkMultiSphereShape collide, every sphere needs to be checked against every other sphere. + Example: 10 spheres colliding with 10 spheres will result in 100 collision checks. + Therefore shapes like bhkCapsuleShape or bhkConvexVerticesShape should be preferred. + + + The spheres which make up the multi sphere shape. Max of 8. + + + + Bethesda extension of hkpBvTreeShape. hkpBvTreeShape adds a bounding volume tree to an hkpShapeCollection. + A bounding volume tree is useful for testing collision between a moving object and large static geometry. + The shape. + + + + Number of bytes for MOPP data. + + XYZ: Origin of the object in mopp coordinates. This is the minimum of all vertices in the packed shape along each axis, minus 0.1. + W: The scaling factor to quantize the MOPP: the quantization factor is equal to 256*256 divided by this number. + In Oblivion files, scale is taken equal to 256*256*254 / (size + 0.2) where size is the largest dimension of the bounding box of the packed shape. + + Tells if MOPP Data was organized into smaller chunks (PS3) or not (PC) + The tree of bounding volume data. + + + + Bethesda extension of hkpMoppBvTreeShape. hkpMoppBvTreeShape is a bounding volume tree using Havok-proprietary MOPP code. + + + + + + + + An interface to a collection of bhkShapes. + + + + A list of shapes. + + Shapes collected in a bhkListShape may not have the correct collision sound/FX due to HavokMaterial issues. + Do not put a bhkPackedNiTriStripsShape in the Sub Shapes. Use a separate collision nodes without a list shape for those. + + List of shapes. Max of 256. + The material of the shape. + + + + Always zeroed. Seemingly unused, or 0 for all values means no override. + + + + Bethesda extension of hkpMeshShape, but using NiTriStripsData instead of Havok storage. + Appears in one old Oblivion NIF, but only in certain distributions. NIF version 10.0.1.0 only. + + + + + + + + + + + + + Bethesda custom hkpShapeCollection using custom packed tri strips data. + + + + + + + + Same as radius + Same as scale. + + + + + Bethesda custom hkpShapeCollection using NiTriStripsData for geometry storage. + The material of the shape. + + + + + + + + + + + + A generic extra data object. + Name of this object. + Block number of the next extra data object. + The extra data was sometimes stored as binary directly on NiExtraData. + Ignore binary data after 4.x as the child block will cover it. + + + + Abstract base class for all interpolators of bool, float, NiQuaternion, NiPoint3, NiColorA, and NiQuatTransform data. + + + + Abstract base class for interpolators that use NiAnimationKeys (Key, KeyGrp) for interpolation. + + + + Animates a color value over time. + + + + + + Uses NiFloatKeys to animate a float value over time. + Pose value if lacking NiFloatData. + + + + + An interpolator for transform keyframes. + + + + + + Uses NiPosKeys to animate an NiPoint3 value over time. + Pose value if lacking NiPosData. + + + + + + + + Used to make an object follow a predefined spline path. + + -1 = Negative, 1 = Positive + Max angle in radians. + + 0, 1, or 2 representing X, Y, or Z. + + + + + + Uses NiBoolKeys to animate a bool value over time. + Pose value if lacking NiBoolData. + + + + + Uses NiBoolKeys to animate a bool value over time. + Unlike NiBoolInterpolator, it ensures that keys have not been missed between two updates. + + + + Flags for NiBlendInterpolator + + + + Interpolator item for array in NiBlendInterpolator. + Reference to an interpolator. + + + + + + + + + Abstract base class for all NiInterpolators that blend the results of sub-interpolators together to compute a final weighted value. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Abstract base class for interpolators storing data via a B-spline. + Animation start time. + Animation stop time. + + + + + + Extra Data for pre-3.0 versions + + + + + + + + + Abstract base class for NiObjects that support names, extra data, and time controllers. + Configures the main shader path + Name of this controllable object, used to refer to the object in .kf files. + + Extra data object index. (The first in a chain) + The number of Extra Data objects referenced through the list. + List of extra data indices. + Controller object index. (The first in a chain) + + + + This is the most common collision object found in NIF files. It acts as a real object that + is visible and possibly (if the body allows for it) interactive. The node itself + is simple, it only has three properties. + For this type of collision object, bhkRigidBody or bhkRigidBodyT is generally used. + Index of the AV object referring to this collision object. + + + + Collision box. + + + Use Alternate Bounding Volume. + + + + + bhkNiCollisionObject flags. + 0x100 and 0x200 are only for bhkBlendCollisionObject + + + + Abstract base class to merge NiCollisionObject with Bethesda Havok. + + OB-FO3: Add 0x28 (SET_LOCAL | USE_VEL) for ANIM_STATIC layer objects. + Post-FO3: Always add 0x80 (SYNC_ON_UPDATE). + + + + + + + + + + + + + + Primary Bethesda Havok object. + + + + Bethesda Havok object used in skeletons. + + + + + + + + Bethesda Havok object for phantoms such as bhkAabbPhantom. + + + + Bethesda Havok object for shape phantoms such as bhkSimpleShapePhantom. + + + + Abstract audio-visual base class from which all of Gamebryo's scene graph objects inherit. + + Basic flags for AV objects. For Bethesda streams above 26 only. + ALL: FO4 lacks the 0x80000 flag always. Skyrim lacks it sometimes. + BSTreeNode: 0x8080E (pre-FO4), 0x400E (FO4) + BSLeafAnimNode: 0x808000E (pre-FO4), 0x500E (FO4) + BSDamageStage, BSBlastNode: 0x8000F (pre-FO4), 0x2000000F (FO4) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Basic flags for AV objects. + The translation vector. + The rotation part of the transformation matrix. + Scaling part (only uniform scaling is supported). + Unknown function. Always seems to be (0, 0, 0) + + All rendering properties attached to this object. + + + + + + + + + Abstract base class for dynamic effects such as NiLights or projected texture effects. + If true, then the dynamic effect is applied to affected nodes during rendering. + + If a node appears in this list, then its entire subtree will be affected by the effect. + As of 4.0 the pointer hash is no longer stored alongside each NiObject on disk, yet this node list still refers to the pointer hashes. Cannot leave the type as Ptr because the link will be invalid. + + If a node appears in this list, then its entire subtree will be affected by the effect. + + + + Abstract base class that represents light sources in a scene graph. + For Bethesda Stream 130 (FO4), NiLight now directly inherits from NiAVObject. + Scales the overall brightness of all light components. + + + + + + + Abstract base class representing all rendering properties. Subclasses are attached to NiAVObjects to control their rendering. + + + + Unknown + + + + + The set order for each derived class of NiPSysModifier. + Note: For Skyrim, BSPSysStripUpdateModifier is 8000 and for FO3 it is 2500. + + + + Abstract base class for all particle system modifiers. + Used to locate the modifier. + + Modifier's priority in the particle modifier chain. + + + + + + + + + + + + + + + + + + + + + + + + + + + + NiParticleSystem parent of this modifier. + Whether or not the modifier is active. + + + + Abstract base class for all particle system emitters. + Speed / Inertia of particle movement. + Adds an amount of randomness to Speed. + Declination / First axis. + Declination randomness / First axis. + Planar Angle / Second axis. + Planar Angle randomness / Second axis . + Defines color of a birthed particle. + Size of a birthed particle. + Particle Radius randomness. + Duration until a particle dies. + Adds randomness to Life Span. + + + + Abstract base class for particle emitters that emit particles from a volume. + Node parent of this modifier? + + + + Abstract base class that provides the base timing and update functionality for all the Gamebryo animation controllers. + Index of the next controller. + + Frequency (is usually 1.0). + Phase (usually 0.0). + Controller start time. + Controller stop time. + Controller target (object index of the first controllable ancestor of this object). + + + + + + Abstract base class for all NiTimeController objects using NiInterpolator objects to animate their target objects. + + + + + DEPRECATED (20.6) + The number of target pointers that follow. + NiNode Targets to be controlled. + + + + DEPRECATED (20.5), replaced by NiMorphMeshModifier. + Time controller for geometry morphing. + + Geometry morphing data index. + + + + + + + + + + Unknown! Used by Daoc->'healing.nif'. + + + + Unknown! Used by Daoc. + This controller's data. + + + + Uses a single NiInterpolator to animate its target value. + + + + + DEPRECATED (10.2), RENAMED (10.2) to NiTransformController + A time controller object for animation key frames. + + + + + NiTransformController replaces NiKeyframeController. + + + + A particle system modifier controller. + NiInterpController::GetCtlrID() string format: + '%s' + Where %s = Value of "Modifier Name" + Used to find the modifier pointer. + + + + Particle system emitter controller. + NiInterpController::GetInterpolatorID() string format: + ['BirthRate', 'EmitterActive'] (for "Interpolator" and "Visibility Interpolator" respectively) + + + + + + A particle system modifier controller that animates a boolean value for particles. + + + + A particle system modifier controller that animates active/inactive state for particles. + + + + + A particle system modifier controller that animates a floating point value for particles. + + + + + Animates the declination value on an NiPSysEmitter object. + + + + Animates the declination variation value on an NiPSysEmitter object. + + + + Animates the size value on an NiPSysEmitter object. + + + + Animates the lifespan value on an NiPSysEmitter object. + + + + Animates the speed value on an NiPSysEmitter object. + + + + Animates the strength value of an NiPSysGravityModifier. + + + + + Abstract base class for all NiInterpControllers that use an NiInterpolator to animate their target float value. + + + + Changes the image a Map (TexDesc) will use. Uses a float interpolator to animate the texture index. + Often used for performing flipbook animation. + Target texture slot (0=base, 4=glow). + + + Time between two flips. + delta = (start_time - stop_time) / sources.num_indices + + + The texture sources. + The image sources + + + + Animates the alpha value of a property using an interpolator. + + + + + Used to animate a single member of an NiTextureTransform. + NiInterpController::GetCtlrID() string formats: + ['%1-%2-TT_TRANSLATE_U', '%1-%2-TT_TRANSLATE_V', '%1-%2-TT_ROTATE', '%1-%2-TT_SCALE_U', '%1-%2-TT_SCALE_V'] + (Depending on "Operation" enumeration, %1 = Value of "Shader Map", %2 = Value of "Texture Slot") + Is the target map a shader map? + The target texture slot. + Controls which aspect of the texture transform to modify. + + + + + Animates the dimmer value of an NiLight. + + + + Abstract base class for all NiInterpControllers that use a NiInterpolator to animate their target boolean value. + + + + Animates the visibility of an NiAVObject. + + + + + Abstract base class for all NiInterpControllers that use an NiInterpolator to animate their target NiPoint3 value. + + + + Time controller for material color. Flags are used for color selection in versions below 10.1.0.0. + Bits 4-5: Target Color (00 = Ambient, 01 = Diffuse, 10 = Specular, 11 = Emissive) + NiInterpController::GetCtlrID() string formats: + ['AMB', 'DIFF', 'SPEC', 'SELF_ILLUM'] (Depending on "Target Color") + Selects which color to control. + + + + + Animates the ambient, diffuse and specular colors of an NiLight. + NiInterpController::GetCtlrID() string formats: + ['Diffuse', 'Ambient'] (Depending on "Target Color") + + + + + + + Abstract base class for all extra data controllers. + NiInterpController::GetCtlrID() string format: + '%s' + Where %s = Value of "Extra Data Name" + + + + + Animates an NiColorExtraData object attached to an NiAVObject. + + + + Animates an NiFloatExtraData object attached to an NiAVObject. + NiInterpController::GetCtlrID() string format is same as parent. + Number of extra bytes. + + + + + + + Animates an NiFloatsExtraData object attached to an NiAVObject. + NiInterpController::GetCtlrID() string format: + '%s[%d]' + Where %s = Value of "Extra Data Name", %d = Value of "Floats Extra Data Index" + + + + + + Animates an NiFloatsExtraData object attached to an NiAVObject. + NiInterpController::GetCtlrID() string format: + '%s[%d]' + Where %s = Value of "Extra Data Name", %d = Value of "Floats Extra Data Index" + + + + + DEPRECATED (20.5), Replaced by NiSkinningLODController. + Level of detail controller for bones. Priority is arranged from low to high. + + Number of LODs. + Number of node arrays. + A list of node sets (each set a sequence of bones). + Number of shape groups. + List of shape groups. + The size of the second list of shape groups. + Group of NiTriShape indices. + + + + A simple LOD controller for bones. + + + + Shader. + The shader name. + Extra data associated with the shader. A value of -1 means the shader is the default implementation. + + The name of the material. + Extra data associated with the material. A value of -1 means the material is the default implementation. + The index of the currently active material. + Cyanide extension (Blood Bowl). + + Whether the materials for this object always needs to be updated before rendering with them. + + + + Describes a visible scene element with vertices like a mesh, a particle system, lines, etc. + Bethesda 20.2.0.7 NIFs: NiGeometry was changed to BSGeometry. + Most new blocks (e.g. BSTriShape) do not refer to NiGeometry except NiParticleSystem was changed to use BSGeometry. + This causes massive inheritance problems so the rows below are doubled up to exclude NiParticleSystem for Bethesda Stream 100+ + and to add data exclusive to BSGeometry. + + + + Data index (NiTriShapeData/NiTriStripData). + Data index (NiTriShapeData/NiTriStripData). + + + + + + + + + + Describes a mesh, built from triangles. + + + + Mesh data: vertices, vertex normals, etc. + Bethesda 20.2.0.7 NIFs: NiParticlesData no longer inherits from NiGeometryData and inherits NiObject directly. + "Num Vertices" is renamed to "BS Max Vertices" for Bethesda 20.2 because Vertices, Normals, Tangents, Colors, and UV arrays + do not have length for NiPSysData regardless of "Num" or booleans. + + Always zero. + Number of vertices. + Number of vertices. + Bethesda uses this for max number of particles in NiPSysData. + Used with NiCollision objects when OBB or TRI is set. + + Is the vertex array present? (Always non-zero.) + The mesh vertices. + + + + Do we have lighting normals? These are essential for proper lighting: if not present, the model will only be influenced by ambient light. + The lighting normals. + Tangent vectors. + Bitangent vectors. + + + + + Do we have vertex colors? These are usually used to fine-tune the lighting of the model. + + Note: how vertex colors influence the model can be controlled by having a NiVertexColorProperty object as a property child of the root node. If this property object is not present, the vertex colors fine-tune lighting. + + Note 2: set to either 0 or 0xFFFFFFFF for NifTexture compatibility. + + The vertex colors. + The lower 6 bits of this field represent the number of UV texture sets. The rest is unused. + + Do we have UV coordinates? + + Note: for compatibility with NifTexture, set this value to either 0x00000000 or 0xFFFFFFFF. + + The UV texture coordinates. They follow the OpenGL standard: some programs may require you to flip the second coordinate. + Consistency Flags + + + + + + + + Describes a mesh, built from triangles. + Number of triangles. + + + + Unknown. Is apparently only used in skeleton.nif files. + Seems to be always zero. + + + + Bethesda-specific collision bounding box for skeletons. + Center of the bounding box. + Dimensions of the bounding box from center. + + + + Unknown. Marks furniture sitting positions? + + + + + + Particle modifier that adds a percentage of object space translation and rotation to particles born in world space. + Amount of blending? + + + + Particle emitter that uses a node, its children and subchildren to emit from. Emission will be evenly spread along points from nodes leading to their direct parents/children only. + + + + Particle Modifier that uses the wind value from the gamedata to alter the path of particles. + The amount of force wind will have on particles. + + + + Bethesda custom tri strips data block for bhkPackedNiTriStripsShape. + + + + + + + Compression on read may not be supported. Vertices may be packed in ushort that are not IEEE standard half-precision. + + + + + + + Transparency. Flags 0x00ED. + + Threshold for alpha testing + + + + + + Ambient light source. + + + + + + + Generic rotating particles data object. + Bethesda 20.2.0.7 NIFs: NiParticlesData no longer inherits from NiGeometryData and inherits NiObject directly. + The maximum number of particles (matches the number of vertices). + The particles' size. + Is the particle size array present? + The individual particle sizes. + The number of active particles at the time the system was saved. This is also the number of valid entries in the following arrays. + Is the particle size array present? + The individual particle sizes. + Is the particle rotation array present? + The individual particle rotations. + Are the angles of rotation present? + Angles of rotation + Are axes of rotation present? + Axes of rotation. + + + How many quads to use in BSPSysSubTexModifier for texture atlasing + 2,4,8,16,32,64 are potential values. If "Has" was no then this should be 256, which represents a 16x16 framed image, which is invalid + Defines UV offsets + Sets aspect ratio for Subtexture Offset UV quads + + + + + + + + Rotating particles data object. + Is the particle rotation array present? + The individual particle rotations. + + + + Particle system data object (with automatic normals?). + + + + Particle system data. + + + + + + + + + + Particle meshes data. + + + + + + + + + Binary extra data object. Used to store tangents and bitangents in Oblivion. + The binary data. + + + + Voxel extra data object. + + + + + Voxel data object. + + + + + + + + + + + + + + Blends bool values together. + The pose value. Invalid if using data. + + + + Blends float values together. + The pose value. Invalid if using data. + + + + Blends NiPoint3 values together. + The pose value. Invalid if using data. + + + + Blends NiQuatTransform values together. + + + + + Wrapper for boolean animation keys. + The boolean keys. + + + + Boolean extra data. + The boolean extra data value. + + + + Contains an NiBSplineBasis for use in interpolation of open, uniform B-Splines. + The number of control points of the B-spline (number of frames of animation plus degree of B-spline minus one). + + + + Uses B-Splines to animate a float value over time. + Base value when curve not defined. + Handle into the data. (USHRT_MAX for invalid handle.) + + + + NiBSplineFloatInterpolator plus the information required for using compact control points. + + + + + + Uses B-Splines to animate an NiPoint3 value over time. + Base value when curve not defined. + Handle into the data. (USHRT_MAX for invalid handle.) + + + + NiBSplinePoint3Interpolator plus the information required for using compact control points. + + + + + + Supports the animation of position, rotation, and scale using an NiQuatTransform. + The NiQuatTransform can be an unchanging pose or interpolated from B-Spline control point channels. + + Handle into the translation data. (USHRT_MAX for invalid handle.) + Handle into the rotation data. (USHRT_MAX for invalid handle.) + Handle into the scale data. (USHRT_MAX for invalid handle.) + + + + NiBSplineTransformInterpolator plus the information required for using compact control points. + + + + + + + + + + + + + Contains one or more sets of control points for use in interpolation of open, uniform B-Splines, stored as either float or compact. + + Float values representing the control data. + + Signed shorts representing the data from 0 to 1 (scaled by SHRT_MAX). + + + + Camera object. + Obsolete flags. + Frustrum left. + Frustrum right. + Frustrum top. + Frustrum bottom. + Frustrum near. + Frustrum far. + Determines whether perspective is used. Orthographic means no perspective. + Viewport left. + Viewport right. + Viewport top. + Viewport bottom. + Level of detail adjust. + + Deprecated. Array is always zero length on disk write. + Deprecated. Array is always zero length on disk write. + + + + + Wrapper for color animation keys. + The color keys. + + + + Extra data in the form of NiColorA (red, green, blue, alpha). + RGBA Color? + + + + Controls animation sequences on a specific branch of the scene graph. + Whether transformation accumulation is enabled. If accumulation is not enabled, the manager will treat all sequence data on the accumulation root as absolute data instead of relative delta values. + + + + + + + Root node in NetImmerse .kf files (until version 10.0). + The sequence name by which the animation system finds and manages this sequence. + The name of the NiAVObject serving as the accumulation root. This is where all accumulated translations, scales, and rotations are applied. + + + + + + + + + + + Root node in Gamebryo .kf files (version 10.0.1.0 and up). + The weight of a sequence describes how it blends with other sequences at the same priority. + + + + + + + + + The owner of this sequence. + The name of the NiAVObject serving as the accumulation root. This is where all accumulated translations, scales, and rotations are applied. + + + + + + + + + Abstract base class for indexing NiAVObject by name. + + + + NiAVObjectPalette implementation. Used to quickly look up objects by name. + Scene root of the object palette. + Number of objects. + The objects. + + + + Directional light source. + + + + NiDitherProperty allows the application to turn the dithering of interpolated colors and fog values on and off. + + + + + DEPRECATED (10.2), REMOVED (20.5). Replaced by NiTransformController and NiLookAtInterpolator. + The data for the controller. + + + + Wrapper for 1D (one-dimensional) floating point animation keys. + The keys. + + + + Extra float data. + The float data. + + + + Extra float array data. + Number of floats in the next field. + Float data. + + + + NiFogProperty allows the application to enable, disable and control the appearance of fog. + + Depth of the fog in normalized units. 1.0 = begins at near plane. 0.5 = begins halfway between the near and far planes. + The color of the fog. + + + + LEGACY (pre-10.1) particle modifier. Applies a gravitational field on the particles. + + The strength/force of this gravity. + The force field type. + The position of the mass point relative to the particle system. + The direction of the applied acceleration. + + + + + + Extra integer data. + The value of the extra data. + + + + Controls animation and collision. Integer holds flags: + Bit 0 : enable havok, bAnimated(Skyrim) + Bit 1 : enable collision, bHavok(Skyrim) + Bit 2 : is skeleton nif?, bRagdoll(Skyrim) + Bit 3 : enable animation, bComplex(Skyrim) + Bit 4 : FlameNodes present, bAddon(Skyrim) + Bit 5 : EditorMarkers present, bEditorMarker(Skyrim) + Bit 6 : bDynamic(Skyrim) + Bit 7 : bArticulated(Skyrim) + Bit 8 : bIKTarget(Skyrim)/needsTransformUpdates + Bit 9 : bExternalEmit(Skyrim) + Bit 10: bMagicShaderParticles(Skyrim) + Bit 11: bLights(Skyrim) + Bit 12: bBreakable(Skyrim) + Bit 13: bSearchedBreakable(Skyrim) .. Runtime only? + + + + Extra integer array data. + Number of integers. + Integers. + + + + An extended keyframe controller. + A link to more keyframe data. + + + + DEPRECATED (10.2), RENAMED (10.2) to NiTransformData. + Wrapper for transformation animation keys. + The number of quaternion rotation keys. If the rotation type is XYZ (type 4) then this *must* be set to 1, and in this case the actual number of keys is stored in the XYZ Rotations field. + The type of interpolation to use for rotation. Can also be 4 to indicate that separate X, Y, and Z values are used for the rotation instead of Quaternions. + The rotation keys if Quaternion rotation is used. + + Individual arrays of keys for rotating X, Y, and Z individually. + Translation keys. + Scale keys. + + + + + + + + + + DEPRECATED (10.2), REMOVED (20.5) + Replaced by NiTransformController and NiLookAtInterpolator. + + + + + + NiLookAtInterpolator rotates an object so that it always faces a target object. + + + + + + + + + + + Describes the surface properties of an object e.g. translucency, ambient color, diffuse color, emissive color, and specular color. + Property flags. + How much the material reflects ambient light. + How much the material reflects diffuse light. + How much light the material reflects in a specular manner. + How much light the material emits. + The material glossiness. + The material transparency (1=non-transparant). Refer to a NiAlphaProperty object in this material's parent NiTriShape object, when alpha is not 1. + + + + + DEPRECATED (20.5), replaced by NiMorphMeshModifier. + Geometry morphing data. + Number of morphing object. + Number of vertices. + This byte is always 1 in all official files. + The geometry morphing objects. + + + + Generic node object for grouping. + The number of child objects. + List of child node object indices. + The number of references to effect objects that follow. + List of node effects. ADynamicEffect? + + + + A NiNode used as a skeleton bone? + + + + Found in Munch's Oddysee + + + + Morrowind specific. + + + + Firaxis-specific UI widgets? + + + + + + Unknown. + + + + Unknown. + + + + Number of unknown links. + Unknown pointers to other buttons. Maybe other buttons in a group so they can be switch off if this one is switched on? + + + + These nodes will always be rotated to face the camera creating a billboard effect for any attached objects. + + In pre-10.1.0.0 the Flags field is used for BillboardMode. + Bit 0: hidden + Bits 1-2: collision mode + Bit 3: unknown (set in most official meshes) + Bits 5-6: billboard mode + + Collision modes: + 00 NONE + 01 USE_TRIANGLES + 10 USE_OBBS + 11 CONTINUE + + Billboard modes: + 00 ALWAYS_FACE_CAMERA + 01 ROTATE_ABOUT_UP + 10 RIGID_FACE_CAMERA + 11 ALWAYS_FACE_CENTER + The way the billboard will react to the camera. + + + + Bethesda-specific extension of Node with animation properties stored in the flags, often 42? + + + + Unknown. + + + + Flags for NiSwitchNode. + + + + + + Represents groups of multiple scenegraph subtrees, only one of which (the "active child") is drawn at any given time. + + + + + + Level of detail selector. Links to different levels of detail of the same model, used to switch a geometry at a specified distance. + + + + + + + + NiPalette objects represent mappings from 8-bit indices to 24-bit RGB or 32-bit RGBA colors. + Not boolean but used as one, always 8-bit. + The number of palette entries. Always 256 but can also be 16. + The color palette. + The color palette. + + + + LEGACY (pre-10.1) particle modifier. + + + + + + + The position of the mass point relative to the particle system? + The direction of the applied acceleration? + + + + LEGACY (pre-10.1) particle modifier. + + + + + LEGACY (pre-10.1) particle modifier. + The time from the beginning of the particle lifetime during which the particle grows. + The time from the end of the particle lifetime during which the particle fades. + + + + LEGACY (pre-10.1) particle modifier. + + + + + + LEGACY (pre-10.1) particle modifier. + + + + + + + Generic particle system node. + + + + LEGACY (pre-10.1). NiParticles which do not house normals and generate them at runtime. + + + + LEGACY (pre-10.1). Particle meshes. + + + + LEGACY (pre-10.1). Particle meshes data. + + + + + A particle system. + Contains members to mimic inheritance shifts for Bethesda 20.2, where NiParticles switched to inheriting BSGeometry. + Until inheritance shifts are supported, the members are on NiParticleSystem instead of NiParticles for module reasons. + + + + + + + + If true, Particles are birthed into world space. If false, Particles are birthed into object space. + The number of modifier references. + The list of particle modifiers. + + + + Particle system. + + + + + + + + + A generic particle system time controller object. + Particle speed in old files + Particle speed + Particle random speed modifier + + vertical emit direction [radians] + 0.0 : up + 1.6 : horizontal + 3.1416 : down + + emitter's vertical opening angle [radians] + horizontal emit direction + emitter's horizontal opening angle + + + Particle size + Particle emit start time + Particle emit stop time + + Particle emission rate in old files + Particle emission rate (particles per second) + Particle lifetime + Particle lifetime random modifier + + + + The object which acts as the basis for the particle emitter. + + + + + + Particle velocity + + The particle's age. + + Timestamp of the last update. + Unknown short + Particle/vertex index matches array index + + Size of the following array. (Maximum number of simultaneous active particles) + Number of valid entries in the following array. (Number of active particles at the time the system was saved) + + + Link to some optional particle modifiers (NiGravity, NiParticleGrowFade, NiParticleBomb, ...) + + + + + + + + + A particle system controller, used by BS in conjunction with NiBSParticleNode. + + + + DEPRECATED (10.2), REMOVED (20.5). Replaced by NiTransformController and NiPathInterpolator. + Time controller for a path. + + -1 = Negative, 1 = Positive + Max angle in radians. + + 0, 1, or 2 representing X, Y, or Z. + + + + + + Component Type + Data Storage Convention + Bits per component + + + + + NiPixelFormat is not the parent to NiPixelData/NiPersistentSrcTextureRendererData, + but actually a member class loaded at the top of each. The two classes are not related. + However, faking this inheritance is useful for several things. + The format of the pixels in this internally stored image. + + 0x000000ff (for 24bpp and 32bpp) or 0x00000000 (for 8bpp) + 0x0000ff00 (for 24bpp and 32bpp) or 0x00000000 (for 8bpp) + 0x00ff0000 (for 24bpp and 32bpp) or 0x00000000 (for 8bpp) + 0xff000000 (for 32bpp) or 0x00000000 (for 24bpp and 8bpp) + Bits per pixel, 0 (Compressed), 8, 24 or 32. + + [96,8,130,0,0,65,0,0] if 24 bits per pixel + [129,8,130,32,0,65,12,0] if 32 bits per pixel + [34,0,0,0,0,0,0,0] if 8 bits per pixel + [X,0,0,0,0,0,0,0] if 0 (Compressed) bits per pixel where X = PixelFormat + + Seems to always be zero. + Bits per pixel, 0 (Compressed), 8, 24 or 32. + + + + + + Channel Data + + + + + + + + + + + + + + + + + A texture. + + + + + + + + + + + + + + + + + + + LEGACY (pre-10.1) particle modifier. + + + + + + + + + + A point light. + + + + + + + Wrapper for position animation keys. + + + + + Wrapper for rotation animation keys. + + + + + + + + Particle modifier that controls and updates the age of particles in the system. + Should the particles spawn on death? + The spawner to use on death. + + + + Particle modifier that applies an explosive force to particles. + The object whose position and orientation are the basis of the force. + The local direction of the force. + How the bomb force will decrease with distance. + The acceleration the bomb will apply to particles. + + + + + + Particle modifier that creates and updates bound volumes. + Optimize by only computing the bound of (1 / Update Skip) of the total particles each frame. + + + + Particle emitter that uses points within a defined Box shape to emit from. + + + + + + + Particle modifier that adds a defined shape to act as a collision object for particles to interact with. + + + + + Particle modifier that adds keyframe data to modify color/alpha values of particles over time. + + + + + Particle emitter that uses points within a defined Cylinder shape to emit from. + + + + + + Particle modifier that applies a linear drag force to particles. + The object whose position and orientation are the basis of the force. + The local direction of the force. + The amount of drag to apply to particles. + The distance up to which particles are fully affected. + The distance at which particles cease to be affected. + + + + DEPRECATED (10.2). Particle system emitter controller data. + + + + + + + Particle modifier that applies a gravitational force to particles. + The object whose position and orientation are the basis of the force. + The local direction of the force. + How the force diminishes by distance. + The acceleration of the force. + The type of gravitational force. + Adds a degree of randomness. + Scale for turbulence. + + + + + Particle modifier that controls the time it takes to grow and shrink a particle. + The time taken to grow from 0 to their specified size. + Specifies the particle generation to which the grow effect should be applied. This is usually generation 0, so that newly created particles will grow. + The time taken to shrink from their specified size to 0. + Specifies the particle generation to which the shrink effect should be applied. This is usually the highest supported generation for the particle system. + A multiplier on the base particle scale. + + + + Particle emitter that uses points on a specified mesh to emit from. + + The meshes which are emitted from. + The method by which the initial particle velocity will be computed. + The manner in which particles are emitted from the Emitter Meshes. + The emission axis if VELOCITY_USE_DIRECTION. + + + + Particle modifier that updates mesh particles using the age of each particle. + + + + + + + + + + + + + + + + + + + + + + + Similar to a Flip Controller, this handles particle texture animation on a single texture atlas + Starting frame/position on atlas + Random chance to start on a different frame? + Ending frame/position on atlas + Frame to start looping + + + + + + + Particle Collider object which particles will interact with. + Width of the plane along the X Axis. + Height of the plane along the Y Axis. + Axis defining a plane, relative to Collider Object. + Axis defining a plane, relative to Collider Object. + + + + Particle Collider object which particles will interact with. + + + + + Particle modifier that updates the particle positions based on velocity and last update time. + + + + Particle modifier that calls reset on a target upon looping. + + + + Particle modifier that adds rotations to particles. + Initial Rotation Speed in radians per second. + Distributes rotation speed over the range [Speed - Variation, Speed + Variation]. + + + Initial Rotation Angle in radians. + Distributes rotation angle over the range [Angle - Variation, Angle + Variation]. + Randomly negate the initial rotation speed? + Assign a random axis to new particles? + Initial rotation axis. + + + + Particle modifier that spawns additional copies of a particle. + Number of allowed generations for spawning. Particles whose generations are >= will not be spawned. + The likelihood of a particular particle being spawned. Must be between 0.0 and 1.0. + The minimum particles to spawn for any given original particle. + The maximum particles to spawn for any given original particle. + How much the spawned particle speed can vary. + How much the spawned particle direction can vary. + Lifespan assigned to spawned particles. + The amount the lifespan can vary. + + + + + WorldShift-specific Particle Spawn modifier + + Default of FLT_MIN would indicate End Time but it seems more like Frequency/Tick Rate or Start Time. + + + + + Particle emitter that uses points within a sphere shape to emit from. + + + + + Particle system controller, tells the system to update its simulation. + + + + Base for all force field particle modifiers. + The object whose position and orientation are the basis of the field. + Magnitude of the force. + How the magnitude diminishes with distance from the Field Object. + Whether or not to use a distance from the Field Object after which there is no effect. + Maximum distance after which there is no effect. + + + + Particle system modifier, implements a vortex field force for particles. + Direction of the vortex field in Field Object's space. + + + + Particle system modifier, implements a gravity field force for particles. + Direction of the gravity field in Field Object's space. + + + + Particle system modifier, implements a drag field force for particles. + Whether or not the drag force applies only in the direction specified. + Direction in which the force applies if Use Direction is true. + + + + Particle system modifier, implements a turbulence field force for particles. + How many turbulence updates per second. + + + + + + + + + + + + + + + + + Particle system controller for force field magnitude. + + + + Particle system controller for force field attenuation. + + + + Particle system controller for force field maximum distance. + + + + Particle system controller for air field air friction. + + + + Particle system controller for air field inherit velocity. + + + + Particle system controller for air field spread. + + + + Particle system controller for emitter initial rotation speed. + + + + Particle system controller for emitter initial rotation speed variation. + + + + Particle system controller for emitter initial rotation angle. + + + + Particle system controller for emitter initial rotation angle variation. + + + + Particle system controller for emitter planar angle. + + + + Particle system controller for emitter planar angle variation. + + + + Particle system modifier, updates the particle velocity to simulate the effects of air movements like wind, fans, or wake. + Direction of the particle velocity + How quickly particles will accelerate to the magnitude of the air field. + How much of the air field velocity will be added to the particle velocity. + + + + The angle of the air field cone if Enable Spread is true. + + + + Guild 2-Specific node + + + + + + + + + + + Unknown controller + + + + Particle system modifier, updates the particle velocity to simulate the effects of point gravity. + If zero, no attenuation. + + + + Abstract class used for different types of LOD selections. + + + + NiRangeLODData controls switching LOD levels based on Z depth from the camera to the NiLODNode. + + + + + + + NiScreenLODData controls switching LOD levels based on proportion of the screen that a bound would include. + + + + + + + + Unknown. + + + + DEPRECATED (pre-10.1), REMOVED (20.3). + Keyframe animation root node, in .kf files. + + + + Determines whether flat shading or smooth shading is used on a shape. + + + + + Skinning data. + Offset of the skin from this bone in bind position. + Number of bones. + This optionally links a NiSkinPartition for hardware-acceleration information. + Enables Vertex Weights for this NiSkinData. + Contains offset data for each node that this skin is influenced by. + + + + Skinning instance. + Skinning data reference. + Refers to a NiSkinPartition objects, which partitions the mesh such that every vertex is only influenced by a limited number of bones. + Armature root node. + The number of node bones referenced as influences. + List of all armature bones. + + + + Old version of skinning instance. + The number of node bones referenced as influences. + The number of vertex weights stored for each bone. + List of all armature bones. + Contains skin weight data for each node that this skin is influenced by. + + + + Skinning data, optimized for hardware skinning. The mesh is partitioned in submeshes such that each vertex of a submesh is influenced only by a limited and fixed number of bones. + + + + + + + + + + A texture. + + + + NiTexture::FormatPrefs. These preferences are a request to the renderer to use a format the most closely matches the settings and may be ignored. + Requests the way the image will be stored. + Requests if mipmaps are used or not. + Requests no alpha, 1-bit alpha, or + + + + Describes texture source and properties. + Is the texture external? + + The external texture file name. + The original source filename of the image embedded by the referred NiPixelData object. + + NiPixelData or NiPersistentSrcTextureRendererData + NiPixelData or NiPersistentSrcTextureRendererData + A set of preferences for the texture format. They are a request only and the renderer may ignore them. + If set, then the application cannot assume that any dynamic changes to the pixel data will show in the rendered image. + A hint to the renderer that the texture can be loaded directly from a texture file into a renderer-specific resource, bypassing the NiPixelData object. + Pixel Data is NiPersistentSrcTextureRendererData instead of NiPixelData. + + + + Gives specularity to a shape. Flags 0x0001. + + + + + LEGACY (pre-10.1) particle modifier. + + + + + + A spot. + + + Describes the distribution of light. + + + + Allows control of stencil testing. + Property flags. + Enables or disables the stencil test. + Selects the compare mode function. + + A bit mask. The default is 0xffffffff. + + + + Used to enabled double sided faces. Default is 3 (DRAW_BOTH). + + + A bit mask. The default is 0xffffffff. + + + + Extra data in the form of text. + Used in various official or user-defined ways, e.g. preventing optimization on objects ("NiOptimizeKeep", "sgoKeep"). + The string. + + + + List of 0x00-seperated strings, which are names of controlled objects and controller types. Used in .kf files in conjunction with NiControllerSequence. + A bunch of 0x00 seperated strings. + + + + Extra data in the form of a list of strings. + Number of strings. + The strings. + + + + Extra data that holds an array of NiTextKey objects for use in animation sequences. + The number of text keys that follow. + List of textual notes and at which time they take effect. Used for designating the start and stop of animations and the triggering of sounds. + + + + Represents an effect that uses projected textures such as projected lights (gobos), environment maps, and fog maps. + Model projection matrix. Always identity? + Model projection translation. Always (0,0,0)? + Texture Filtering mode. + + Texture Clamp mode. + The type of effect that the texture is used for. + The method that will be used to generate UV coordinates for the texture effect. + Image index. + Source texture index. + Determines whether a clipping plane is used. Always 8-bit. + + + + + + + + LEGACY (pre-10.1) + + Either 210 or 194. + + + + + + LEGACY (pre-10.1) + 0 if the texture is internal to the NIF file. + The filepath to the texture. + Link to the internally stored image data. + + + + + + LEGACY (pre-10.1) + + Property flags. + Link to the texture image. + + + + + Describes how a fragment shader should be configured for a given piece of geometry. + Property flags. + Property flags. + Determines how the texture will be applied. Seems to have special functions in Oblivion. + Number of textures. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Wrapper for transformation animation keys. + + + + A shape node that refers to singular triangle data. + + + + Holds mesh data using a list of singular triangles. + Num Triangles times 3. + Do we have triangle data? + Triangle data. + Triangle face data. + Number of shared normals groups. + The shared normals. + + + + A shape node that refers to data organized into strips of triangles + + + + Holds mesh data using strips of triangles. + + The number of points in each triangle strip. + Do we have strip point data? + The points in the Triangle strips. Size is the sum of all entries in Strip Lengths. + The points in the Triangle strips. Size is the sum of all entries in Strip Lengths. + + + + Unknown + + + The number of child objects. + List of child node object indices. + + + + + + Holds mesh data using a list of singular triangles. + + + + LEGACY (pre-10.1) + Sub data of NiBezierMesh + + + + + + + + + + + + + LEGACY (pre-10.1) + Unknown + + + + + + + + + + + + + + + A shape node that holds continuous level of detail information. + Seems to be specific to Freedom Force. + Found in: Freedom Force, Spellbinder + + + + Holds mesh data for continuous level of detail shapes. + Presumably a progressive mesh with triangles specified by edge splits. + Found in: Freedom Force, Spellbinder + + + + + + + + + + + + + A copy of NiSkinInstance for use with NiClod meshes. + Found in: Freedom Force, Spellbinder + + + + DEPRECATED (pre-10.1), REMOVED (20.3). + Time controller for texture coordinates. + + Texture coordinate controller data index. + + + + DEPRECATED (pre-10.1), REMOVED (20.3) + Texture coordinate data. + + Four UV data groups. Appear to be U translation, V translation, U scaling/tiling, V scaling/tiling. + + + + + DEPRECATED (20.5). + Extra data in the form of a vector (as x, y, z, w components). + The vector data. + + + + Property of vertex colors. This object is referred to by the root object of the NIF file whenever some NiTriShapeData object has vertex colors with non-default settings; if not present, vertex colors have vertex_mode=2 and lighting_mode=1. + + In Flags from 20.1.0.3 on. + In Flags from 20.1.0.3 on. + + + + DEPRECATED (10.x), REMOVED (?) + Not used in skinning. + Unsure of use - perhaps for morphing animation or gravity. + Number of vertices. + The vertex weights. + + + + DEPRECATED (10.2), REMOVED (?), Replaced by NiBoolData. + Visibility data for a controller. + + + + + + Allows applications to switch between drawing solid geometry or wireframe outlines. + + + + + Allows applications to set the test and write modes of the renderer's Z-buffer and to set the comparison function used for the Z-buffer test. + + + Z-Test function. In Flags from 20.1.0.3 on. + + + + + Morrowind-specific node for collision mesh. + + + + LEGACY (pre-10.1) + Raw image data. + Image width + Image height + The format of the raw image data. + Image pixel data. + Image pixel data. + + + + + + Used to turn sorting off for individual subtrees in a scene. Useful if objects must be drawn in a fixed order. + Sorting + + + + + Represents cube maps that are created from either a set of six image files, six blocks of pixel data, or a single pixel data with six faces. + + + + + + Object which manages a NxScene object and the Gamebryo objects that interact with it. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Object which caches the properties of NxScene, stored as a snapshot in a NiPhysXScene. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A PhysX prop which holds information about PhysX actors in a Gamebryo scene + + + + + + + + + + + + + + For serialization of PhysX objects and to attach them to the scene. + + + + + + + + + + + + + + + For serializing NxActor objects. + + + + + + + + + + + + + + + + + + + + + + + + + + For serializing NxBodyDesc objects. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A PhysX Joint abstract base class. + + + + + + + + + + + + + + + A 6DOF (6 degrees of freedom) joint. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + For serializing NxShapeDesc objects + + + + + + + + + + + + + + + + + + + + Holds mesh data for streaming. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + For serializing NxMaterialDesc objects. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + For serializing NxClothDesc objects. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A destination is a link between a PhysX actor and a Gamebryo object being driven by the physics. + + + + + + Base for destinations that set a rigid body state. + + + + Connects PhysX rigid body actors to a scene node. + + + + + A source is a link between a Gamebryo object and a PhysX actor. + + + + + + Sets state of a rigid body PhysX actor. + + + + + Sets state of kinematic PhysX actor. + + + + Sends Gamebryo scene state to a PhysX dynamic actor. + + + + Wireframe geometry. + + + + Wireframe geometry data. + Is vertex connected to other (next?) vertex? + + + + Two dimensional screen elements. + + Offset in vertex array. + + Offset in indices array. + + + + DEPRECATED (20.5), functionality included in NiMeshScreenElements. + Two dimensional screen elements. + + + + + + + + + + + + + DEPRECATED (20.5), replaced by NiMeshScreenElements. + Two dimensional screen elements. + + + + NiRoomGroup represents a set of connected rooms i.e. a game level. + Object that represents the room group as seen from the outside. + + + + + + + + + + NiRoom objects represent cells in a cell-portal culling system. + + + + + The portals which see into the room. + + The portals which see out of the room. + + All geometry associated with the room. Seems to be Ref for legacy. + + + + NiPortal objects are grouping nodes that support aggressive visibility culling. + They represent flat polygonal regions through which a part of a scene graph can be viewed. + + Unused in 20.x, possibly also 10.x. + + + Root of the scenegraph which is to be seen through this portal. + + + + Bethesda-specific fade node. + + + + FO3 Shader Type + + + + + + + + + + + + + Shader Property Flags + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Shader Property Flags 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bethesda-specific property. + + + + Scales the intensity of the environment/cube map. + + + + Bethesda-specific property. + How to handle texture borders. + + + + Bethesda-specific property. + The texture glow map. + At this cosine of angle falloff will be equal to Falloff Start Opacity + At this cosine of angle falloff will be equal to Falloff Stop Opacity + Alpha falloff multiplier at start angle + Alpha falloff multiplier at end angle + + + + Bethesda-specific property. + Texture Set + The amount of distortion. **Not based on physically accurate refractive index** (0=none) (0-1) + Rate of texture movement for refraction shader. + The number of passes the parallax shader can apply. + The strength of the parallax. + + Glow color and alpha + + + + This controller is used to animate float variables in BSEffectShaderProperty. + Which float variable in BSEffectShaderProperty to animate. + + + + This controller is used to animate colors in BSEffectShaderProperty. + Which color in BSEffectShaderProperty to animate. + + + + This controller is used to animate float variables in BSLightingShaderProperty. + Which float variable in BSLightingShaderProperty to animate. + + + + This controller is used to animate float variables in BSLightingShaderProperty. + NOTE: FO4 CK reports this block as unsupported. + NOTE: FO4 CK reports this as unsupported. Which integral variable in BSLightingShaderProperty to animate. + + + + This controller is used to animate colors in BSLightingShaderProperty. + Which color in BSLightingShaderProperty to animate. + + + + + + Skyrim, Paired with dummy TriShapes, this controller generates lightning shapes for special effects. + First interpolator controls Generation. + References generation interpolator. + References interpolator for Mutation of strips + References subdivision interpolator. + References branches interpolator. + References branches variation interpolator. + References length interpolator. + References length variation interpolator. + References width interpolator. + References interpolator for amplitude control. 0=straight, 50=wide + + + + How far lightning will stretch to. + How far lightning variation will stretch to. + How wide the bolt will be. + Influences forking behavior with a multiplier. + + + + + Reference to a shader property. + + + + Bethesda-specific Texture Set. + + Textures. + 0: Diffuse + 1: Normal/Gloss + 2: Glow(SLSF2_Glow_Map)/Skin/Hair/Rim light(SLSF2_Rim_Lighting) + 3: Height/Parallax + 4: Environment + 5: Environment Mask + 6: Subsurface for Multilayer Parallax + 7: Back Lighting Map (SLSF2_Back_Lighting) + + + + + Bethesda-specific property. Found in Fallout3 + + + + Sets what sky function this object fulfills in BSSkyShaderProperty or SkyShaderProperty. + + + + + + + + + Bethesda-specific property. Found in Fallout3 + The texture. + Sky Object Type + + + + Bethesda-specific property. + Texture file name + + + + Bethesda-specific property. + + + + Bethesda-specific property. + + + + Bethesda-specific property. + Texture file name + + + + Bethesda-specific property. + + + + Bethesda-specific property. + + + + Bethesda-specific property. + + + + Skyrim Shader Property Flags 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skyrim Shader Property Flags 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fallout 4 Shader Property Flags 1 + + + + Fallout 4 Shader Property Flags 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bethesda shader property for Skyrim and later. + Skyrim Shader Flags for setting render/shader options. + Skyrim Shader Flags for setting render/shader options. + Fallout 4 Shader Flags. Mostly overridden if "Name" is a path to a BGSM/BGEM file. + Fallout 4 Shader Flags. Mostly overridden if "Name" is a path to a BGSM/BGEM file. + + + + + + Offset UVs + Offset UV Scale to repeat tiling textures, see above. + Texture Set, can have override in an esm/esp + Glow color and alpha + Multiplied emissive colors + + How to handle texture borders. + The material opacity (1=opaque). Greater than 1.0 is used to affect alpha falloff i.e. staying opaque longer based on vertex alpha and alpha mask. + The amount of distortion. **Not based on physically accurate refractive index** (0=none) + The material specular power, or glossiness. + The base roughness, multiplied by the smoothness map. + Adds a colored highlight. + Brightness of specular highlight. (0=not visible) + Controls strength for envmap/backlight/rim/softlight lighting effect? + Controls strength for envmap/backlight/rim/softlight lighting effect? + + + + + + + + + + + + + Scales the intensity of the environment/cube map. + + + + + Tints the base texture. Overridden by game settings. + + Tints the base texture. Overridden by game settings. + + + How far from the surface the inner layer appears to be. + Depth of inner parallax layer effect. + Scales the inner parallax layer texture. + How strong the environment/cube map is. + CK lists "snow material" when used. + Eye cubemap scale + Offset to set center for left eye cubemap + Offset to set center for right eye cubemap + + + + Bethesda effect shader property for Skyrim and later. + + + + + + + + + Offset UVs + Offset UV Scale to repeat tiling textures + points to an external texture. + + How to handle texture borders. + + + + At this cosine of angle falloff will be equal to Falloff Start Opacity + At this cosine of angle falloff will be equal to Falloff Stop Opacity + Alpha falloff multiplier at start angle + Alpha falloff multiplier at end angle + + Base color + Multiplier for Base Color (RGB part) + + Points to an external texture, used as palette for SLSF1_Greyscale_To_PaletteColor/SLSF1_Greyscale_To_PaletteAlpha. + + + + + + + + + + + + + Skyrim water shader property flags + + + + Skyrim water shader property, different from "WaterShaderProperty" seen in Fallout. + + + + + + + Offset UVs. Seems to be unused, but it fits with the other Skyrim shader properties. + Offset UV Scale to repeat tiling textures, see above. + + + + + Skyrim Sky shader block. + + + + + + + Offset UVs. Seems to be unused, but it fits with the other Skyrim shader properties. + Offset UV Scale to repeat tiling textures, see above. + points to an external texture. + + + + + Bethesda-specific skin instance. + + + + + + Bethesda-specific extra data. Lists locations and normals on a mesh that are appropriate for decal placement. + + + + + + Bethesda-specific particle modifier. + + + + + + + + + + + + + Flags for BSValueNode. + + + + Bethesda-specific node. Found on fxFire effects + + + + + + + Bethesda-Specific (mesh?) Particle System. + + + + + Bethesda-Specific (mesh?) Particle System Data. + + + + + + + + + Bethesda-Specific (mesh?) Particle System Modifier. + + + + + + Bethesda-Specific time controller. + + + + + + Bethesda-Specific particle system. + + + + + + + + + Particle system (multi?) emitter controller. + + + + + + + Bethesda-Specific time controller. + + + + + Bethesda-Specific node. + + + + + + + Bethesda-Specific node. + + + + + + + Bethesda-Specific node. + + + + Bethesda-Specific node. + + + + + Bethesda-specific time controller. + + + + + A list of shapes. However, + - The sub shapes must ALL be convex: Sphere, Capsule, Cylinder, Convex Vertices, Convex Transform + - The radius of all shapes must be identical + - The number of sub shapes is restricted to 255 + - The number of vertices of each sub shape is restricted to 255 + + For this reason you most likely cannot combine Sphere Shapes, Capsule Shapes, and Convex Vertices Shapes, + as their Radius values differ in function. (Sphere/Capsule radius = physical size, CVS radius = padding/shell) + + Shapes collected in a bhkConvexListShape may not have the correct material noise. + + List of shapes. Max of 255. + The material of the shape. + + + + + If true, an AABB of the children's AABBs is used, which is faster but larger than building an AABB from each child. + A distance which is used for getClosestPoint(). If the object being tested is closer, the children are recursed. Otherwise it returns this value. + + + + Bethesda-specific class. + + + + + + + Bethesda-specific interpolator. + + + + + + + Anim note types. + + + + + + + Bethesda-specific object. + Type of this note. + Location in time. + + + + + + + Bethesda-specific object. + Number of BSAnimNote objects. + BSAnimNote objects. + + + + Bethesda custom bhkUnaryAction. Does not hold a link to the body like other bhkUnaryActions however. + + + + + + + + + Culling modes for multi bound nodes. + + + + + + + + + Bethesda-specific node. + + + + + + Bethesda-specific object. + + + + + Abstract base type for bounding data. + + + + Oriented bounding box. + Center of the box. + Size of the box along each axis. + Rotation of the bounding box. + + + + Bethesda-specific object. + + + + + + This is only defined because of recursion issues. + + + + + + + + Bethesda-specific. Describes groups of triangles either segmented in a grid (for LOD) or by body part for skinned FO4 meshes. + + Index = previous Index + previous Num Tris in Segment * 3 + The number of triangles belonging to this segment + + + + + + + Bethesda-specific AV object. + Number of segments in the square grid + Configuration of each segment + + + + Bethesda-specific object. + Position of the AABB's center + Extent of the AABB in all directions + + + + Type of data in this channel + Number of bytes per element of this channel + Total number of bytes of this channel (num vertices times num bytes per element) + Number of bytes per element in all channels together. Sum of num channel bytes per element over all block infos. + Unsure. The block in which this channel is stored? Usually there is only one block, and so this is zero. + Offset (in bytes) of this channel. Sum of all num channel bytes per element of all preceeding block infos. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No longer appears to occur in any vanilla files. Older versions of FNV had the block in nvdlc01vaultposter01.nif. + + + + Usually there is exactly one block. + + + + + Bethesda-specific extra data. + + + + + + Bethesda-specific time controller. + + + + Bethesda-Specific node. + + + + Bethesda extension of hkpBreakableConstraintData, a wrapper around hkpConstraintInstance. + The constraint can "break" i.e. stop applying the forces to each body to keep them constrained. + The wrapped constraint. + The larger the value, the harder to "break" the constraint. + No: Constraint stays active. Yes: Constraint gets removed when breaking threshold is exceeded. + + + + Bethesda extension of hkpReorientAction (or similar). Will try to reorient a body to stay facing a given direction. + + + + + + + + + + Found in Fallout 3 .psa files, extra ragdoll info for NPCs/creatures. (usually idleanims\deathposes.psa) + Defines different kill poses. The game selects the pose randomly and applies it to a skeleton immediately upon ragdolling. + Poses can be previewed in GECK Object Window-Actor Data-Ragdoll and selecting Pose Matching tab. + + + + + + + + Found in Fallout 3, more ragdoll info? (meshes\ragdollconstraint\*.rdt) + + + + + + Data for bhkRagdollTemplate + + + + + + + + + + + + A range of indices, which make up a region (such as a submesh). + + + + + + Sets how objects are to be cloned. + + + + + + + The data format of components. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Determines how a data stream is used? + + + + + + + + Determines how the data stream is accessed? + + + + + + + + + + + + + + The size in bytes of this data stream. + + Number of regions (such as submeshes). + The regions in the mesh. Regions can be used to mark off submeshes which are independent draw calls. + Number of components of the data (matches corresponding field in MeshData). + The format of each component in this data stream. + + + + + + + Type of data (POSITION, POSITION_BP, INDEX, NORMAL, NORMAL_BP, + TEXCOORD, BLENDINDICES, BLENDWEIGHT, BONE_PALETTE, COLOR, DISPLAYLIST, + MORPH_POSITION, BINORMAL_BP, TANGENT_BP). + + + An extra index of the data. For example, if there are 3 uv maps, + then the corresponding TEXCOORD data components would have indices + 0, 1, and 2, respectively. + + + + + Reference to a data stream object which holds the data used by this reference. + Sets whether this stream data is per-instance data for use in hardware instancing. + The number of submesh-to-region mappings that this data stream has. + A lookup table that maps submeshes to regions. + + Describes the semantic of each component. + + + + An object that can be rendered. + Per-material data. + + + + Describes the type of primitives stored in a mesh object. + + + + + + + + + + A sync point corresponds to a particular stage in per-frame processing. + + + + + + + + + + + + Base class for mesh modifiers. + + The sync points supported by this mesh modifier for SubmitTasks. + + The sync points supported by this mesh modifier for CompleteTasks. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + When non-zero, equal to the number of primitives. + When non-zero, equal to the number of primitives. + Some integer associated with each primitive. + + + The max value to appear in the Mapped Primitives array. + + + + The primitive type of the mesh, such as triangles or lines. + + The number of submeshes contained in this mesh. + Sets whether hardware instancing is being used. + The combined bounding volume of all submeshes. + + + + + + + + + + Manipulates a mesh with the semantic MORPHWEIGHTS using an NiMorphMeshModifier. + + + + + + + + + The element semantic. + Whether or not to normalize the data. + + + + Performs linear-weighted blending between a set of target data streams. + + FLAG_RELATIVETARGETS = 0x01 + FLAG_UPDATENORMALS = 0x02 + FLAG_NEEDSUPDATE = 0x04 + FLAG_ALWAYSUPDATE = 0x08 + FLAG_NEEDSCOMPLETION = 0x10 + FLAG_SKINNED = 0x20 + FLAG_SWSKINNED = 0x40 + + The number of morph targets. + The number of morphing data stream elements. + Semantics and normalization of the morphing data stream elements. + + + + + USE_SOFTWARE_SKINNING = 0x0001 + RECOMPUTE_BOUNDS = 0x0002 + + The root bone of the skeleton. + The transform that takes the root bone parent coordinate system into the skin coordinate system. + The number of bones referenced by this mesh modifier. + Pointers to the bone nodes that affect this skin. + The transforms that go from bind-pose space to bone space. + The bounds of the bones. Only stored if the RECOMPUTE_BOUNDS bit is set. + + + + An instance of a hardware-instanced mesh in a scene graph. + The instanced mesh this object represents. + + + + + Mesh modifier that provides per-frame instancing capabilities in Gamebryo. + + + + + + + + + + + + + + + + + Defines the levels of detail for a given character and dictates the character's current LOD. + + + + + + + + + + + + + + + + + + + Describes the various methods that may be used to specify the orientation of the particles. + + + + Represents a particle system. + + + + + + + + + + + + + + + + + + + + + + + + + + + Represents a particle system that uses mesh particles instead of sprite-based particles. + + + + + + + + + + A mesh modifier that uses particle system data to generate camera-facing quads. + + + + A mesh modifier that uses particle system data to generate aligned quads for each particle. + + + + + + + + + + + + + + + + + + + + + + + + The mesh modifier that performs all particle system simulation. + + + + + + Abstract base class for a single step in the particle system simulation process. It has no seralized data. + + + + + + + + + + + + Encapsulates a floodgate kernel that updates particle size, colors, and rotations. + + The particle size keys. + The loop behavior for the size keys. + + The particle color keys. + The loop behavior for the color keys. + + The particle rotation keys. + The loop behavior for the rotation keys. + The the amount of time over which a particle's size is ramped from 0.0 to 1.0 in seconds + The the amount of time over which a particle's size is ramped from 1.0 to 0.0 in seconds + Specifies the particle generation to which the grow effect should be applied. This is usually generation 0, so that newly created particles will grow. + Specifies the particle generation to which the shrink effect should be applied. This is usually the highest supported generation for the particle system, so that particles will shrink immediately before getting killed. + + + + + Encapsulates a floodgate kernel that simulates particle forces. + + The forces affecting the particle system. + + + + Encapsulates a floodgate kernel that simulates particle colliders. + + The colliders affecting the particle system. + + + + Encapsulates a floodgate kernel that updates mesh particle alignment and transforms. + + The particle rotation keys. + The loop behavior for the rotation keys. + + + + Encapsulates a floodgate kernel that updates particle positions and ages. As indicated by its name, this step should be attached last in the NiPSSimulator mesh modifier. + + + + Updates the bounding volume for an NiPSParticleSystem object. + Number of particle bounds to skip updating every frame. Higher = more updates each frame. + + + + + + This is used by the Floodgate kernel to determine which NiPSForceHelpers functions to call. + + + + Abstract base class for all particle forces. + + + The force type is set by each derived class and cannot be changed. + + + + + + + + + + + + + + + Abstract base class for all particle field forces. + + + + + + + + + Applies a linear drag force to particles. + + + + + + + + + Applies a gravitational force to particles. + + + + + + + + + + + Applies an explosive force to particles. + + + + + + + + + + Inside a field, updates particle velocity to simulate the effects of air movements. + + + + + + + + + + Inside a field, updates particle velocity to simulate the effects of directional gravity. + + + + + + + Inside a field, updates particle velocity to simulate the effects of drag. + + + + + + Inside a field, updates particle velocity to simulate the effects of point gravity. + + + + + + Inside a field, updates particle velocity to simulate the effects of turbulence. + + + + + Inside a field, updates particle velocity to simulate the effects of a vortex. + + + + + + + Abstract base class for all particle emitters. + + + + + + + + + + + + + + + + + + + + + + + + + Abstract base class for particle emitters that emit particles from a volume. + + + + + + A particle emitter that emits particles from a rectangular volume. + + + + + + + A particle emitter that emits particles from a spherical volume. + + + + + A particle emitter that emits particles from a cylindrical volume. + + + + + + A particle emitter that emits particles from a toroidal volume. + + + + + + Emits particles from one or more NiMesh objects. A random mesh emitter is selected for each particle emission. + + + + + + + + + + Emits particles from a curve. + + + + + + + + + + Abstract base class for all particle emitter time controllers. + + + + + Abstract base class for controllers that animate a floating point value on an NiPSEmitter object. + + + + Animates particle emission and birth rate. + + + + + Abstract base class for all particle force time controllers. + + + + + Abstract base class for controllers that animate a Boolean value on an NiPSForce object. + + + + Abstract base class for controllers that animate a floating point value on an NiPSForce object. + + + + Animates whether or not an NiPSForce object is active. + + + + Animates the strength value of an NiPSGravityForce object. + + + + Animates the attenuation value of an NiPSFieldForce object. + + + + Animates the magnitude value of an NiPSFieldForce object. + + + + Animates the max distance value of an NiPSFieldForce object. + + + + Animates the speed value on an NiPSEmitter object. + + + + Animates the size value on an NiPSEmitter object. + + + + Animates the declination value on an NiPSEmitter object. + + + + Animates the declination variation value on an NiPSEmitter object. + + + + Animates the planar angle value on an NiPSEmitter object. + + + + Animates the planar angle variation value on an NiPSEmitter object. + + + + Animates the rotation angle value on an NiPSEmitter object. + + + + Animates the rotation angle variation value on an NiPSEmitter object. + + + + Animates the rotation speed value on an NiPSEmitter object. + + + + Animates the rotation speed variation value on an NiPSEmitter object. + + + + Animates the lifespan value on an NiPSEmitter object. + + + + Calls ResetParticleSystem on an NiPSParticleSystem target upon looping. + + + + + + This is used by the Floodgate kernel to determine which NiPSColliderHelpers functions to call. + + + + Abstract base class for all particle colliders. + + + + + + + + + + A planar collider for particles. + + + + + + + + + A spherical collider for particles. + + + + + + Creates a new particle whose initial parameters are based on an existing particle. + + + + + + + + + + + + + + + + Implements Gamebryo particle systems that use PhysX actors for the particles + + + + + + + + + + + + + A PhysX prop which holds information about a PhysX particle system prop. + + + + + + An object which interfaces between the PhysX scene and the Gamebryo particle system. + + + + + Adds additional sync points to a NiPSSimulator mesh modifier. + + + + Used to set the post-simulation state of particles in a physics-enabled particle system. + + + + Used to push the results of the particle system update into the PhysX actors. + + + + + + The name of the animated NiAVObject. + The RTTI type of the NiProperty the controller is attached to, if applicable. + The RTTI type of the NiTimeController. + An ID that can uniquely identify the controller among others of the same type on the same NiObjectNET. + An ID that can uniquely identify the interpolator among others of the same type on the same NiObjectNET. + + Channel Indices are BASE/POS = 0, ROT = 1, SCALE = 2, FLAG = 3 + Channel Types are: + INVALID = 0, COLOR, BOOL, FLOAT, POINT3, ROT = 5 + Any channel may be | 0x40 which means POSED + The FLAG (3) channel flags affects the whole evaluator: + REFERENCED = 0x1, TRANSFORM = 0x2, ALWAYSUPDATE = 0x4, SHUTDOWN = 0x8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + For floating point as boolean, 0.0 indicates false and 1.0 indicates true. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Handle into the data. (USHRT_MAX for invalid handle.) + + + + + + + + + Handle into the data. (USHRT_MAX for invalid handle.) + + + + + + + + + Handle into the data. (USHRT_MAX for invalid handle.) + + + + + + + + + + Handle into the translation data. (USHRT_MAX for invalid handle.) + Handle into the rotation data. (USHRT_MAX for invalid handle.) + Handle into the scale data. (USHRT_MAX for invalid handle.) + + + + + + + + + + + + + + + + + + + + + + + -1 = Negative, 1 = Positive + Max angle in radians. + + 0, 1, or 2 representing X, Y, or Z. + + + + + + Root node in Gamebryo .kf files (20.5.0.1 and up). + For 20.5.0.0, "NiSequenceData" is an alias for "NiControllerSequence" and this is not handled in nifxml. + This was not found in any 20.5.0.0 KFs available and they instead use NiControllerSequence directly. + After 20.5.0.1, Controlled Blocks are no longer used and instead the sequences uses NiEvaluator. + + + + + + + + + + + The name of the NiAVObject serving as the accumulation root. This is where all accumulated translations, scales, and rotations are applied. + + + + + Flags for NiShadowGenerator. + Bit Patterns: + AUTO_CALC_NEARFAR = (AUTO_NEAR_DIST | AUTO_FAR_DIST) = 0xC0 + AUTO_CALC_FULL = (AUTO_NEAR_DIST | AUTO_FAR_DIST | AUTO_DIR_LIGHT_FRUSTUM_WIDTH | AUTO_DIR_LIGHT_FRUSTUM_POSITION) = 0x3C0 + + + + An NiShadowGenerator object is attached to an NiDynamicEffect object to inform the shadowing system that the effect produces shadows. + + + + + + + + + + + + + + + + Blood Bowl Specific + Used on things like hair/tails in conjunction with bones to animate them. + + + The number of node bones referenced as influences. + List of all armature bones. + The number of node bones referenced as influences. + List of all armature bones. + + + + Divinity 2 specific block + + + + + + + + + Compressed collision mesh. + Points to root node? + + A shell that is added around the shape. + + Scale + A shell that is added around the shape. + Scale + The collision mesh data. + + + + hkpWeldingUtility::WeldingType + + + + hkpCompressedMeshShape::MaterialType + + + + + + + + A compressed mesh shape for collision in Skyrim. + Number of bits in the shape-key reserved for a triangle index + Number of bits in the shape-key reserved for a triangle index and its winding + Mask used to get the triangle index and winding from a shape-key/ + Mask used to get the triangle index from a shape-key. + Quantization error. + + + + + Unused. + + Unused. + + Unused. + + Materials used by Chunks. Chunks refer to this table by index. + Number of hkpNamedMeshMaterial. Unused. + Number of chunk transformations + Transforms used by Chunks. Chunks refer to this table by index. + + Vertices paired with Big Tris (triangles that are too large for chunks) + + Triangles that are too large to fit in a chunk. + + + Number of hkpCompressedMeshShape::ConvexPiece. Unused. + + + + Orientation marker for Skyrim's inventory view. + How to show the nif in the player's inventory. + Typically attached to the root node of the nif tree. + If not present, then Skyrim will still show the nif in inventory, + using the default values. + Name should be 'INV' (without the quotes). + For rotations, a short of "4712" appears as "4.712" but "959" appears as "0.959" meshes\weapons\daedric\daedricbowskinned.nif + + + + Zoom factor. + + + + Unknown + Number of bone entries + Bone Entry + + + + Links a nif with a Havok Behavior .hkx animation file + Name of the hkx file. + Unknown, has to do with blending appended bones onto an actor. + + + + A controller that trails a bone behind an actor. + How long it takes to rotate about an actor back to rest position. + How the bone lags rotation + How far bone will tail an actor. + + + + A variation on NiTriShape, for visibility control over vertex groups. + + + + + + + Furniture Marker for actors + + + + Unknown, related to trees. + + + + Node for handling Trees, Switches branch configurations for variation? + + + + + + + + + + + Fallout 4 Tri Shape + + + + + + + + + + + + + + + + + + + + + Fallout 4 LOD Tri Shape + + + + + + + If Bone ID is 0xffffffff, this value refers to the Segment at the listed index. Otherwise this is the "Biped Object", which is like the body part types in Skyrim and earlier. + A hash of the bone name string. + Maximum of 8. + + + + + + + + > + + + + + Fallout 4 Sub-Index Tri Shape + + + + + + + + + + + + + Fallout 4 Physics System + + + + Fallout 4 Collision Object + + Note: The CK Preview scenegraph reports these bits incorrectly. + + + + + + + Fallout 4 Collision System + + + + + Fallout 4 Ragdoll System + + + + + Fallout 4 Extra Data + + + + Fallout 4 Cloth data + + + + + + + Fallout 4 Bone Transform + + + + + + + + Fallout 4 Skin Instance + + + + + + + + + + Fallout 4 Bone Data + + + + + + Fallout 4 Positional Data + + + + + + + + + + + + + + Fallout 4 Item Slot Parent + + + + + + Fallout 4 Item Slot Child + + + + + + + Fallout 4 Eye Center Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This is the data necessary to access the shared geometry in a PSG/CSG file. + BSCRC32 of the filename without the PSG/CSG extension. + Offset of the geometry data in the PSG/CSG file. + + + + Fallout 4 Packed Combined Geometry Data. + Geometry is baked into the file and given a list of transforms to position each copy. + + + + + + + + + + + Fallout 4 Packed Combined Shared Geometry Data. + Geometry is NOT baked into the file. It is instead a reference to the geometry via a CSG filename hash and data offset. + + + + + + + + + + + + + + + + + + + + + + + + + Large ref flag. + + + + + Distant Object flags. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fallout 76 Collision Query Proxy Data + + + + From b1e6d8fec667490b54f3fa573ad76614cb18b3fd Mon Sep 17 00:00:00 2001 From: gavrant Date: Wed, 17 May 2023 23:27:16 +0300 Subject: [PATCH 048/118] Re-enabled copying of nif.xml (the "correct" one) and kfm.xml in the project file builder --- NifSkope.pro | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NifSkope.pro b/NifSkope.pro index 9b531ec60..ee4770dee 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -483,7 +483,7 @@ win32:contains(QT_ARCH, i386) { } XML += \ - build/docsys/nifxml/nif.xml \ + build/nif.xml \ build/docsys/kfmxml/kfm.xml QSS += \ @@ -502,8 +502,7 @@ win32:contains(QT_ARCH, i386) { copyDirs( $$SHADERS, shaders ) #copyDirs( $$LANG, lang ) - copyFiles( $$QSS ) - #copyFiles( $$XML ) + copyFiles( $$XML $$QSS ) # Copy Readmes and rename to TXT copyFiles( $$READMES,,,, md:txt ) From 23bebc868e531d20ad4620db3b984343ebc158af Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 18 May 2023 05:49:53 +0300 Subject: [PATCH 049/118] Slowed down view "zoom" (mouse wheel and Q, E, Page Up, Page Down keys) to about a quarter of the former speed --- src/glview.cpp | 41 ++++++++++++++++++++++------------------- src/glview.h | 1 - 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/glview.cpp b/src/glview.cpp index 550bdf3e4..8e5ca3b31 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -87,6 +87,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define ZOOM_MIN 1.0 #define ZOOM_MAX 1000.0 +#define ZOOM_PAGE_KEY_MULT 1.025 + +#define ZOOM_QE_KEY_MULT 1.025 +#define ZOOM_MOUSE_WHEEL_MULT 0.95 #ifndef M_PI #define M_PI 3.1415926535897932385 @@ -946,19 +950,6 @@ void GLView::rotate( float x, float y, float z ) update(); } -void GLView::zoom( float z ) -{ - Zoom *= z; - - if ( Zoom < ZOOM_MIN ) - Zoom = ZOOM_MIN; - - if ( Zoom > ZOOM_MAX ) - Zoom = ZOOM_MAX; - - update(); -} - void GLView::setCenter() { Node * node = scene->getNode( model, scene->currentBlock ); @@ -1021,6 +1012,13 @@ void GLView::setRotation( float x, float y, float z ) void GLView::setZoom( float z ) { Zoom = z; + + if (Zoom < ZOOM_MIN) + Zoom = ZOOM_MIN; + + if (Zoom > ZOOM_MAX) + Zoom = ZOOM_MAX; + update(); } @@ -1337,12 +1335,12 @@ void GLView::advanceGears() //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 ); + if ( kbd[ Qt::Key_Q ] ) setDistance( Dist / ZOOM_QE_KEY_MULT ); + if ( kbd[ Qt::Key_E ] ) setDistance( Dist * ZOOM_QE_KEY_MULT ); // Focal Length - if ( kbd[ Qt::Key_PageUp ] ) zoom( 1.1f ); - if ( kbd[ Qt::Key_PageDown ] ) zoom( 1 / 1.1f ); + if ( kbd[ Qt::Key_PageUp ] ) setZoom( Zoom * ZOOM_PAGE_KEY_MULT ); + if ( kbd[ Qt::Key_PageDown ] ) setZoom( Zoom / ZOOM_PAGE_KEY_MULT ); if ( mouseMov[0] != 0 || mouseMov[1] != 0 || mouseMov[2] != 0 ) { move( mouseMov[0], mouseMov[1], mouseMov[2] ); @@ -1812,9 +1810,14 @@ void GLView::mouseReleaseEvent( QMouseEvent * event ) void GLView::wheelEvent( QWheelEvent * event ) { if ( view == ViewWalk ) - mouseMov += Vector3( 0, 0, event->delta() ); + mouseMov += Vector3( 0, 0, ((double) event->delta()) / 4.0 ); else - setDistance( Dist * (event->delta() < 0 ? 1.0 / 0.8 : 0.8) ); + { + if (event->delta() < 0) + setDistance( Dist / ZOOM_MOUSE_WHEEL_MULT ); + else + setDistance( Dist * ZOOM_MOUSE_WHEEL_MULT ); + } } diff --git a/src/glview.h b/src/glview.h index acfea7d74..799cb56cb 100644 --- a/src/glview.h +++ b/src/glview.h @@ -130,7 +130,6 @@ class GLView final : public QGLWidget void center(); void move( float, float, float ); void rotate( float, float, float ); - void zoom( float ); void setCenter(); void setDistance( float ); From 8f7287db323dff5a2a1b5875a33f37f328f5c2bf Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 18 May 2023 07:57:18 +0300 Subject: [PATCH 050/118] UV Editor: - Instead of a tiny 512x512 window, open the UV editor maximized and more zoomed in - Changed the window's type from Tool to Window. This shows the editor in Windows task bar and adds maximize/minimize buttons to its title bar. --- src/ui/widgets/uvedit.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index d6df9591d..84258ea75 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -65,7 +65,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #endif -#define BASESIZE 512.0 +#define BASESIZE 1024.0 #define GRIDSIZE 16.0 #define GRIDSEGS 4 #define ZOOMUNIT -64.0 @@ -86,7 +86,7 @@ UVWidget * UVWidget::createEditor( NifModel * nif, const QModelIndex & idx ) return nullptr; } - uvw->show(); + uvw->showMaximized(); return uvw; } @@ -112,7 +112,7 @@ QStringList UVWidget::texnames = { UVWidget::UVWidget( QWidget * parent ) - : QGLWidget( QGLFormat( QGL::SampleBuffers ), parent, 0, Qt::Tool ), undoStack( new QUndoStack( this ) ) + : QGLWidget( QGLFormat( QGL::SampleBuffers ), parent, 0, Qt::Window ), undoStack( new QUndoStack( this ) ) { setWindowTitle( tr( "UV Editor" ) ); setFocusPolicy( Qt::StrongFocus ); @@ -803,6 +803,11 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) iShape = nifIndex; isDataOnSkin = false; + auto newTitle = tr("UV Editor"); + if (nif) + newTitle += tr(" - ") + nif->getFileInfo().fileName(); + setWindowTitle(newTitle); + game = Game::GameManager::get_game(nif->getVersionNumber(), nif->getUserVersion(), nif->getUserVersion2()); // Version dependent actions From ceabba3525f12bb1e8e05ed6d4291c5f48e3ea09 Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 18 May 2023 09:34:48 +0300 Subject: [PATCH 051/118] Show index of items in arrays (vertices, triangles, textures, etc.) right in the Name column of Block Details instead of a tooltip (for example, "Vertex 123") --- src/model/nifmodel.cpp | 54 +++++++++++++++++++++++++++++++++++++----- src/model/nifmodel.h | 3 +++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index c1783e441..f95e82392 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -44,12 +44,42 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +QHash NifModel::arrayPseudonyms; +void NifModel::setupArrayPseudonyms() +{ + if (!arrayPseudonyms.isEmpty()) + return; + + auto registerPseudonym = [](const QString & plural, const QString & singular) + { + arrayPseudonyms.insert(plural, singular + " "); + }; + + registerPseudonym("Vertex Data", "Vertex"); + registerPseudonym("Vertices", "Vertex"); + registerPseudonym("Triangles", "Triangle"); + registerPseudonym("Normals", "Normal"); + registerPseudonym("Tangents", "Tangent"); + registerPseudonym("Bitangents", "Bitangent"); + registerPseudonym("Vertex Colors", "Vertex Color"); + registerPseudonym("Textures", "Texture"); + registerPseudonym("Strings", "String"); + registerPseudonym("Children", "Child"); + registerPseudonym("Extra Data List", "Extra Data"); + registerPseudonym("Chunk Materials", "Chunk Material"); + registerPseudonym("Chunk Transforms", "Chunk Transform"); + registerPseudonym("Big Verts", "Big Vert"); + registerPseudonym("Big Tris", "Big Tri"); + registerPseudonym("Chunks", "Chunk"); + registerPseudonym("Effects", "Effect"); +} //! @file nifmodel.cpp The NIF data model. NifModel::NifModel( QObject * parent ) : BaseModel( parent ) { + setupArrayPseudonyms(); updateSettings(); clear(); @@ -1181,15 +1211,27 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const switch ( column ) { case NameCol: { + auto iname = item->name(); + if ( ndr ) - return item->name(); + return iname; - QString a = ""; + if ( itemType(index) == "NiBlock" ) + return QString::number(getBlockNumber(index)) + " " + iname; + else if (isArray(item->parent())) { + auto arrayName = arrayPseudonyms.value(iname); + if (arrayName.isEmpty()) + { + if (iname == "UV Sets") + arrayName = QString((item->value().type() == NifValue::tVector2) ? "UV " : "UV Set "); + else + arrayName = iname + " "; + } - if ( itemType( index ) == "NiBlock" ) - a = QString::number( getBlockNumber( index ) ) + " "; + return arrayName + QString::number(item->row()); + } - return a + item->name(); + return " " + iname; } break; case TypeCol: @@ -1368,7 +1410,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const case NameCol: { if ( item->parent() && isArray( item->parent() ) ) { - return QString( "array index: %1" ).arg( item->row() ); + return QString(); } else { QString tip = QString( "

%1

%2

" ) .arg( item->name() ) diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index a7086507b..c9a03578a 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -388,6 +388,9 @@ public slots: static QHash blocks; static QMap blockHashes; + static QHash arrayPseudonyms; + static void setupArrayPseudonyms(); + private: struct Settings { From 191b23a31ca1aa140f74c67fb261f1472d66922a Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 18 May 2023 10:16:29 +0300 Subject: [PATCH 052/118] Auto-expand top level of Block List on loading a mesh file --- src/nifskope_ui.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 2816855fe..6c9646787 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -840,6 +840,9 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) // Center the model on load ogl->center(); + // Expand the top level of Block List tree + ui->list->expandToDepth(0); + // Hide Progress Bar QTimer::singleShot( timeout, progress, SLOT( hide() ) ); } From f5309b5f4075621224bd6662e944fc0cb30603bc Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 18 May 2023 10:48:03 +0300 Subject: [PATCH 053/118] Disable Reload menu item if no mesh file is loaded --- src/nifskope_ui.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 6c9646787..53e52adc7 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -186,6 +186,8 @@ void NifSkope::initActions() connect( ui->aSave, &QAction::triggered, this, &NifSkope::save ); connect( ui->aSaveAs, &QAction::triggered, this, &NifSkope::saveAsDlg ); + ui->aReload->setDisabled(true); + // TODO: Assure Actions and Scene state are synced // Set Data for Actions to pass onto Scene when clicking /* @@ -611,6 +613,7 @@ void NifSkope::initToolBars() connect ( ogl->scene, &Scene::disableSave, [this]() { ui->aSave->setDisabled(true); ui->aSaveAs->setDisabled(true); + ui->aReload->setDisabled(true); } ); // LOD Toolbar @@ -798,6 +801,7 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) ui->aSave->setDisabled(false); ui->aSaveAs->setDisabled(false); + ui->aReload->setDisabled(false); int timeout = 2500; if ( success ) { @@ -907,6 +911,7 @@ void NifSkope::enableUi() ui->aSaveMenu->setEnabled( true ); ui->aSave->setEnabled( true ); ui->aSaveAs->setEnabled( true ); + ui->aReload->setEnabled( true ); ui->aHeader->setEnabled( true ); ui->mRender->setEnabled( true ); From ccbe389bfa79736b8632427928171c18d58ed87d Mon Sep 17 00:00:00 2001 From: gavrant Date: Tue, 30 May 2023 15:22:36 +0300 Subject: [PATCH 054/118] Additional "pseudonyms" for array items in Block Details + minor refactoring --- src/model/nifmodel.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index f95e82392..b2d76b4c5 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -51,10 +51,7 @@ void NifModel::setupArrayPseudonyms() if (!arrayPseudonyms.isEmpty()) return; - auto registerPseudonym = [](const QString & plural, const QString & singular) - { - arrayPseudonyms.insert(plural, singular + " "); - }; + #define registerPseudonym(plural, singular) arrayPseudonyms.insert(plural, singular) registerPseudonym("Vertex Data", "Vertex"); registerPseudonym("Vertices", "Vertex"); @@ -73,6 +70,12 @@ void NifModel::setupArrayPseudonyms() registerPseudonym("Big Tris", "Big Tri"); registerPseudonym("Chunks", "Chunk"); registerPseudonym("Effects", "Effect"); + registerPseudonym("Partitions", "Partition"); + registerPseudonym("Bones", "Bone"); + registerPseudonym("Bone List", "Bone"); + registerPseudonym("Bone Weights", "Bone Weight"); + registerPseudonym("Vertex Weights", "Vertex Weight"); + registerPseudonym("Bone Indices", "Bone Index"); } //! @file nifmodel.cpp The NIF data model. @@ -1218,17 +1221,15 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const if ( itemType(index) == "NiBlock" ) return QString::number(getBlockNumber(index)) + " " + iname; - else if (isArray(item->parent())) { + else if ( isArray(item->parent()) ) { auto arrayName = arrayPseudonyms.value(iname); - if (arrayName.isEmpty()) - { + if ( arrayName.isEmpty() ) { if (iname == "UV Sets") - arrayName = QString((item->value().type() == NifValue::tVector2) ? "UV " : "UV Set "); + arrayName = QString( (item->value().type() == NifValue::tVector2) ? "UV" : "UV Set" ); else - arrayName = iname + " "; + arrayName = iname; } - - return arrayName + QString::number(item->row()); + return arrayName + " " + QString::number(item->row()); } return " " + iname; From 9ce35de6a8d377bdb695771fece8649f0c71034b Mon Sep 17 00:00:00 2001 From: gavrant Date: Wed, 31 May 2023 07:08:15 +0300 Subject: [PATCH 055/118] - Replaced all scene options "&" checks with function calls. IMO, it makes the code more readable. - Fixed some compiler warnings. - Minor refactoring here and there. --- src/gl/bsshape.cpp | 21 +++-- src/gl/glmesh.cpp | 34 ++++---- src/gl/glnode.cpp | 27 +++---- src/gl/glproperty.cpp | 10 +-- src/gl/glscene.cpp | 12 +-- src/gl/glscene.h | 4 + src/gl/renderer.cpp | 183 +++++++++++++++++++++--------------------- src/glview.cpp | 36 ++++----- 8 files changed, 158 insertions(+), 169 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index efc447bc6..43246921f 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -287,7 +287,7 @@ void BSShape::transformShapes() transformRigid = true; - if ( isSkinned && scene->options & Scene::DoSkinning ) { + if ( isSkinned && scene->hasOption(Scene::DoSkinning) ) { transformRigid = false; int vcnt = verts.count(); @@ -352,13 +352,13 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) glPointSize( 8.5 ); // TODO: Only run this if BSXFlags has "EditorMarkers present" flag - if ( !(scene->options & Scene::ShowMarkers) && name.contains( "EditorMarker" ) ) + if ( !scene->hasOption(Scene::ShowMarkers) && name.contains( "EditorMarker" ) ) return; auto nif = static_cast(iBlock.model()); if ( Node::SELECTING ) { - if ( scene->selMode & Scene::SelObject ) { + if ( scene->isSelModeObject() ) { int s_nodeId = ID2COLORKEY( nodeId ); glColor4ubv( (GLubyte *)&s_nodeId ); } else { @@ -401,7 +401,7 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) if ( nifVersion >= 130 && hasVertexColors && colors.count() ) doVCs = true; - if ( transColors.count() && (scene->options & Scene::DoVertexColors) && doVCs ) { + if ( transColors.count() && scene->hasOption(Scene::DoVertexColors) && doVCs ) { glEnableClientState( GL_COLOR_ARRAY ); glColorPointer( 4, GL_FLOAT, 0, transColors.constData() ); } else if ( nifVersion < 130 && !hasVertexColors && (bslsp && bslsp->hasVertexColors) ) { @@ -469,7 +469,7 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) glDisable( GL_POLYGON_OFFSET_FILL ); - if ( scene->selMode & Scene::SelVertex ) { + if ( scene->isSelModeVertex() ) { drawVerts(); } @@ -520,10 +520,10 @@ void BSShape::drawVerts() const void BSShape::drawSelection() const { glDisable(GL_FRAMEBUFFER_SRGB); - if ( scene->options & Scene::ShowNodes ) + if ( scene->hasOption(Scene::ShowNodes) ) Node::drawSelection(); - if ( isHidden() || !(scene->selMode & Scene::SelObject) ) + if ( isHidden() || !scene->isSelModeObject() ) return; auto idx = scene->currentIndex; @@ -615,7 +615,7 @@ void BSShape::drawSelection() const if ( n == "Bounding Sphere" && !extraData ) { auto sph = BoundSphere( nif, idx ); if ( sph.radius > 0.0 ) { - glColor4f( 1, 1, 1, 0.33 ); + glColor4f( 1, 1, 1, 0.33f ); drawSphereSimple( sph.center, sph.radius, 72 ); } } @@ -631,7 +631,6 @@ void BSShape::drawSelection() const for ( int i = 0; i < dataCt; i++ ) { auto d = data.child( i, 0 ); - int numC = nif->get( d, "Num Combined" ); auto c = nif->getIndex( d, "Combined" ); int cCt = nif->rowCount( c ); @@ -652,7 +651,7 @@ void BSShape::drawSelection() const float pbvR = nif->get( iBSphere.child( 1, 2 ) ); if ( pbvR > 0.0 ) { - glColor4f( 0, 1, 0, 0.33 ); + glColor4f( 0, 1, 0, 0.33f ); drawSphereSimple( pbvC, pbvR, 72 ); } @@ -677,7 +676,7 @@ void BSShape::drawSelection() const glMultMatrix( scene->view * t ); if ( bvR > 0.0 ) { - glColor4f( 1, 1, 1, 0.33 ); + glColor4f( 1, 1, 1, 0.33f ); drawSphereSimple( Vector3( 0, 0, 0 ), bvR, 72 ); } diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 362b99d79..4f6f7b47f 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -94,11 +94,12 @@ void Mesh::clear() void Shape::setController( const NifModel * nif, const QModelIndex & iController ) { - if ( nif->itemName( iController ) == "NiGeomMorpherController" ) { + QString contrName = nif->itemName(iController); + if ( contrName == "NiGeomMorpherController" ) { Controller * ctrl = new MorphController( this, iController ); ctrl->update( nif, iController ); controllers.append( ctrl ); - } else if ( nif->itemName( iController ) == "NiUVController" ) { + } else if ( contrName == "NiUVController" ) { Controller * ctrl = new UVController( this, iController ); ctrl->update( nif, iController ); controllers.append( ctrl ); @@ -113,9 +114,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() - && nif->checkVersion( 0x14020007, 0x14020007 ) ) - { + if ( !scene->hasOption(Scene::DisableShaders) && shader.isNull() && nif->checkVersion( 0x14020007, 0x14020007 ) ) { updateShaderProperties( nif ); } @@ -188,12 +187,12 @@ void Shape::boneSphere( const NifModel * nif, const QModelIndex & index ) const return; Transform boneT = Transform( nif, index ); - Transform t = (scene->options & Scene::DoSkinning) ? viewTrans() : Transform(); + Transform t = scene->hasOption(Scene::DoSkinning) ? viewTrans() : Transform(); t = t * skeletonTrans * bone->localTrans( 0 ) * boneT; auto bSphere = BoundSphere( nif, index ); if ( bSphere.radius > 0.0 ) { - glColor4f( 1, 1, 1, 0.33 ); + glColor4f( 1, 1, 1, 0.33f ); auto pos = boneT.rotation.inverted() * (bSphere.center - boneT.translation); drawSphereSimple( t * pos, bSphere.radius, 36 ); } @@ -205,7 +204,7 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) // Was Skinning toggled? // If so, switch between partition triangles and data triangles - bool doSkinningCurr = (scene->options & Scene::DoSkinning); + bool doSkinningCurr = scene->hasOption(Scene::DoSkinning); updateSkin |= (doSkinning != doSkinningCurr) && nif->checkVersion( 0, 0x14040000 ); updateData |= updateSkin; doSkinning = doSkinningCurr; @@ -295,7 +294,7 @@ QModelIndex Mesh::vertexAt( int idx ) const bool Mesh::isHidden() const { return ( Node::isHidden() - || ( !(scene->options & Scene::ShowHidden) /*&& Options::onlyTextured()*/ + || ( !scene->hasOption(Scene::ShowHidden) && !properties.get() && !properties.get() ) @@ -481,7 +480,6 @@ void Mesh::transform() for ( uint k = 0; k < numStreamComponents; k++ ) { auto typeK = datastreamFormats[k]; int typeLength = ( (typeK & 0x000F0000) >> 0x10 ); - int typeSize = ( (typeK & 0x00000F00) >> 0x08 ); switch ( (typeK & 0x00000FF0) >> 0x04 ) { case 0x10: @@ -831,7 +829,6 @@ void Mesh::transform() } isSkinned = weights.count() || partitions.count(); - } Node::transform(); @@ -990,13 +987,13 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) return; // TODO: Only run this if BSXFlags has "EditorMarkers present" flag - if ( !(scene->options & Scene::ShowMarkers) && name.startsWith( "EditorMarker" ) ) + if ( !scene->hasOption(Scene::ShowMarkers) && name.startsWith( "EditorMarker" ) ) return; auto nif = static_cast(iBlock.model()); if ( Node::SELECTING ) { - if ( scene->selMode & Scene::SelObject ) { + if ( scene->isSelModeObject() ) { int s_nodeId = ID2COLORKEY( nodeId ); glColor4ubv( (GLubyte *)&s_nodeId ); } else { @@ -1053,10 +1050,7 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) // 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 ) - { + if ( transColors.count() && scene->hasOption(Scene::DoVertexColors) && doVCs ) { glEnableClientState( GL_COLOR_ARRAY ); glColorPointer( 4, GL_FLOAT, 0, transColors.constData() ); } else { @@ -1127,7 +1121,7 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) glDisable( GL_POLYGON_OFFSET_FILL ); glPointSize( 8.5 ); - if ( scene->selMode & Scene::SelVertex ) { + if ( scene->isSelModeVertex() ) { drawVerts(); } @@ -1164,10 +1158,10 @@ void Mesh::drawVerts() const void Mesh::drawSelection() const { - if ( scene->options & Scene::ShowNodes ) + if ( scene->hasOption(Scene::ShowNodes) ) Node::drawSelection(); - if ( isHidden() || !(scene->selMode & Scene::SelObject) ) + if ( isHidden() || !scene->isSelModeObject() ) return; auto idx = scene->currentIndex; diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index e0aedac5d..218c5ad7d 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -474,7 +474,7 @@ Node * Node::findChild( const QString & str ) const bool Node::isHidden() const { - if ( scene->options & Scene::ShowHidden ) + if ( scene->hasOption(Scene::ShowHidden) ) return false; if ( flags.node.hidden || ( parent && parent->isHidden() ) ) @@ -528,7 +528,7 @@ void Node::draw() if ( isHidden() || iBlock == scene->currentBlock ) return; - if ( !(scene->selMode & Scene::SelObject) ) + if ( !scene->isSelModeObject() ) return; if ( Node::SELECTING ) { @@ -585,7 +585,7 @@ void Node::drawSelection() const if ( !nif ) return; - if ( !(scene->selMode & Scene::SelObject) ) + if ( !scene->isSelModeObject() ) return; bool extraData = false; @@ -675,7 +675,7 @@ void Node::drawSelection() const } - if ( currentBlock.endsWith( "Node" ) && scene->options & Scene::ShowNodes && scene->options & Scene::ShowAxes ) { + if ( currentBlock.endsWith( "Node" ) && scene->hasOption(Scene::ShowNodes) && scene->hasOption(Scene::ShowAxes) ) { glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); Transform t; @@ -774,7 +774,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackselMode & Scene::SelObject) ) + if ( !scene->isSelModeObject() ) return; stack.push( iShape ); @@ -966,7 +966,6 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStack 0 ) { QModelIndex iParent = scene->currentIndex.parent(); - int rowCount = nif->rowCount( iParent ); for ( int j = 0; j < i; j++ ) { totalVerts += nif->get( iParent.child( j, 0 ), "Num Vertices" ); } @@ -1063,10 +1062,10 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackoptions & Scene::ShowConstraints) ) ) + if ( !( nif && iConstraint.isValid() && scene && scene->hasOption(Scene::ShowConstraints) ) ) return; - if ( !(scene->selMode & Scene::SelObject) ) + if ( !scene->isSelModeObject() ) return; Transform tBodyA; @@ -1369,7 +1368,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c void Node::drawHavok() { - if ( !(scene->selMode & Scene::SelObject) ) + if ( !scene->isSelModeObject() ) return; // TODO: Why are all these here - "drawNodes", "drawFurn", "drawHavok"? @@ -1565,13 +1564,13 @@ void Node::drawHavok() drawHvkShape( nif, nif->getBlock( nif->getLink( iBody, "Shape" ) ), shapeStack, scene, colors[ color_index ] ); - if ( Node::SELECTING && scene->options & Scene::ShowAxes ) { + if ( Node::SELECTING && scene->hasOption(Scene::ShowAxes) ) { int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iBody ) ); glColor4ubv( (GLubyte *)&s_nodeId ); glDepthFunc( GL_ALWAYS ); drawAxes( Vector3( nif->get( iBody, "Center" ) ), 1.0f / bhkScaleMult( nif ), false ); glDepthFunc( GL_LEQUAL ); - } else if ( scene->options & Scene::ShowAxes ) { + } else if ( scene->hasOption(Scene::ShowAxes) ) { drawAxes( Vector3( nif->get( iBody, "Center" ) ), 1.0f / bhkScaleMult( nif ) ); } @@ -1779,7 +1778,7 @@ void Node::drawFurn() if ( !( iBlock.isValid() && nif ) ) return; - if ( !(scene->selMode & Scene::SelObject) ) + if ( !scene->isSelModeObject() ) return; QModelIndex iExtraDataList = nif->getIndex( iBlock, "Extra Data List" ); @@ -1878,10 +1877,8 @@ BoundSphere Node::bounds() const { BoundSphere boundsphere; - auto opts = scene->options; - // the node itself - if ( (opts & Scene::ShowNodes) || (opts & Scene::ShowCollision) ) { + if ( scene->hasOption(Scene::ShowNodes) || scene->hasOption(Scene::ShowCollision) ) { boundsphere |= BoundSphere( worldTrans().translation, 0 ); } diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 9bd5249a1..4f9dea77c 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -252,7 +252,7 @@ void AlphaProperty::setThreshold( float threshold ) void glProperty( AlphaProperty * p ) { - if ( p && p->alphaBlend && (p->scene->options & Scene::DoBlending) ) { + if ( p && p->alphaBlend && p->scene->hasOption(Scene::DoBlending) ) { glDisable( GL_POLYGON_OFFSET_FILL ); glEnable( GL_BLEND ); glBlendFunc( p->alphaSrc, p->alphaDst ); @@ -260,7 +260,7 @@ void glProperty( AlphaProperty * p ) glDisable( GL_BLEND ); } - if ( p && p->alphaTest && (p->scene->options & Scene::DoBlending) ) { + if ( p && p->alphaTest && p->scene->hasOption(Scene::DoBlending) ) { glDisable( GL_POLYGON_OFFSET_FILL ); glEnable( GL_ALPHA_TEST ); glAlphaFunc( p->alphaFunc, p->alphaThreshold ); @@ -539,7 +539,7 @@ int TexturingProperty::getId( const QString & texname ) void glProperty( TexturingProperty * p ) { - if ( p && (p->scene->options & Scene::DoTexturing) && p->bind( 0 ) ) { + if ( p && p->scene->hasOption(Scene::DoTexturing) && p->bind(0) ) { glEnable( GL_TEXTURE_2D ); } } @@ -609,7 +609,7 @@ void TextureProperty::setController( const NifModel * nif, const QModelIndex & i void glProperty( TextureProperty * p ) { - if ( p && (p->scene->options & Scene::DoTexturing) && p->bind() ) { + if ( p && p->scene->hasOption(Scene::DoTexturing) && p->bind() ) { glEnable( GL_TEXTURE_2D ); } } @@ -867,7 +867,7 @@ void BSShaderLightingProperty::update( const NifModel * nif, const QModelIndex & void glProperty( BSShaderLightingProperty * p ) { - if ( p && (p->scene->options & Scene::DoTexturing) && p->bind( 0 ) ) { + if ( p && p->scene->hasOption(Scene::DoTexturing) && p->bind(0) ) { glEnable( GL_TEXTURE_2D ); } } diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 72c4517ec..b448770a4 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -336,11 +336,11 @@ void Scene::draw() { drawShapes(); - if ( options & ShowNodes ) + if ( hasOption(ShowNodes) ) drawNodes(); - if ( options & ShowCollision ) + if ( hasOption(ShowCollision) ) drawHavok(); - if ( options & ShowMarkers ) + if ( hasOption(ShowMarkers) ) drawFurn(); drawSelection(); @@ -348,7 +348,7 @@ void Scene::draw() void Scene::drawShapes() { - if ( options & DoBlending ) { + if ( hasOption(DoBlending) ) { NodeList secondPass; for ( Node * node : roots.list() ) { @@ -470,7 +470,7 @@ QString Scene::textStats() int Scene::bindTexture( const QString & fname ) { - if ( !(options & DoTexturing) || fname.isEmpty() ) + if ( !hasOption(DoTexturing) || fname.isEmpty() ) return 0; return textures->bind( fname, game ); @@ -478,7 +478,7 @@ int Scene::bindTexture( const QString & fname ) int Scene::bindTexture( const QModelIndex & iSource ) { - if ( !(options & DoTexturing) || !iSource.isValid() ) + if ( !hasOption(DoTexturing) || !iSource.isValid() ) return 0; return textures->bind( iSource, game ); diff --git a/src/gl/glscene.h b/src/gl/glscene.h index 882902725..bcc23f015 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -120,6 +120,7 @@ class Scene final : public QObject Q_DECLARE_FLAGS( SceneOptions, SceneOption ); SceneOptions options; + inline bool hasOption(SceneOptions optValue) const { return ( options & optValue ); } enum VisModes { @@ -132,6 +133,7 @@ class Scene final : public QObject Q_DECLARE_FLAGS( VisMode, VisModes ); VisMode visMode; + inline bool hasVisMode(VisModes modeValue) const { return ( visMode & modeValue ); } enum SelModes { @@ -143,6 +145,8 @@ class Scene final : public QObject Q_DECLARE_FLAGS( SelMode, SelModes ); SelMode selMode; + inline bool isSelModeObject() const { return ( selMode & SelObject ); } + inline bool isSelModeVertex() const { return ( selMode & SelVertex ); } enum LodLevel { diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index cf9d3fd2c..36e9cce15 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -477,8 +477,8 @@ QString Renderer::setupProgram( Shape * mesh, const QString & hint ) mesh->activeProperties( props ); if ( !shader_ready || hint.isNull() - || (mesh->scene->options & Scene::DisableShaders) - || (mesh->scene->visMode & Scene::VisSilhouette) + || mesh->scene->hasOption(Scene::DisableShaders) + || mesh->scene->hasVisMode(Scene::VisSilhouette) || (mesh->nifVersion == 0) ) { setupFixedFunction( mesh, props ); return {}; @@ -607,9 +607,12 @@ static QString cube = "shaders/cubemap.dds"; bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & props, const QVector & iBlocks, bool eval ) { - const NifModel * nif = qobject_cast( mesh->index().model() ); + auto meshidx = mesh->index(); + if ( !meshidx.isValid() ) + return false; - if ( !mesh->index().isValid() || !nif ) + const NifModel * nif = qobject_cast(meshidx.model()); + if ( !nif ) return false; if ( eval && !prog->conditions.eval( nif, iBlocks ) ) @@ -617,18 +620,17 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & fn->glUseProgram( prog->id ); - auto opts = mesh->scene->options; - auto vis = mesh->scene->visMode; + auto scene = mesh->scene; + auto lsp = mesh->bslsp; + auto esp = mesh->bsesp; Material * mat = nullptr; - if ( mesh->bslsp && mesh->bslsp->mat() ) - mat = mesh->bslsp->mat(); - else if ( mesh->bsesp && mesh->bsesp->mat() ) - mat = mesh->bsesp->mat(); + if ( lsp ) + mat = lsp->mat(); + if ( esp && !mat ) + mat = esp->mat(); - QString default_n = ::default_n; - if ( mesh->nifVersion == 155 ) - default_n = ::default_ns; + QString default_n = (mesh->nifVersion == 155) ? ::default_ns : ::default_n; // texturing @@ -638,23 +640,23 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // to be accessible from here TexClampMode clamp = TexClampMode::WRAP_S_WRAP_T; - if ( mesh->bslsp ) - clamp = mesh->bslsp->getClampMode(); + if ( lsp ) + clamp = lsp->getClampMode(); int texunit = 0; if ( bsprop ) { QString forced; - if ( (opts & Scene::DoLighting) && (vis & Scene::VisNormalsOnly) ) + if ( scene->hasOption(Scene::DoLighting) && scene->hasVisMode(Scene::VisNormalsOnly) ) forced = white; QString alt = white; - if ( opts & Scene::DoErrorColor ) + if ( scene->hasOption(Scene::DoErrorColor) ) alt = magenta; bool result = prog->uniSampler( bsprop, SAMP_BASE, 0, texunit, alt, clamp, forced ); } else { GLint uniBaseMap = prog->uniformLocations[SAMP_BASE]; - if ( uniBaseMap >= 0 && (texprop || (bsprop && mesh->bslsp)) ) { + if ( uniBaseMap >= 0 && (texprop || (bsprop && lsp)) ) { if ( !activateTextureUnit( texunit ) || (texprop && !texprop->bind( 0 )) ) prog->uniSamplerBlank( SAMP_BASE, texunit ); else @@ -662,9 +664,9 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } } - if ( bsprop && !mesh->bsesp ) { + if ( bsprop && !esp ) { QString forced; - if ( !(opts & Scene::DoLighting) ) + if ( !scene->hasOption(Scene::DoLighting) ) forced = default_n; prog->uniSampler( bsprop, SAMP_NORMAL, 1, texunit, default_n, clamp, forced ); } else if ( !bsprop ) { @@ -692,7 +694,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } } - if ( bsprop && !mesh->bsesp ) { + if ( bsprop && !esp ) { prog->uniSampler( bsprop, SAMP_GLOW, 2, texunit, black, clamp ); } else if ( !bsprop ) { GLint uniGlowMap = prog->uniformLocations[SAMP_GLOW]; @@ -721,112 +723,111 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // BSLightingShaderProperty - if ( mesh->bslsp ) { - prog->uni1f( LIGHT_EFF1, mesh->bslsp->getLightingEffect1() ); - prog->uni1f( LIGHT_EFF2, mesh->bslsp->getLightingEffect2() ); + if ( lsp ) { + prog->uni1f( LIGHT_EFF1, lsp->getLightingEffect1() ); + prog->uni1f( LIGHT_EFF2, lsp->getLightingEffect2() ); - prog->uni1f( ALPHA, mesh->bslsp->getAlpha() ); + prog->uni1f( ALPHA, lsp->getAlpha() ); - auto uvS = mesh->bslsp->getUvScale(); + auto uvS = lsp->getUvScale(); prog->uni2f( UV_SCALE, uvS.x, uvS.y ); - auto uvO = mesh->bslsp->getUvOffset(); + auto uvO = lsp->getUvOffset(); prog->uni2f( UV_OFFSET, uvO.x, uvO.y ); prog->uni4m( MAT_VIEW, mesh->viewTrans().toMatrix4() ); prog->uni4m( MAT_WORLD, mesh->worldTrans().toMatrix4() ); - prog->uni1i( G2P_COLOR, mesh->bslsp->greyscaleColor ); + prog->uni1i( G2P_COLOR, lsp->greyscaleColor ); prog->uniSampler( bsprop, SAMP_GRAYSCALE, 3, texunit, "", TexClampMode::MIRRORED_S_MIRRORED_T ); - prog->uni1i( HAS_TINT_COLOR, mesh->bslsp->hasTintColor ); - if ( mesh->bslsp->hasTintColor ) { - auto tC = mesh->bslsp->getTintColor(); + prog->uni1i( HAS_TINT_COLOR, lsp->hasTintColor ); + if ( lsp->hasTintColor ) { + auto tC = lsp->getTintColor(); prog->uni3f( TINT_COLOR, tC.red(), tC.green(), tC.blue() ); } - prog->uni1i( HAS_MAP_DETAIL, mesh->bslsp->hasDetailMask ); + prog->uni1i( HAS_MAP_DETAIL, lsp->hasDetailMask ); prog->uniSampler( bsprop, SAMP_DETAIL, 3, texunit, "shaders/blankdetailmap.dds", clamp ); - prog->uni1i( HAS_MAP_TINT, mesh->bslsp->hasTintMask ); + prog->uni1i( HAS_MAP_TINT, lsp->hasTintMask ); prog->uniSampler( bsprop, SAMP_TINT, 6, texunit, gray, clamp ); // Rim & Soft params - prog->uni1i( HAS_SOFT, mesh->bslsp->hasSoftlight ); - prog->uni1i( HAS_RIM, mesh->bslsp->hasRimlight ); + prog->uni1i( HAS_SOFT, lsp->hasSoftlight ); + prog->uni1i( HAS_RIM, lsp->hasRimlight ); prog->uniSampler( bsprop, SAMP_LIGHT, 2, texunit, default_n, clamp ); // Backlight params - prog->uni1i( HAS_MAP_BACK, mesh->bslsp->hasBacklight ); + prog->uni1i( HAS_MAP_BACK, lsp->hasBacklight ); prog->uniSampler( bsprop, SAMP_BACKLIGHT, 7, texunit, default_n, clamp ); // Glow params - if ( (opts & Scene::DoGlow) && (opts & Scene::DoLighting) && (mesh->bslsp->hasEmittance || mesh->nifVersion == 155) ) - prog->uni1f( GLOW_MULT, mesh->bslsp->getEmissiveMult() ); + if ( scene->hasOption(Scene::DoGlow) && scene->hasOption(Scene::DoLighting) && (lsp->hasEmittance || mesh->nifVersion == 155) ) + prog->uni1f( GLOW_MULT, lsp->getEmissiveMult() ); else prog->uni1f( GLOW_MULT, 0 ); - prog->uni1i( HAS_EMIT, mesh->bslsp->hasEmittance ); - prog->uni1i( HAS_MAP_GLOW, mesh->bslsp->hasGlowMap ); - auto emC = mesh->bslsp->getEmissiveColor(); + prog->uni1i( HAS_EMIT, lsp->hasEmittance ); + prog->uni1i( HAS_MAP_GLOW, lsp->hasGlowMap ); + auto emC = lsp->getEmissiveColor(); prog->uni3f( GLOW_COLOR, emC.red(), emC.green(), emC.blue() ); // Specular params - float s = ((opts & Scene::DoSpecular) && (opts & Scene::DoLighting)) ? mesh->bslsp->getSpecularStrength() : 0.0; + float s = (scene->hasOption(Scene::DoSpecular) && scene->hasOption(Scene::DoLighting)) ? lsp->getSpecularStrength() : 0.0; prog->uni1f( SPEC_SCALE, s ); // Assure specular power does not break the shaders - auto gloss = mesh->bslsp->getSpecularGloss(); + auto gloss = lsp->getSpecularGloss(); prog->uni1f( SPEC_GLOSS, gloss ); - auto spec = mesh->bslsp->getSpecularColor(); + auto spec = lsp->getSpecularColor(); prog->uni3f( SPEC_COLOR, spec.red(), spec.green(), spec.blue() ); - prog->uni1i( HAS_MAP_SPEC, mesh->bslsp->hasSpecularMap ); + prog->uni1i( HAS_MAP_SPEC, lsp->hasSpecularMap ); if ( mesh->nifVersion <= 130 ) { - if ( mesh->nifVersion == 130 || (mesh->bslsp->hasSpecularMap && !mesh->bslsp->hasBacklight) ) + if ( mesh->nifVersion == 130 || (lsp->hasSpecularMap && !lsp->hasBacklight) ) prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, white, clamp ); else prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, black, clamp ); } if ( mesh->nifVersion >= 130 ) { - prog->uni1i( DOUBLE_SIDE, mesh->bslsp->getIsDoubleSided() ); - prog->uni1f( G2P_SCALE, mesh->bslsp->paletteScale ); - prog->uni1f( SS_ROLLOFF, mesh->bslsp->getLightingEffect1() ); - prog->uni1f( POW_FRESNEL, mesh->bslsp->fresnelPower ); - prog->uni1f( POW_RIM, mesh->bslsp->rimPower ); - prog->uni1f( POW_BACK, mesh->bslsp->backlightPower ); + prog->uni1i( DOUBLE_SIDE, lsp->getIsDoubleSided() ); + prog->uni1f( G2P_SCALE, lsp->paletteScale ); + prog->uni1f( SS_ROLLOFF, lsp->getLightingEffect1() ); + prog->uni1f( POW_FRESNEL, lsp->fresnelPower ); + prog->uni1f( POW_RIM, lsp->rimPower ); + prog->uni1f( POW_BACK, lsp->backlightPower ); } // Multi-Layer prog->uniSampler( bsprop, SAMP_INNER, 6, texunit, default_n, clamp ); - if ( mesh->bslsp->hasMultiLayerParallax ) { + if ( lsp->hasMultiLayerParallax ) { - auto inS = mesh->bslsp->getInnerTextureScale(); + auto inS = lsp->getInnerTextureScale(); prog->uni2f( INNER_SCALE, inS.x, inS.y ); - prog->uni1f( INNER_THICK, mesh->bslsp->getInnerThickness() ); + prog->uni1f( INNER_THICK, lsp->getInnerThickness() ); - prog->uni1f( OUTER_REFR, mesh->bslsp->getOuterRefractionStrength() ); - prog->uni1f( OUTER_REFL, mesh->bslsp->getOuterReflectionStrength() ); + prog->uni1f( OUTER_REFR, lsp->getOuterRefractionStrength() ); + prog->uni1f( OUTER_REFL, lsp->getOuterReflectionStrength() ); } // Environment Mapping - prog->uni1i( HAS_MAP_CUBE, mesh->bslsp->hasEnvironmentMap ); - prog->uni1i( HAS_MASK_ENV, mesh->bslsp->useEnvironmentMask ); + prog->uni1i( HAS_MAP_CUBE, lsp->hasEnvironmentMap ); + prog->uni1i( HAS_MASK_ENV, lsp->useEnvironmentMask ); float refl = 0.0; - if ( mesh->bslsp->hasEnvironmentMap - && (opts & Scene::DoCubeMapping) && (opts & Scene::DoLighting) ) - refl = mesh->bslsp->getEnvironmentReflection(); + if ( lsp->hasEnvironmentMap && scene->hasOption(Scene::DoCubeMapping) && scene->hasOption(Scene::DoLighting) ) + refl = lsp->getEnvironmentReflection(); prog->uni1f( ENV_REFLECTION, refl ); @@ -852,70 +853,70 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } // Parallax - prog->uni1i( HAS_MAP_HEIGHT, mesh->bslsp->hasHeightMap ); + prog->uni1i( HAS_MAP_HEIGHT, lsp->hasHeightMap ); prog->uniSampler( bsprop, SAMP_HEIGHT, 3, texunit, gray, clamp ); } // BSEffectShaderProperty - if ( mesh->bsesp ) { + if ( esp ) { prog->uni4m( MAT_WORLD, mesh->worldTrans().toMatrix4() ); - clamp = mesh->bsesp->getClampMode(); + clamp = esp->getClampMode(); prog->uniSampler( bsprop, SAMP_BASE, 0, texunit, white, clamp ); - prog->uni1i( DOUBLE_SIDE, mesh->bsesp->getIsDoubleSided() ); + prog->uni1i( DOUBLE_SIDE, esp->getIsDoubleSided() ); - auto uvS = mesh->bsesp->getUvScale(); + auto uvS = esp->getUvScale(); prog->uni2f( UV_SCALE, uvS.x, uvS.y ); - auto uvO = mesh->bsesp->getUvOffset(); + auto uvO = esp->getUvOffset(); prog->uni2f( UV_OFFSET, uvO.x, uvO.y ); - prog->uni1i( HAS_MAP_BASE, mesh->bsesp->hasSourceTexture ); - prog->uni1i( HAS_MAP_G2P, mesh->bsesp->hasGreyscaleMap ); + prog->uni1i( HAS_MAP_BASE, esp->hasSourceTexture ); + prog->uni1i( HAS_MAP_G2P, esp->hasGreyscaleMap ); - prog->uni1i( G2P_ALPHA, mesh->bsesp->greyscaleAlpha ); - prog->uni1i( G2P_COLOR, mesh->bsesp->greyscaleColor ); + prog->uni1i( G2P_ALPHA, esp->greyscaleAlpha ); + prog->uni1i( G2P_COLOR, esp->greyscaleColor ); - prog->uni1i( USE_FALLOFF, mesh->bsesp->useFalloff ); - prog->uni1i( HAS_RGBFALL, mesh->bsesp->hasRGBFalloff ); - prog->uni1i( HAS_WEAP_BLOOD, mesh->bsesp->hasWeaponBlood ); + prog->uni1i( USE_FALLOFF, esp->useFalloff ); + prog->uni1i( HAS_RGBFALL, esp->hasRGBFalloff ); + prog->uni1i( HAS_WEAP_BLOOD, esp->hasWeaponBlood ); // Glow params - auto emC = mesh->bsesp->getEmissiveColor(); + auto emC = esp->getEmissiveColor(); prog->uni4f( GLOW_COLOR, emC.red(), emC.green(), emC.blue(), emC.alpha() ); - prog->uni1f( GLOW_MULT, mesh->bsesp->getEmissiveMult() ); + prog->uni1f( GLOW_MULT, esp->getEmissiveMult() ); // Falloff params prog->uni4f( FALL_PARAMS, - mesh->bsesp->falloff.startAngle, mesh->bsesp->falloff.stopAngle, - mesh->bsesp->falloff.startOpacity, mesh->bsesp->falloff.stopOpacity + esp->falloff.startAngle, esp->falloff.stopAngle, + esp->falloff.startOpacity, esp->falloff.stopOpacity ); - prog->uni1f( FALL_DEPTH, mesh->bsesp->falloff.softDepth ); + prog->uni1f( FALL_DEPTH, esp->falloff.softDepth ); // BSEffectShader textures prog->uniSampler( bsprop, SAMP_GRAYSCALE, 1, texunit, "", TexClampMode::MIRRORED_S_MIRRORED_T ); if ( mesh->nifVersion >= 130 ) { - prog->uni1f( LIGHT_INF, mesh->bsesp->getLightingInfluence() ); + prog->uni1f( LIGHT_INF, esp->getLightingInfluence() ); - prog->uni1i( HAS_MAP_NORMAL, mesh->bsesp->hasNormalMap && (opts & Scene::DoLighting) ); + prog->uni1i( HAS_MAP_NORMAL, esp->hasNormalMap && scene->hasOption(Scene::DoLighting) ); prog->uniSampler( bsprop, SAMP_NORMAL, 3, texunit, default_n, clamp ); - prog->uni1i( HAS_MAP_CUBE, mesh->bsesp->hasEnvMap ); - prog->uni1i( HAS_MASK_ENV, mesh->bsesp->hasEnvMask ); + prog->uni1i( HAS_MAP_CUBE, esp->hasEnvMap ); + prog->uni1i( HAS_MASK_ENV, esp->hasEnvMask ); float refl = 0.0; - if ( mesh->bsesp->hasEnvMap && (opts & Scene::DoCubeMapping) && (opts & Scene::DoLighting) ) - refl = mesh->bsesp->getEnvironmentReflection(); + if ( esp->hasEnvMap && scene->hasOption(Scene::DoCubeMapping) && scene->hasOption(Scene::DoLighting) ) + refl = esp->getEnvironmentReflection(); prog->uni1f( ENV_REFLECTION, refl ); @@ -938,12 +939,12 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & prog->uniSampler( bsprop, SAMP_LIGHTING, 7, texunit, lighting, clamp ); } - prog->uni1f( LUM_EMIT, mesh->bsesp->lumEmittance ); + prog->uni1f( LUM_EMIT, esp->lumEmittance ); } } // Defaults for uniforms in older meshes - if ( !mesh->bsesp && !mesh->bslsp ) { + if ( !esp && !lsp ) { prog->uni2f( UV_SCALE, 1.0, 1.0 ); prog->uni2f( UV_OFFSET, 0.0, 0.0 ); } @@ -1012,8 +1013,8 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // setup blending glProperty( props.get() ); - - if ( mat && (mesh->scene->options & Scene::DoBlending) ) { + + if ( mat && scene->hasOption(Scene::DoBlending) ) { static const GLenum blendMap[11] = { GL_ONE, GL_ZERO, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, @@ -1149,7 +1150,7 @@ void Renderer::setupFixedFunction( Shape * mesh, const PropertyList & props ) // setup texturing - if ( !(mesh->scene->options & Scene::DoTexturing) ) + if ( !mesh->scene->hasOption(Scene::DoTexturing) ) return; if ( TexturingProperty * texprop = props.get() ) { diff --git a/src/glview.cpp b/src/glview.cpp index 8e5ca3b31..cd5a53948 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -300,7 +300,7 @@ void GLView::initializeGL() { GLenum err; - if ( scene->options & Scene::DoMultisampling ) { + if ( scene->hasOption(Scene::DoMultisampling) ) { if ( !glContext->hasExtension( "GL_EXT_framebuffer_multisample" ) ) { scene->options &= ~Scene::DoMultisampling; //qDebug() << "System does not support multisampling"; @@ -345,7 +345,7 @@ void GLView::glProjection( int x, int y ) BoundSphere bs = scene->view * scene->bounds(); - if ( scene->options & Scene::ShowAxes ) { + if ( scene->hasOption(Scene::ShowAxes) ) { bs |= BoundSphere( scene->view * Vector3(), axis ); } @@ -413,7 +413,7 @@ void GLView::paintGL() glPushMatrix(); // Clear Viewport - if ( scene->visMode & Scene::VisSilhouette ) { + if ( scene->hasVisMode(Scene::VisSilhouette) ) { qglClearColor( QColor( 255, 255, 255, 255 ) ); } @@ -475,7 +475,7 @@ void GLView::paintGL() glLoadIdentity(); // Draw the grid - if ( scene->options & Scene::ShowGrid ) { + if ( scene->hasOption(Scene::ShowGrid) ) { glDisable( GL_ALPHA_TEST ); glDisable( GL_BLEND ); glDisable( GL_LIGHTING ); @@ -519,7 +519,7 @@ void GLView::paintGL() GLfloat mat_spec[] = { 0.0f, 0.0f, 0.0f, 1.0f }; - if ( scene->options & Scene::DoLighting ) { + if ( scene->hasOption(Scene::DoLighting) ) { // Setup light Vector4 lightDir( 0.0, 0.0, 1.0, 0.0 ); @@ -530,7 +530,7 @@ void GLView::paintGL() v = m * v; lightDir = Vector4( viewTrans.rotation * v, 0.0 ); - if ( scene->visMode & Scene::VisLightPos ) { + if ( scene->hasVisMode(Scene::VisLightPos) ) { glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_DEPTH_TEST ); @@ -557,10 +557,7 @@ void GLView::paintGL() } float amb = ambient; - if ( (scene->visMode & Scene::VisNormalsOnly) - && (scene->options & Scene::DoTexturing) - && !(scene->options & Scene::DisableShaders) ) - { + if ( scene->hasVisMode(Scene::VisNormalsOnly) && scene->hasOption(Scene::DoTexturing) && !scene->hasOption(Scene::DisableShaders) ) { amb = 0.1f; } @@ -576,10 +573,7 @@ void GLView::paintGL() 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; - } + float amb = scene->hasOption(Scene::DisableShaders) ? 0.0f : 0.5f; GLfloat mat_amb[] = { amb, amb, amb, 1.0f }; GLfloat mat_diff[] = { 1.0f, 1.0f, 1.0f, 1.0f }; @@ -593,7 +587,7 @@ void GLView::paintGL() glLightfv( GL_LIGHT0, GL_SPECULAR, mat_spec ); } - if ( scene->visMode & Scene::VisSilhouette ) { + if ( scene->hasVisMode(Scene::VisSilhouette) ) { GLfloat mat_diff[] = { 0.0f, 0.0f, 0.0f, 1.0f }; GLfloat mat_amb[] = { 0.0f, 0.0f, 0.0f, 1.0f }; @@ -607,7 +601,7 @@ void GLView::paintGL() glLightfv( GL_LIGHT0, GL_SPECULAR, mat_spec ); } - if ( scene->options & Scene::DoMultisampling ) + if ( scene->hasOption(Scene::DoMultisampling) ) glEnable( GL_MULTISAMPLE_ARB ); #ifndef QT_NO_DEBUG @@ -634,7 +628,7 @@ void GLView::paintGL() // Draw the model scene->draw(); - if ( scene->options & Scene::ShowAxes ) { + if ( scene->hasOption(Scene::ShowAxes) ) { // Resize viewport to small corner of screen int axesSize = std::min( width() / 10, 125 ); glViewport( 0, 0, axesSize, axesSize ); @@ -887,13 +881,13 @@ QModelIndex GLView::indexAt( const QPoint & pos, int cycle ) QList df; - if ( scene->options & Scene::ShowCollision ) + if ( scene->hasOption(Scene::ShowCollision) ) df << &Scene::drawHavok; - if ( scene->options & Scene::ShowNodes ) + if ( scene->hasOption(Scene::ShowNodes) ) df << &Scene::drawNodes; - if ( scene->options & Scene::ShowMarkers ) + if ( scene->hasOption(Scene::ShowMarkers) ) df << &Scene::drawFurn; df << &Scene::drawShapes; @@ -909,7 +903,7 @@ QModelIndex GLView::indexAt( const QPoint & pos, int cycle ) QModelIndex chooseIndex; - if ( scene->selMode & Scene::SelVertex ) { + if ( scene->isSelModeVertex() ) { // Vertex int block = choose >> 16; int vert = choose - (block << 16); From 762a575e62cca3f0ebb3eec91f29c19e87c3ae94 Mon Sep 17 00:00:00 2001 From: gavrant Date: Sun, 18 Jun 2023 14:37:18 +0300 Subject: [PATCH 056/118] Major refactoring and streamlining of updating internal classes from nif data. Primary goal: making sure that the properties of internal classes are reset on each update. That should fix some bugs happening when a nif is changed in NifSkope. Secondary goals: optimizations and better structure of the code. Bonuses: - Replaced NifModel::getUserVersion2() with new NifModel::getBSVersion(). The latter has a better name and returns the version from cache (created once, on file load) instead of reading it from the header every time. - Added static NifModel functions for very common casts of QModelIndex to NifModel. - Replaced many "protected property + basic getter and setter" in Property classes with just "public property" to simplify the code. - Deleted some obsolete/unused properties and functions, mainly in IControllable family of classes (nif nodes and properties). - Fixed some compiler warnings. - Better naming, minor optimizations, cleanup here and there. --- src/gl/bsshape.cpp | 411 ++++------- src/gl/bsshape.h | 27 +- src/gl/controllers.cpp | 74 +- src/gl/glcontroller.cpp | 55 +- src/gl/glcontroller.h | 5 +- src/gl/glmesh.cpp | 1286 ++++++++++++++++----------------- src/gl/glmesh.h | 65 +- src/gl/glnode.cpp | 110 ++- src/gl/glnode.h | 6 +- src/gl/glparticles.cpp | 41 +- src/gl/glparticles.h | 4 +- src/gl/glproperty.cpp | 947 ++++++++++-------------- src/gl/glproperty.h | 264 +++---- src/gl/glscene.cpp | 50 +- src/gl/glscene.h | 2 +- src/gl/gltex.cpp | 13 +- src/gl/gltexloaders.cpp | 10 +- src/gl/gltools.cpp | 6 +- src/gl/icontrollable.h | 7 +- src/gl/renderer.cpp | 124 ++-- src/glview.cpp | 1 - src/io/material.h | 7 +- src/lib/importex/importex.cpp | 18 +- src/lib/importex/obj.cpp | 14 +- src/model/nifmodel.cpp | 35 +- src/model/nifmodel.h | 17 +- src/nifskope_ui.cpp | 4 +- src/spells/blocks.cpp | 11 +- src/spells/flags.cpp | 3 +- src/spells/havok.cpp | 4 +- src/spells/mesh.cpp | 5 +- src/spells/normals.cpp | 12 +- src/spells/sanitize.cpp | 8 +- src/spells/sanitize.h | 4 +- src/spells/tangentspace.cpp | 16 +- src/spells/texture.cpp | 10 +- src/ui/widgets/uvedit.cpp | 6 +- src/ui/widgets/xmlcheck.cpp | 2 +- 38 files changed, 1619 insertions(+), 2065 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 43246921f..b96739dcd 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -7,202 +7,174 @@ #include "model/nifmodel.h" -BSShape::BSShape( Scene * s, const QModelIndex & b ) : Shape( s, b ) +void BSShape::updateImpl( const NifModel * nif, const QModelIndex & index ) { - -} + Shape::updateImpl( nif, index ); -void BSShape::clear() -{ - Node::clear(); - - iSkin = iSkinData = iSkinPart = QModelIndex(); - bssp = nullptr; - bslsp = nullptr; - bsesp = nullptr; - - verts.clear(); - norms.clear(); - tangents.clear(); - bitangents.clear(); - triangles.clear(); - coords.clear(); - colors.clear(); - bones.clear(); - weights.clear(); + if ( index == iBlock ) { + isLOD = nif->isNiBlock( iBlock, "BSMeshLODTriShape" ); + if ( isLOD ) + emit nif->lodSliderChanged(true); + } } -void BSShape::update( const NifModel * nif, const QModelIndex & index ) +void BSShape::updateData( const NifModel * nif ) { - Shape::update( nif, index ); + auto vertexFlags = nif->get(iBlock, "Vertex Desc"); - if ( !iBlock.isValid() || !index.isValid() ) - return; + isDynamic = nif->inherits(iBlock, "BSDynamicTriShape"); - bool extraData = nif->inherits( index, "NiProperty" ) || nif->inherits( index, "BSShaderTextureSet" ); + hasVertexColors = vertexFlags.HasFlag(VertexAttribute::VA_COLOR); - if ( iBlock != index && iSkin != index && iSkinData != index && !extraData ) - return; + dataBound = BoundSphere(nif, iBlock); - nifVersion = nif->getUserVersion2(); - // Update shaders from this mesh's shader property - updateShaderProperties( nif ); + // Is the shape skinned? + resetSkinning(); + if ( vertexFlags.HasFlag(VertexAttribute::VA_SKINNING) ) { + isSkinned = true; - if ( extraData ) - return; - - isLOD = nif->isNiBlock( iBlock, "BSMeshLODTriShape" ); - if ( isLOD ) - emit nif->lodSliderChanged( true ); - - auto vertexFlags = nif->get( iBlock, "Vertex Desc" ); + QString skinInstName, skinDataName; + if ( nif->getBSVersion() >= 130 ) { + skinInstName = "BSSkin::Instance"; + skinDataName = "BSSkin::BoneData"; + } else { + skinInstName = "NiSkinInstance"; + skinDataName = "NiSkinData"; + } - hasVertexColors = vertexFlags.HasFlag( VertexAttribute::VA_COLOR ); + iSkin = nif->getBlock( nif->getLink( nif->getIndex( iBlock, "Skin" ) ), skinInstName ); + if ( iSkin.isValid() ) { + iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), skinDataName ); + if ( nif->getBSVersion() == 100 ) + iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); + } + } - bool isDynamic = nif->inherits( iBlock, "BSDynamicTriShape" ); + bool hasDataOnSkinPart = ( isSkinned && iSkinPart.isValid() ); - bool isDataOnSkin = false; - bool isSkinned = vertexFlags & VertexFlags::VF_SKINNED; - if ( nifVersion >= 130 ) { - skinInstName = "BSSkin::Instance"; - skinDataName = "BSSkin::BoneData"; + // Fill vertex data + resetVertexData(); + numVerts = 0; + if ( hasDataOnSkinPart ) { + // For skinned geometry, the vertex data is stored in the NiSkinPartition + // The triangles are split up among the partitions + iData = nif->getIndex( iSkinPart, "Vertex Data" ); + int dataSize = nif->get( iSkinPart, "Data Size" ); + int vertexSize = nif->get( iSkinPart, "Vertex Size" ); + if ( iData.isValid() && dataSize > 0 && vertexSize > 0 ) + numVerts = dataSize / vertexSize; } else { - skinInstName = "NiSkinInstance"; - skinDataName = "NiSkinData"; + iData = nif->getIndex( iBlock, "Vertex Data" ); + if ( iData.isValid() ) + numVerts = nif->rowCount( iData ); } - iSkin = nif->getBlock( nif->getLink( nif->getIndex( iBlock, "Skin" ) ), skinInstName ); - if ( !iSkin.isValid() ) - isSkinned = false; + TexCoords coordset; // For compatibility with coords list - if ( isSkinned ) { - iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), skinDataName ); - iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); - - if ( nifVersion == 100 ) - isDataOnSkin = true; + QVector dynVerts; + if ( isDynamic ) { + dynVerts = nif->getArray( iBlock, "Vertices" ); + int nDynVerts = dynVerts.count(); + if ( nDynVerts < numVerts ) + numVerts = nDynVerts; } - updateData = true; - updateBounds |= updateData; - updateSkin = isSkinned; - transformRigid = !isSkinned; + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index( i, 0, iData ); + float bitX; - if ( updateBounds ) - dataBound = BoundSphere( nif, iBlock ); + if ( isDynamic ) { + auto dynv = dynVerts.at(i); + verts << Vector3( dynv ); + bitX = dynv[3]; + } else { + verts << nif->get( idx, "Vertex" ); + bitX = nif->getValue( nif->getIndex( idx, "Bitangent X" ) ).toFloat(); + } - int dataSize = 0; - if ( !isDataOnSkin ) { - iVertData = nif->getIndex( iBlock, "Vertex Data" ); - iTriData = nif->getIndex( iBlock, "Triangles" ); - iData = iVertData; - if ( !iVertData.isValid() || !iTriData.isValid() ) - return; + // Bitangent Y/Z + auto bitYi = nif->getValue( nif->getIndex(idx, "Bitangent Y") ).toCount(); + auto bitZi = nif->getValue( nif->getIndex(idx, "Bitangent Z") ).toCount(); + auto bitY = (double(bitYi) / 255.0) * 2.0 - 1.0; + auto bitZ = (double(bitZi) / 255.0) * 2.0 - 1.0; - numVerts = std::min( nif->get( iBlock, "Num Vertices" ), nif->rowCount( iVertData ) ); - numTris = std::min( nif->get( iBlock, "Num Triangles" ), nif->rowCount( iTriData ) ); + coordset << nif->get( idx, "UV" ); + norms += nif->get( idx, "Normal" ); + tangents += nif->get( idx, "Tangent" ); + bitangents += Vector3( bitX, bitY, bitZ ); - dataSize = nif->get( iBlock, "Data Size" ); - } else { - // For skinned geometry, the vertex data is stored on the NiSkinPartition - // The triangles are split up among the partitions - - iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); - if ( !iSkinPart.isValid() ) - return; + auto vcIdx = nif->getIndex( idx, "Vertex Colors" ); + colors += vcIdx.isValid() ? nif->get( vcIdx ) : Color4(0, 0, 0, 1); + } - iVertData = nif->getIndex( iSkinPart, "Vertex Data" ); - iTriData = QModelIndex(); - iData = iVertData; + // Add coords as the first set of QList + coords.append( coordset ); - dataSize = nif->get( iSkinPart, "Data Size" ); - auto vertexSize = nif->get( iSkinPart, "Vertex Size" ); - if ( !iVertData.isValid() || dataSize == 0 || vertexSize == 0 ) - return; + numVerts = verts.count(); - numVerts = dataSize / vertexSize; + // Fill triangle data + if ( hasDataOnSkinPart ) { + auto iPartitions = nif->getIndex( iSkinPart, "Partitions" ); + if ( iPartitions.isValid() ) { + int n = nif->rowCount( iPartitions ); + for ( int i = 0; i < n; i++ ) + triangles << nif->getArray( nif->index( i, 0, iPartitions ), "Triangles" ); + } + } + else { + auto iTriData = nif->getIndex( iBlock, "Triangles" ); + if ( iTriData.isValid() ) + triangles = nif->getArray( iTriData ); } + // TODO (Gavrant): validate triangles' vertex indices, throw out triangles with the wrong ones - if ( iBlock == index && dataSize > 0 ) { - verts.clear(); - norms.clear(); - tangents.clear(); - bitangents.clear(); - triangles.clear(); - coords.clear(); - colors.clear(); + // Fill skeleton data + resetSkeletonData(); + if ( isSkinned && iSkin.isValid() ) { + skeletonRoot = nif->getLink( iSkin, "Skeleton Root" ); + if ( nif->getBSVersion() < 130 ) + skeletonTrans = Transform( nif, iSkinData ); - // For compatibility with coords list - TexCoords coordset; + bones = nif->getLinkArray( iSkin, "Bones" ); + auto nTotalBones = bones.count(); - auto dynVerts = nif->getArray(iBlock, "Vertices"); - if ( isDynamic ) { - for ( const auto & v : dynVerts ) - verts << Vector3(v); - } + weights.fill( BoneWeights(), nTotalBones ); + for ( int i = 0; i < nTotalBones; i++ ) + weights[i].bone = bones[i]; + auto nTotalWeights = weights.count(); for ( int i = 0; i < numVerts; i++ ) { - auto idx = nif->index( i, 0, iVertData ); - - if ( !isDynamic ) - verts << nif->get( idx, "Vertex" ); - - coordset << nif->get( idx, "UV" ); + auto idx = nif->index( i, 0, iData ); + auto wts = nif->getArray( idx, "Bone Weights" ); + auto bns = nif->getArray( idx, "Bone Indices" ); + if ( wts.count() < 4 || bns.count() < 4 ) + continue; + + for ( int j = 0; j < 4; j++ ) { + if ( bns[j] >= nTotalWeights ) + continue; - // Bitangent X - auto bitX = nif->getValue( nif->getIndex( idx, "Bitangent X" ) ).toFloat(); - if ( isDynamic ) { - bitX = dynVerts.at(i)[3]; - } - // Bitangent Y/Z - auto bitYi = nif->getValue( nif->getIndex( idx, "Bitangent Y" ) ).toCount(); - auto bitZi = nif->getValue( nif->getIndex( idx, "Bitangent Z" ) ).toCount(); - auto bitY = (double( bitYi ) / 255.0) * 2.0 - 1.0; - auto bitZ = (double( bitZi ) / 255.0) * 2.0 - 1.0; - - norms += nif->get( idx, "Normal" ); - tangents += nif->get( idx, "Tangent" ); - bitangents += Vector3( bitX, bitY, bitZ ); - - auto vcIdx = nif->getIndex( idx, "Vertex Colors" ); - if ( vcIdx.isValid() ) { - colors += nif->get( vcIdx ); + if ( wts[j] > 0.0 ) + weights[bns[j]].weights << VertexWeight( i, wts[j] ); } } - - // Add coords as first set of QList - coords.append( coordset ); - - if ( !isDataOnSkin ) { - triangles = nif->getArray( iTriData ); - triangles = triangles.mid( 0, numTris ); - } else { - auto partIdx = nif->getIndex( iSkinPart, "Partitions" ); - for ( int i = 0; i < nif->rowCount( partIdx ); i++ ) - triangles << nif->getArray( nif->index( i, 0, partIdx ), "Triangles" ); - } - } - - if ( bssp ) - isVertexAlphaAnimation = bssp->hasSF2( ShaderFlags::SLSF2_Tree_Anim ); - - if ( isVertexAlphaAnimation ) { - for ( int i = 0; i < colors.count(); i++ ) - colors[i].setRGBA( colors[i].red(), colors[i].green(), colors[i].blue(), 1.0 ); + auto b = nif->getIndex( iSkinData, "Bone List" ); + for ( int i = 0; i < nTotalWeights; i++ ) + weights[i].setTransform( nif, b.child( i, 0 ) ); } } QModelIndex BSShape::vertexAt( int idx ) const { - auto nif = static_cast(iBlock.model()); + auto nif = NifModel::fromIndex( iBlock ); if ( !nif ) return QModelIndex(); // Vertices are on NiSkinPartition in version 100 auto blk = iBlock; - if ( nifVersion < 130 && iSkinPart.isValid() ) { - if ( nif->inherits( blk, "BSDynamicTriShape" ) ) + if ( iSkinPart.isValid() ) { + if ( isDynamic ) return nif->getIndex( blk, "Vertices" ).child( idx, 0 ); blk = iSkinPart; @@ -211,74 +183,13 @@ QModelIndex BSShape::vertexAt( int idx ) const return nif->getIndex( nif->getIndex( blk, "Vertex Data" ).child( idx, 0 ), "Vertex" ); } - -void BSShape::transform() -{ - if ( isHidden() ) - return; - - const NifModel * nif = static_cast(iBlock.model()); - if ( !nif || !iBlock.isValid() ) { - clear(); - return; - } - - if ( updateData ) { - updateData = false; - } - - if ( updateSkin ) { - updateSkin = false; - isSkinned = false; - - bones.clear(); - weights.clear(); - partitions.clear(); - - if ( iSkin.isValid() && iSkinData.isValid() ) { - skeletonRoot = nif->getLink( iSkin, "Skeleton Root" ); - if ( nifVersion < 130 ) - skeletonTrans = Transform( nif, iSkinData ); - - bones = nif->getLinkArray( iSkin, "Bones" ); - weights.fill( BoneWeights(), bones.count() ); - for ( int i = 0; i < bones.count(); i++ ) - weights[i].bone = bones[i]; - - for ( int i = 0; i < numVerts; i++ ) { - auto idx = nif->index( i, 0, iVertData ); - auto wts = nif->getArray( idx, "Bone Weights" ); - auto bns = nif->getArray( idx, "Bone Indices" ); - if ( wts.count() < 4 || bns.count() < 4 ) - continue; - - for ( int j = 0; j < 4; j++ ) { - if ( bns[j] >= weights.count() ) - continue; - - if ( wts[j] > 0.0 ) - weights[bns[j]].weights << VertexWeight( i, wts[j] ); - } - } - - auto b = nif->getIndex( iSkinData, "Bone List" ); - for ( int i = 0; i < weights.count(); i++ ) - weights[i].setTransform( nif, b.child( i, 0 ) ); - - isSkinned = weights.count(); - } - } - - Node::transform(); -} - void BSShape::transformShapes() { if ( isHidden() ) return; - const NifModel * nif = static_cast(iBlock.model()); - if ( !nif || !iBlock.isValid() ) { + auto nif = NifModel::fromValidIndex( iBlock ); + if ( !nif ) { clear(); return; } @@ -287,28 +198,25 @@ void BSShape::transformShapes() transformRigid = true; - if ( isSkinned && scene->hasOption(Scene::DoSkinning) ) { + if ( isSkinned && weights.count() && scene->hasOption(Scene::DoSkinning) ) { transformRigid = false; - int vcnt = verts.count(); - - transVerts.resize( vcnt ); + transVerts.resize( numVerts ); transVerts.fill( Vector3() ); - transNorms.resize( vcnt ); + transNorms.resize( numVerts ); transNorms.fill( Vector3() ); - transTangents.resize( vcnt ); + transTangents.resize( numVerts ); transTangents.fill( Vector3() ); - transBitangents.resize( vcnt ); + transBitangents.resize( numVerts ); transBitangents.fill( Vector3() ); - Node * root = findParent( 0 ); for ( const BoneWeights & bw : weights ) { Node * bone = root ? root->findChild( bw.bone ) : nullptr; if ( bone ) { Transform t = scene->view * bone->localTrans( 0 ) * bw.trans; for ( const VertexWeight & w : bw.weights ) { - if ( w.vertex >= vcnt ) + if ( w.vertex >= numVerts ) continue; transVerts[w.vertex] += t * verts[w.vertex] * w.weight; @@ -319,7 +227,7 @@ void BSShape::transformShapes() } } - for ( int n = 0; n < vcnt; n++ ) { + for ( int n = 0; n < numVerts; n++ ) { transNorms[n].normalize(); transTangents[n].normalize(); transBitangents[n].normalize(); @@ -327,7 +235,7 @@ void BSShape::transformShapes() boundSphere = BoundSphere( transVerts ); boundSphere.applyInv( viewTrans() ); - updateBounds = false; + needUpdateBounds = false; } else { transVerts = verts; transNorms = norms; @@ -336,11 +244,10 @@ void BSShape::transformShapes() } transColors = colors; - if ( nifVersion < 130 && bslsp ) { - if ( !(bslsp->getFlags1() & ShaderFlags::SLSF1_Vertex_Alpha) ) { - for ( int c = 0; c < colors.count(); c++ ) - transColors[c] = Color4( colors[c].red(), colors[c].green(), colors[c].blue(), 1.0f ); - } + // TODO (Gavrant): suspicious code. Should the check be replaced with !bssp.hasVertexAlpha ? + if ( nif->getBSVersion() < 130 && bslsp && !bslsp->hasSF1(ShaderFlags::SLSF1_Vertex_Alpha) ) { + for ( int c = 0; c < colors.count(); c++ ) + transColors[c] = Color4( colors[c].red(), colors[c].green(), colors[c].blue(), 1.0f ); } } @@ -355,7 +262,7 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) if ( !scene->hasOption(Scene::ShowMarkers) && name.contains( "EditorMarker" ) ) return; - auto nif = static_cast(iBlock.model()); + auto nif = NifModel::fromIndex( iBlock ); if ( Node::SELECTING ) { if ( scene->isSelModeObject() ) { @@ -367,13 +274,7 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) } // Draw translucent meshes in second pass - AlphaProperty * aprop = findProperty(); - Material * mat = (bssp) ? bssp->mat() : nullptr; - - drawSecond |= aprop && aprop->blend(); - drawSecond |= mat && mat->bDecal; - - if ( secondPass && drawSecond ) { + if ( secondPass && drawInSecondPass ) { secondPass->add( this ); return; } @@ -384,7 +285,7 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) } // Render polygon fill slightly behind alpha transparency and wireframe - if ( !drawSecond ) { + if ( !drawInSecondPass ) { glEnable( GL_POLYGON_OFFSET_FILL ); glPolygonOffset( 1.0f, 2.0f ); } @@ -396,15 +297,15 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) glEnableClientState( GL_NORMAL_ARRAY ); glNormalPointer( GL_FLOAT, 0, transNorms.constData() ); - bool doVCs = (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); + bool doVCs = ( bssp && bssp->hasSF2(ShaderFlags::SLSF2_Vertex_Colors) ); // Always do vertex colors for FO4 if colors present - if ( nifVersion >= 130 && hasVertexColors && colors.count() ) + if ( nif->getBSVersion() >= 130 && hasVertexColors && colors.count() ) doVCs = true; if ( transColors.count() && scene->hasOption(Scene::DoVertexColors) && doVCs ) { glEnableClientState( GL_COLOR_ARRAY ); glColorPointer( 4, GL_FLOAT, 0, transColors.constData() ); - } else if ( nifVersion < 130 && !hasVertexColors && (bslsp && bslsp->hasVertexColors) ) { + } else if ( nif->getBSVersion() < 130 && !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 ) ); @@ -415,14 +316,15 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) if ( !Node::SELECTING ) { - if ( nifVersion == 155 ) + if ( nif->getBSVersion() == 155 ) glEnable( GL_FRAMEBUFFER_SRGB ); else glDisable( GL_FRAMEBUFFER_SRGB ); shader = scene->renderer->setupProgram( this, shader ); - } else if ( nifVersion == 155 ) { - glDisable( GL_FRAMEBUFFER_SRGB ); + } else { + if ( nif->getBSVersion() == 155 ) + glDisable( GL_FRAMEBUFFER_SRGB ); } if ( isDoubleSided ) { @@ -492,15 +394,15 @@ void BSShape::drawVerts() const glVertex( transVerts.value( i ) ); } - auto nif = static_cast(iBlock.model()); + auto nif = NifModel::fromIndex( iBlock ); if ( !nif ) return; // Vertices are on NiSkinPartition in version 100 bool selected = iBlock == scene->currentBlock; - if ( nifVersion < 130 && iSkinPart.isValid() ) { + if ( iSkinPart.isValid() ) { selected |= iSkinPart == scene->currentBlock; - selected |= nif->inherits( iBlock, "BSDynamicTriShape" ); + selected |= isDynamic; } @@ -532,8 +434,8 @@ void BSShape::drawSelection() const // Is the current block extra data bool extraData = false; - auto nif = static_cast(idx.model()); - if ( !nif || !blk.isValid() ) + auto nif = NifModel::fromValidIndex(blk); + if ( !nif ) return; // Set current block name and detect if extra data @@ -887,7 +789,7 @@ void BSShape::drawSelection() const } // General wireframe - if ( blk == iBlock && idx != iVertData && p != "Vertex Data" && p != "Vertices" ) { + if ( blk == iBlock && idx != iData && p != "Vertex Data" && p != "Vertices" ) { glLineWidth( 1.6f ); glNormalColor(); for ( const Triangle& tri : triangles ) { @@ -906,8 +808,8 @@ void BSShape::drawSelection() const BoundSphere BSShape::bounds() const { - if ( updateBounds ) { - updateBounds = false; + if ( needUpdateBounds ) { + needUpdateBounds = false; if ( verts.count() ) { boundSphere = BoundSphere( verts ); } else { @@ -917,8 +819,3 @@ BoundSphere BSShape::bounds() const return worldTrans() * boundSphere; } - -bool BSShape::isHidden() const -{ - return Node::isHidden(); -} diff --git a/src/gl/bsshape.h b/src/gl/bsshape.h index 51922f764..72badbbf1 100644 --- a/src/gl/bsshape.h +++ b/src/gl/bsshape.h @@ -12,16 +12,7 @@ class BSShape : public Shape { public: - BSShape( Scene * s, const QModelIndex & b ); - ~BSShape() { clear(); } - - // IControllable - - void clear() override; - void update( const NifModel * nif, const QModelIndex & ) override; - void transform() override; - - // end IControllable + BSShape( Scene * s, const QModelIndex & b ) : Shape( s, b ) { } // Node @@ -32,9 +23,6 @@ class BSShape : public Shape BoundSphere bounds() const override; - bool isHidden() const override; - //QString textStats() const override; - // end Node // Shape @@ -43,17 +31,12 @@ class BSShape : public Shape QModelIndex vertexAt( int ) const override; protected: + BoundSphere dataBound; - QPersistentModelIndex iVertData; - QPersistentModelIndex iTriData; - - QString skinDataName; - QString skinInstName; - - int numVerts = 0; - int numTris = 0; + bool isDynamic = false; - BoundSphere dataBound; + void updateImpl( const NifModel * nif, const QModelIndex & index ) override; + void updateData( const NifModel * nif ) override; }; #endif // BSSHAPE_H diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index ae5035ea2..9240b36ae 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -85,9 +85,8 @@ bool ControllerManager::update( const NifModel * nif, const QModelIndex & index void ControllerManager::setSequence( const QString & seqname ) { - const NifModel * nif = static_cast(iBlock.model()); - - if ( target && iBlock.isValid() && nif ) { + auto nif = NifModel::fromValidIndex(iBlock); + if ( nif && target ) { MultiTargetTransformController * multiTargetTransformer = 0; for ( Controller * c : target->controllers ) { if ( c->typeId() == "NiMultiTargetTransformController" ) { @@ -260,9 +259,8 @@ void TransformController::updateTime( float time ) void TransformController::setInterpolator( const QModelIndex & idx ) { - const NifModel * nif = static_cast(idx.model()); - - if ( nif && idx.isValid() ) { + auto nif = NifModel::fromValidIndex(idx); + if ( nif ) { if ( interpolator ) { delete interpolator; interpolator = 0; @@ -333,9 +331,8 @@ bool MultiTargetTransformController::update( const NifModel * nif, const QModelI bool MultiTargetTransformController::setInterpolatorNode( Node * node, const QModelIndex & idx ) { - const NifModel * nif = static_cast(idx.model()); - - if ( !nif || !idx.isValid() ) + auto nif = NifModel::fromValidIndex(idx); + if ( !nif ) return false; QMutableListIterator it( extraTargets ); @@ -441,7 +438,7 @@ void MorphController::updateTime( float time ) } } - target->updateBounds = true; + target->needUpdateBounds = true; } bool MorphController::update( const NifModel * nif, const QModelIndex & index ) @@ -500,7 +497,7 @@ UVController::~UVController() void UVController::updateTime( float time ) { - const NifModel * nif = static_cast(iData.model()); + auto nif = NifModel::fromIndex( iData ); QModelIndex uvGroups = nif->getIndex( iData, "UV Groups" ); // U trans, V trans, U scale, V scale @@ -526,7 +523,7 @@ void UVController::updateTime( float time ) } } - target->updateData = true; + target->needUpdateData = true; // TODO (Gavrant): it's probably wrong (because the target shape would reset its UV map then) } bool UVController::update( const NifModel * nif, const QModelIndex & index ) @@ -812,7 +809,7 @@ void AlphaController::updateTime( float time ) float threshold; if ( interpolate( threshold, iData, "Data", ctrlTime( time ), lAlpha ) ) - alphaProp->setThreshold( threshold / 255.0 ); + alphaProp->alphaThreshold = threshold / 255.0f; } } @@ -880,9 +877,8 @@ TexFlipController::TexFlipController( TextureProperty * prop, const QModelIndex void TexFlipController::updateTime( float time ) { - const NifModel * nif = static_cast(iSources.model()); - - if ( !((target || oldTarget) && active && iSources.isValid() && nif) ) + auto nif = NifModel::fromValidIndex(iSources); + if ( !((target || oldTarget) && active && nif) ) return; float r = 0; @@ -990,7 +986,7 @@ void EffectFloatController::updateTime( float time ) if ( interpolate( val, iData, "Data", ctrlTime( time ), lIdx ) ) { switch ( variable ) { case EffectFloat::Emissive_Multiple: - target->setEmissive( target->getEmissiveColor(), val ); + target->emissiveMult = val; break; case EffectFloat::Falloff_Start_Angle: target->falloff.startAngle = val; @@ -1005,24 +1001,19 @@ void EffectFloatController::updateTime( float time ) target->falloff.stopOpacity = val; break; case EffectFloat::Alpha: - { - auto c = target->getEmissiveColor(); - auto m = target->getEmissiveMult(); - - target->setEmissive( Color4( c.red(), c.green(), c.blue(), val ), m ); - } + target->emissiveColor.setAlpha( val ); break; case EffectFloat::U_Offset: - target->setUvOffset( val, target->getUvOffset().y ); + target->uvOffset.x = val; break; case EffectFloat::U_Scale: - target->setUvScale( val, target->getUvScale().y ); + target->uvScale.x = val; break; case EffectFloat::V_Offset: - target->setUvOffset( target->getUvOffset().x, val ); + target->uvOffset.y = val; break; case EffectFloat::V_Scale: - target->setUvScale( target->getUvScale().x, val ); + target->uvScale.y = val; break; default: break; @@ -1058,13 +1049,8 @@ void EffectColorController::updateTime( float time ) if ( interpolate( val, iData, "Data", ctrlTime( time ), lIdx ) ) { switch ( variable ) { case 0: - { - auto c = target->getEmissiveColor(); - auto m = target->getEmissiveMult(); - - target->setEmissive( Color4( val[0], val[1], val[2], c.alpha() ), m ); + target->emissiveColor = Color4( val[0], val[1], val[2], target->emissiveColor.alpha() ); break; - } default: break; } @@ -1101,31 +1087,31 @@ void LightingFloatController::updateTime( float time ) case LightingFloat::Refraction_Strength: break; case LightingFloat::Reflection_Strength: - target->setEnvironmentReflection( val ); + target->environmentReflection = val; break; case LightingFloat::Glossiness: - target->setSpecular( target->getSpecularColor(), val, target->getSpecularStrength() ); + target->specularGloss = val; break; case LightingFloat::Specular_Strength: - target->setSpecular( target->getSpecularColor(), target->getSpecularGloss(), val ); + target->specularStrength = val; break; case LightingFloat::Emissive_Multiple: - target->setEmissive( target->getEmissiveColor(), val ); + target->emissiveMult = val; break; case LightingFloat::Alpha: - target->setAlpha( val ); + target->alpha = val; break; case LightingFloat::U_Offset: - target->setUvOffset( val, target->getUvOffset().y ); + target->uvOffset.x = val; break; case LightingFloat::U_Scale: - target->setUvScale( val, target->getUvScale().y ); + target->uvScale.x = val; break; case LightingFloat::V_Offset: - target->setUvOffset( target->getUvOffset().x, val ); + target->uvOffset.y = val; break; case LightingFloat::V_Scale: - target->setUvScale( target->getUvScale().x, val ); + target->uvScale.y = val; break; default: break; @@ -1161,10 +1147,10 @@ void LightingColorController::updateTime( float time ) if ( interpolate( val, iData, "Data", ctrlTime( time ), lIdx ) ) { switch ( variable ) { case 0: - target->setSpecular( { val[0], val[1], val[2] }, target->getSpecularGloss(), target->getSpecularStrength() ); + target->specularColor = { val[0], val[1], val[2] }; break; case 1: - target->setEmissive( { val[0], val[1], val[2] }, target->getEmissiveMult() ); + target->emissiveColor = { val[0], val[1], val[2] }; break; default: break; diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index a06bb43a5..28933570e 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -99,24 +99,25 @@ Controller * IControllable::findController( const QModelIndex & index ) return nullptr; } - -void IControllable::update( const NifModel * nif, const QModelIndex & i ) +void IControllable::update( const NifModel * nif, const QModelIndex & index ) { - if ( !iBlock.isValid() ) { + if ( !iBlock.isValid() ) clear(); - return; + else { + updateImpl(nif, index); } +} - bool x = false; - +void IControllable::updateImpl(const NifModel * nif, const QModelIndex & index) +{ + bool doUpdate = ( iBlock == index ); for ( Controller * ctrl : controllers ) { - ctrl->update( nif, i ); - - if ( ctrl->index() == i ) - x = true; + ctrl->update( nif, index ); + if ( !doUpdate && ctrl->index() == index ) + doUpdate = true; } - if ( iBlock == i || x ) { + if ( doUpdate ) { name = nif->get( iBlock, "Name" ); // sync the list of attached controllers QList rem( controllers ); @@ -177,6 +178,12 @@ void IControllable::setSequence( const QString & seqname ) } } +void IControllable::registerController( const NifModel* nif, Controller* ctrl ) +{ + ctrl->update(nif, ctrl->index()); + controllers.append(ctrl); +} + /* * Controller @@ -203,15 +210,14 @@ void Controller::setInterpolator( const QModelIndex & index ) { iInterpolator = index; - const NifModel * nif = static_cast( index.model() ); - + auto nif = NifModel::fromIndex( index ); if ( nif ) iData = nif->getBlock( nif->getLink( iInterpolator, "Data" ) ); } bool Controller::update( const NifModel * nif, const QModelIndex & index ) { - if ( iBlock.isValid() && iBlock == index ) { + if ( index == iBlock && iBlock.isValid() ) { start = nif->get( index, "Start Time" ); stop = nif->get( index, "Stop Time" ); phase = nif->get( index, "Phase" ); @@ -237,10 +243,10 @@ bool Controller::update( const NifModel * nif, const QModelIndex & index ) } } - if ( iInterpolator.isValid() && ( iInterpolator == index ) ) + if ( index == iInterpolator && iInterpolator.isValid() ) iData = nif->getBlock( nif->getLink( iInterpolator, "Data" ) ); - return ( index.isValid() && ( ( index == iBlock ) || ( index == iInterpolator ) || ( index == iData ) ) ); + return ( index.isValid() && (index == iBlock || index == iInterpolator || index == iData) ); } float Controller::ctrlTime( float time ) const @@ -367,9 +373,8 @@ bool Controller::timeIndex( float time, const NifModel * nif, const QModelIndex template bool interpolate( T & value, const QModelIndex & array, float time, int & last ) { - const NifModel * nif = static_cast( array.model() ); - - if ( nif && array.isValid() ) { + auto nif = NifModel::fromValidIndex(array); + if ( nif ) { QModelIndex frames = nif->getIndex( array, "Keys" ); int next; float x; @@ -446,9 +451,9 @@ template <> bool Controller::interpolate( bool & value, const QModelIndex & arra { int next; float x; - const NifModel * nif = static_cast( array.model() ); - if ( nif && array.isValid() ) { + auto nif = NifModel::fromValidIndex(array); + if ( nif ) { QModelIndex frames = nif->getIndex( array, "Keys" ); if ( timeIndex( time, nif, frames, last, next, x ) ) { @@ -465,9 +470,9 @@ template <> bool Controller::interpolate( Matrix & value, const QModelIndex & ar { int next; float x; - const NifModel * nif = static_cast( array.model() ); - - if ( nif && array.isValid() ) { + + auto nif = NifModel::fromValidIndex(array); + if ( nif ) { switch ( nif->get( array, "Rotation Type" ) ) { case 4: { @@ -540,7 +545,7 @@ struct qarray qarray( const QModelIndex & array, uint off = 0 ) : array_( array ), off_( off ) { - nif_ = static_cast( array_.model() ); + nif_ = NifModel::fromIndex( array_ ); } qarray( const qarray & other, uint off = 0 ) : nif_( other.nif_ ), array_( other.array_ ), off_( other.off_ + off ) diff --git a/src/gl/glcontroller.h b/src/gl/glcontroller.h index 41ce73479..42be7816d 100644 --- a/src/gl/glcontroller.h +++ b/src/gl/glcontroller.h @@ -126,9 +126,8 @@ class Controller template bool Controller::interpolate( T & value, const QModelIndex & data, const QString & arrayid, float time, int & lastindex ) { - const NifModel * nif = static_cast( data.model() ); - - if ( nif && data.isValid() ) { + auto nif = NifModel::fromValidIndex(data); + if ( nif ) { QModelIndex array = nif->getIndex( data, arrayid ); return interpolate( value, array, time, lastindex ); } diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 4f6f7b47f..c9560528a 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -36,6 +36,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gl/controllers.h" #include "gl/glscene.h" #include "gl/renderer.h" +#include "io/material.h" #include "io/nifstream.h" #include "model/nifmodel.h" @@ -56,127 +57,97 @@ Shape::Shape( Scene * s, const QModelIndex & b ) : Node( s, b ) shapeNumber = s->shapes.count(); } -Mesh::Mesh( Scene * s, const QModelIndex & b ) : Shape( s, b ) -{ -} - - -void Mesh::clear() +void Shape::clear() { Node::clear(); - iData = iSkin = iSkinData = iSkinPart = iTangentData = QModelIndex(); - bssp = nullptr; - bslsp = nullptr; - bsesp = nullptr; + resetSkinning(); + resetVertexData(); + resetSkeletonData(); - verts.clear(); - norms.clear(); - colors.clear(); - coords.clear(); - tangents.clear(); - bitangents.clear(); - triangles.clear(); - tristrips.clear(); - weights.clear(); - partitions.clear(); - sortedTriangles.clear(); - indices.clear(); transVerts.clear(); transNorms.clear(); transColors.clear(); transTangents.clear(); transBitangents.clear(); + bssp = nullptr; + bslsp = nullptr; + bsesp = nullptr; + alphaProperty = nullptr; + isLOD = false; isDoubleSided = false; } +void Shape::transform() +{ + if ( needUpdateData ) { + needUpdateData = false; + + auto nif = NifModel::fromValidIndex( iBlock ); + if ( nif ) { + needUpdateBounds = true; // Force update bounds + updateData(nif); + + if ( isVertexAlphaAnimation ) { + int nColors = colors.count(); + for ( int i = 0; i < nColors; i++ ) + colors[i].setRGBA( colors[i].red(), colors[i].green(), colors[i].blue(), 1 ); + } + } else { + clear(); + return; + } + } + + Node::transform(); +} + void Shape::setController( const NifModel * nif, const QModelIndex & iController ) { QString contrName = nif->itemName(iController); if ( contrName == "NiGeomMorpherController" ) { Controller * ctrl = new MorphController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } else if ( contrName == "NiUVController" ) { Controller * ctrl = new UVController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } else { Node::setController( nif, iController ); } } - -void Shape::update( const NifModel * nif, const QModelIndex & index ) +void Shape::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Node::update( nif, index ); - - // If shaders are reenabled, reset - if ( !scene->hasOption(Scene::DisableShaders) && shader.isNull() && nif->checkVersion( 0x14020007, 0x14020007 ) ) { - updateShaderProperties( nif ); - } - - if ( bssp && nifVersion == 155 ) { - depthTest = bssp->getDepthTest(); - depthWrite = bssp->getDepthWrite(); - isDoubleSided = bssp->getIsDoubleSided(); - } -} - -void Shape::updateShaderProperties( const NifModel * nif ) -{ - auto prop = nif->getLink( iBlock, "Shader Property" ); - if ( prop == -1 ) - return; - - QModelIndex iLSP = nif->getBlock( prop, "BSLightingShaderProperty" ); - QModelIndex iESP = nif->getBlock( prop, "BSEffectShaderProperty" ); - - // Reset stored shader so it can reassess conditions - shader = ""; - - if ( iLSP.isValid() ) { - if ( !bslsp ) - bslsp = properties.get(); - - if ( !bslsp ) - return; - - bssp = bslsp; - - bslsp->updateParams( nif, iLSP ); - - // Mesh alpha override - translucent = (bslsp->getAlpha() < 1.0); - translucent |= bslsp->hasRefraction; - - } else if ( iESP.isValid() ) { - if ( !bsesp ) - bsesp = properties.get(); + Node::updateImpl( nif, index ); + + if ( index == iBlock ) { + shader = ""; // Reset stored shader so it can reassess conditions + + bslsp = nullptr; + bsesp = nullptr; + bssp = properties.get(); + if ( bssp ) { + auto shaderType = bssp->typeId(); + if ( shaderType == "BSLightingShaderProperty" ) + bslsp = bssp->cast(); + else if ( shaderType == "BSEffectShaderProperty" ) + bsesp = bssp->cast(); + } - if ( !bsesp ) - return; + alphaProperty = properties.get(); - bssp = bsesp; + needUpdateData = true; + updateShader(); - bsesp->updateParams( nif, iESP ); + } else if ( isSkinned && (index == iSkin || index == iSkinData || index == iSkinPart) ) { + needUpdateData = true; - // Mesh alpha override - translucent = (bsesp->getAlpha() < 1.0) && !findProperty(); + } else if ( (bssp && bssp->isParamBlock(index)) || (alphaProperty && index == alphaProperty->index()) ) { + updateShader(); + } - - // Draw mesh second - drawSecond |= translucent; - - if ( !bssp ) - return; - - depthTest = bssp->getDepthTest(); - depthWrite = bssp->getDepthWrite(); - isDoubleSided = bssp->getIsDoubleSided(); - isVertexAlphaAnimation = bssp->getFlags2() & ShaderFlags::SLSF2_Tree_Anim; } void Shape::boneSphere( const NifModel * nif, const QModelIndex & index ) const @@ -198,640 +169,617 @@ void Shape::boneSphere( const NifModel * nif, const QModelIndex & index ) const } } -void Mesh::update( const NifModel * nif, const QModelIndex & index ) +void Shape::resetSkinning() { - Shape::update( nif, index ); - - // Was Skinning toggled? - // If so, switch between partition triangles and data triangles - bool doSkinningCurr = scene->hasOption(Scene::DoSkinning); - updateSkin |= (doSkinning != doSkinningCurr) && nif->checkVersion( 0, 0x14040000 ); - updateData |= updateSkin; - doSkinning = doSkinningCurr; - - if ( (!iBlock.isValid() || !index.isValid()) && !updateSkin ) - return; - - isLOD = nif->isNiBlock( iBlock, "BSLODTriShape" ); - if ( isLOD ) - emit nif->lodSliderChanged( true ); - - updateData |= ( iData == index ) || ( iTangentData == index ); - updateSkin |= ( iSkin == index ); - updateSkin |= ( iSkinData == index ); - updateSkin |= ( iSkinPart == index ); - updateSkin |= ( updateData && isSkinned && doSkinning ); - - if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) - updateSkin = false; - - isVertexAlphaAnimation = false; - isDoubleSided = false; - - // Update shader from this mesh's shader property - if ( nif->checkVersion( 0x14020007, 0 ) && nif->inherits( iBlock, "NiTriBasedGeom" ) ) - updateShaderProperties( nif ); - - if ( iBlock == index ) { - - if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { - qDebug() << nif->get( iBlock, "Num Submeshes" ) << " submeshes"; - iData = nif->getIndex( iBlock, "Datastreams" ); - if ( iData.isValid() ) { - qDebug() << "Got " << nif->rowCount( iData ) << " rows of data"; - updateData = true; - updateBounds = true; - } else { - qDebug() << "Did not find data in NiMesh ???"; - } - - return; - } - - for ( const auto link : nif->getChildLinks( id() ) ) { - QModelIndex iChild = nif->getBlock( link ); - if ( !iChild.isValid() ) - continue; - - if ( nif->inherits( iChild, "NiTriShapeData" ) || nif->inherits( iChild, "NiTriStripsData" ) ) { - if ( !iData.isValid() ) { - iData = iChild; - updateData = true; - } else if ( iData != iChild ) { - Message::append( tr( "Warnings were generated while updating meshes." ), - tr( "Block %1 has multiple data blocks" ).arg( id() ) - ); - } - } else if ( nif->inherits( iChild, "NiSkinInstance" ) ) { - if ( !iSkin.isValid() ) { - iSkin = iChild; - updateSkin = true; - } else if ( iSkin != iChild ) { - Message::append( tr( "Warnings were generated while updating meshes." ), - tr( "Block %1 has multiple skin instances" ).arg( id() ) - ); - } - } - } - } - - updateBounds |= updateData; + isSkinned = false; + iSkin = iSkinData = iSkinPart = QModelIndex(); } -QModelIndex Mesh::vertexAt( int idx ) const +void Shape::resetVertexData() { - auto nif = static_cast(iBlock.model()); - if ( !nif ) - return QModelIndex(); + numVerts = 0; - auto iVertexData = nif->getIndex( iData, "Vertices" ); - auto iVertex = iVertexData.child( idx, 0 ); + iData = iTangentData = QModelIndex(); - return iVertex; + verts.clear(); + norms.clear(); + colors.clear(); + coords.clear(); + tangents.clear(); + bitangents.clear(); + triangles.clear(); + tristrips.clear(); + weights.clear(); + partitions.clear(); + sortedTriangles.clear(); } - -bool Mesh::isHidden() const +void Shape::resetSkeletonData() { - return ( Node::isHidden() - || ( !scene->hasOption(Scene::ShowHidden) - && !properties.get() - && !properties.get() - ) - ); -} + skeletonRoot = 0; + skeletonTrans = Transform(); -bool compareTriangles( const QPair & tri1, const QPair & tri2 ) -{ - return ( tri1.second < tri2.second ); + bones.clear(); + weights.clear(); + partitions.clear(); } -void Mesh::transform() +void Shape::updateShader() { - const NifModel * nif = static_cast( iBlock.model() ); - - if ( !nif || !iBlock.isValid() ) { - clear(); - return; + if ( bslsp ) + translucent = (bslsp->alpha < 1.0) || bslsp->hasRefraction; + else if ( bsesp ) + translucent = (bsesp->getAlpha() < 1.0) && !alphaProperty; + else + translucent = false; + + Material * mat = (bssp) ? bssp->getMaterial() : nullptr; + drawInSecondPass = translucent || ( alphaProperty && alphaProperty->blend() ) || ( mat && mat->hasDecal() ); + + if ( bssp ) { + depthTest = bssp->depthTest; + depthWrite = bssp->depthWrite; + isDoubleSided = bssp->isDoubleSided; + isVertexAlphaAnimation = bssp->isVertexAlphaAnimation; + } else { + depthTest = true; + depthWrite = true; + isDoubleSided = false; + isVertexAlphaAnimation = false; } +} - if ( updateData ) { - nifVersion = nif->getUserVersion2(); - updateData = false; - - - // NiMesh Rendering - if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { - verts.clear(); - norms.clear(); - tangents.clear(); - bitangents.clear(); - coords.clear(); - colors.clear(); - indices.clear(); - weights.clear(); - triangles.clear(); - - - - // All the semantics used by this mesh - NiMesh::SemanticFlags semFlags = NiMesh::HAS_NONE; - // Loop over the data once for initial setup and validation - // and build the semantic-index maps for each datastream's components - using CompSemIdxMap = QVector>; - QVector compSemanticIndexMaps; - for ( int i = 0; i < nif->rowCount( iData ); i++ ) { - auto stream = nif->getLink( iData.child( i, 0 ), "Stream" ); - auto iDataStream = nif->getBlock( stream ); - - auto usage = NiMesh::DataStreamUsage( nif->get( iDataStream, "Usage" ) ); - auto access = nif->get( iDataStream, "Access" ); - - // Invalid Usage and Access, abort - if ( usage == access && access == 0 ) - return; - - // For each datastream, store the semantic and the index (used for E_TEXCOORD) - auto iComponentSemantics = nif->getIndex( iData.child( i, 0 ), "Component Semantics" ); - uint numComponents = nif->get( iData.child( i, 0 ), "Num Components" ); - CompSemIdxMap compSemanticIndexMap; - for ( uint j = 0; j < numComponents; j++ ) { - auto name = nif->get( iComponentSemantics.child( j, 0 ), "Name" ); - auto sem = NiMesh::semanticStrings.value( name ); - uint idx = nif->get( iComponentSemantics.child( j, 0 ), "Index" ); - compSemanticIndexMap.insert( j, {sem, idx} ); - - // Create UV stubs for multi-coord systems - if ( sem == NiMesh::E_TEXCOORD ) - coords.append( TexCoords() ); - - // Assure Index datastream is first and Usage is correct - bool invalidIndex = false; - if ( (sem == NiMesh::E_INDEX && (i != 0 || usage != NiMesh::USAGE_VERTEX_INDEX)) - || (usage == NiMesh::USAGE_VERTEX_INDEX && (i != 0 || sem != NiMesh::E_INDEX)) ) - invalidIndex = true; - - if ( invalidIndex ) { - Message::append( tr( NIMESH_ABORT ), - tr( "[%1] NifSkope requires 'INDEX' datastream be first, with Usage type 'USAGE_VERTEX_INDEX'." ) - .arg( stream ), - QMessageBox::Warning ); - return; - } - - semFlags = NiMesh::SemanticFlags(semFlags | (1 << sem)); - } +void Mesh::updateImpl( const NifModel * nif, const QModelIndex & index ) +{ + Shape::updateImpl(nif, index); - compSemanticIndexMaps << compSemanticIndexMap; - } + if ( index == iBlock ) { + isLOD = nif->isNiBlock( iBlock, "BSLODTriShape" ); + if ( isLOD ) + emit nif->lodSliderChanged( true ); - // This NiMesh does not have vertices, abort - if ( !(semFlags & NiMesh::HAS_POSITION || semFlags & NiMesh::HAS_POSITION_BP) ) - return; + } else if ( index == iData || index == iTangentData ) { + needUpdateData = true; - // The number of triangle indices across the submeshes for this NiMesh - quint32 totalIndices = 0; - // The highest triangle index value in this NiMesh - quint32 maxIndex = 0; - // 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 - // filled in order for each data stream. - // Submeshes may be required if total index values exceed USHRT_MAX - QMap submeshMap; - ushort numSubmeshes = nif->get( iData.child( i, 0 ), "Num Submeshes" ); - auto iSubmeshMap = nif->getIndex( iData.child( i, 0 ), "Submesh To Region Map" ); - for ( ushort j = 0; j < numSubmeshes; j++ ) - submeshMap.insert( j, nif->get( iSubmeshMap.child( j, 0 ) ) ); - - // Get the datastream - quint32 stream = nif->getLink( iData.child( i, 0 ), "Stream" ); - auto iDataStream = nif->getBlock( stream ); - - auto usage = NiMesh::DataStreamUsage(nif->get( iDataStream, "Usage" )); - // Only process USAGE_VERTEX and USAGE_VERTEX_INDEX - if ( usage > NiMesh::USAGE_VERTEX ) - continue; - - // Datastream can be split into multiple regions - // Each region has a Start Index which is added as an offset to the index read from the stream - QVector> regions; - quint32 numRegions = nif->get( iDataStream, "Num Regions" ); - quint32 numIndices = 0; - auto iRegions = nif->getIndex( iDataStream, "Regions" ); - if ( iRegions.isValid() ) { - for ( quint32 j = 0; j < numRegions; j++ ) { - regions.append( { nif->get( iRegions.child( j, 0 ), "Start Index" ), - nif->get( iRegions.child( j, 0 ), "Num Indices" ) } - ); - - numIndices += regions[j].second; - } - } + } +} - if ( usage == NiMesh::USAGE_VERTEX_INDEX ) { - totalIndices = numIndices; - // RESERVE not RESIZE - indices.reserve( totalIndices ); - } else if ( compIdx == 1 ) { - // Indices should be built already - if ( indices.size() != totalIndices ) - return; - - quint32 maxSize = maxIndex + 1; - // RESIZE - verts.resize( maxSize ); - norms.resize( maxSize ); - tangents.resize( maxSize ); - bitangents.resize( maxSize ); - colors.resize( maxSize ); - weights.resize( maxSize ); - if ( coords.size() == 0 ) - coords.resize( 1 ); - - for ( auto & c : coords ) - c.resize( maxSize ); - } +void Mesh::updateData( const NifModel * nif ) +{ + resetSkinning(); + resetVertexData(); + if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) + updateData_NiMesh( nif ); + else + updateData_NiTriShape( nif ); - // Get the format of each component - QVector datastreamFormats; - uint numStreamComponents = nif->get( iDataStream, "Num Components" ); - for ( uint j = 0; j < numStreamComponents; j++ ) { - auto format = nif->get( nif->getIndex( iDataStream, "Component Formats" ).child( j, 0 ) ); - datastreamFormats.append( NiMesh::DataStreamFormat(format) ); - } + // Fill skinning and skeleton data + resetSkeletonData(); + if ( iSkin.isValid() ) { + isSkinned = true; - Q_ASSERT( compSemanticIndexMaps[i].size() == numStreamComponents ); - - auto tempMdl = std::make_unique( this ); - - QByteArray streamData = nif->get( nif->getIndex( iDataStream, "Data" ).child( 0, 0 ) ); - QBuffer streamBuffer( &streamData ); - streamBuffer.open( QIODevice::ReadOnly ); - - NifIStream tempInput( tempMdl.get(), &streamBuffer ); - NifValue tempValue; - - bool abort = false; - for ( const auto & r : regions ) for ( uint j = 0; j < r.second; j++ ) { - auto off = r.first; - Q_ASSERT( totalIndices >= off + j ); - for ( uint k = 0; k < numStreamComponents; k++ ) { - auto typeK = datastreamFormats[k]; - int typeLength = ( (typeK & 0x000F0000) >> 0x10 ); - - switch ( (typeK & 0x00000FF0) >> 0x04 ) { - case 0x10: - tempValue.changeType( NifValue::tByte ); - break; - case 0x11: - if ( typeK == NiMesh::F_NORMUINT8_4 ) - tempValue.changeType( NifValue::tByteColor4 ); - typeLength = 1; - break; - case 0x13: - if ( typeK == NiMesh::F_NORMUINT8_4_BGRA ) - tempValue.changeType( NifValue::tByteColor4 ); - typeLength = 1; - break; - case 0x21: - tempValue.changeType( NifValue::tShort ); - break; - case 0x23: - if ( typeLength == 3 ) - tempValue.changeType( NifValue::tHalfVector3 ); - else if ( typeLength == 2 ) - tempValue.changeType( NifValue::tHalfVector2 ); - else if ( typeLength == 1 ) - tempValue.changeType( NifValue::tHfloat ); - - typeLength = 1; - - break; - case 0x42: - tempValue.changeType( NifValue::tInt ); - break; - case 0x43: - if ( typeLength == 3 ) - tempValue.changeType( NifValue::tVector3 ); - else if ( typeLength == 2 ) - tempValue.changeType( NifValue::tVector2 ); - else if ( typeLength == 4 ) - tempValue.changeType( NifValue::tVector4 ); - else if ( typeLength == 1 ) - tempValue.changeType( NifValue::tFloat ); - - typeLength = 1; - - break; - } + iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), "NiSkinData" ); - for ( int l = 0; l < typeLength; l++ ) { - tempInput.read( tempValue ); - } + iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); + if ( !iSkinPart.isValid() && iSkinData.isValid() ) { + // nif versions < 10.2.0.0 have skin partition linked in the skin data block + iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); + } - auto compType = compSemanticIndexMaps[i].value( k ).first; - switch ( typeK ) - { - case NiMesh::F_FLOAT32_3: - case NiMesh::F_FLOAT16_3: - Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); - switch ( compType ) { - case NiMesh::E_POSITION: - case NiMesh::E_POSITION_BP: - verts[j + off] = tempValue.get(); - break; - case NiMesh::E_NORMAL: - case NiMesh::E_NORMAL_BP: - norms[j + off] = tempValue.get(); - break; - case NiMesh::E_TANGENT: - case NiMesh::E_TANGENT_BP: - tangents[j + off] = tempValue.get(); - break; - case NiMesh::E_BINORMAL: - case NiMesh::E_BINORMAL_BP: - bitangents[j + off] = tempValue.get(); - break; - default: - break; - } - break; - case NiMesh::F_UINT16_1: - if ( compType == NiMesh::E_INDEX ) { - Q_ASSERT( usage == NiMesh::USAGE_VERTEX_INDEX ); - // TODO: The total index value across all submeshes - // is likely allowed to exceed USHRT_MAX. - // For now limit the index. - quint32 ind = tempValue.get() + off; - if ( ind > 0xFFFF ) - qDebug() << QString( "[%1] %2" ).arg( stream ).arg( ind ); - - ind = std::min( ind, (quint32)0xFFFF ); - - // Store the highest index - if ( ind > maxIndex ) - maxIndex = ind; - - indices.append( (quint16)ind ); - } - break; - case NiMesh::F_FLOAT32_2: - case NiMesh::F_FLOAT16_2: - Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); - if ( compType == NiMesh::E_TEXCOORD ) { - quint32 coordSet = compSemanticIndexMaps[i].value( k ).second; - Q_ASSERT( coords.size() > coordSet ); - coords[coordSet][j + off] = tempValue.get(); - } - break; - case NiMesh::F_UINT8_4: - // BLENDINDICES, do nothing for now - break; - case NiMesh::F_NORMUINT8_4: - Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); - if ( compType == NiMesh::E_COLOR ) - colors[j + off] = tempValue.get(); - break; - case NiMesh::F_NORMUINT8_4_BGRA: - Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); - if ( compType == NiMesh::E_COLOR ) { - // Swizzle BGRA -> RGBA - auto c = tempValue.get().data(); - colors[j + off] = {c[2], c[1], c[0], c[3]}; - } - break; - default: - Message::append( tr( NIMESH_ABORT ), tr( "[%1] Unsupported Component: %2" ).arg( stream ) - .arg( NifValue::enumOptionName( "ComponentFormat", typeK ) ), - QMessageBox::Warning ); - abort = true; - break; - } + skeletonRoot = nif->getLink( iSkin, "Skeleton Root" ); + skeletonTrans = Transform( nif, iSkinData ); - if ( abort == true ) - break; - } - } + bones = nif->getLinkArray( iSkin, "Bones" ); - // Clear is extremely expensive. Must be outside of loop - tempMdl->clear(); + QModelIndex idxBones = nif->getIndex( iSkinData, "Bone List" ); + if ( idxBones.isValid() ) { + int nTotalBones = bones.count(); + int nBoneList = nif->rowCount( idxBones ); + // Ignore weights listed in NiSkinData if NiSkinPartition exists + int vcnt = ( nif->get( iSkinData, "Has Vertex Weights" ) && !iSkinPart.isValid() ) ? numVerts : 0; + for ( int b = 0; b < nBoneList && b < nTotalBones; b++ ) + weights.append( BoneWeights( nif, idxBones.child( b, 0 ), bones[b], vcnt ) ); + } - compIdx++; - } + if ( iSkinPart.isValid() ) { + QModelIndex idx = nif->getIndex( iSkinPart, "Partitions" ); - // Clear unused vertex attributes - // Note: Do not clear normals as this breaks fixed function for some reason - if ( !(semFlags & NiMesh::HAS_BINORMAL) ) - bitangents.clear(); - if ( !(semFlags & NiMesh::HAS_TANGENT) ) - tangents.clear(); - if ( !(semFlags & NiMesh::HAS_COLOR) ) - colors.clear(); - if ( !(semFlags & NiMesh::HAS_BLENDINDICES) || !(semFlags & NiMesh::HAS_BLENDWEIGHT) ) - weights.clear(); - - Q_ASSERT( verts.size() == maxIndex + 1 ); - Q_ASSERT( indices.size() == totalIndices ); - - // Make geometry - triangles.resize( indices.size() / 3 ); - auto meshPrimitiveType = nif->get( iBlock, "Primitive Type" ); - switch ( meshPrimitiveType ) { - case NiMesh::PRIMITIVE_TRIANGLES: - for ( int k = 0, t = 0; k < indices.size(); k += 3, t++ ) - triangles[t] = { indices[k], indices[k + 1], indices[k + 2] }; - break; - case NiMesh::PRIMITIVE_TRISTRIPS: - case NiMesh::PRIMITIVE_LINES: - case NiMesh::PRIMITIVE_LINESTRIPS: - case NiMesh::PRIMITIVE_QUADS: - case NiMesh::PRIMITIVE_POINTS: - Message::append( tr( NIMESH_ABORT ), tr( "[%1] Unsupported Primitive: %2" ) - .arg( nif->getBlockNumber( iBlock ) ) - .arg( NifValue::enumOptionName( "MeshPrimitiveType", meshPrimitiveType ) ), - QMessageBox::Warning - ); - break; + uint numTris = 0; + uint numStrips = 0; + for ( int i = 0; i < nif->rowCount( idx ) && idx.isValid(); i++ ) { + partitions.append( SkinPartition( nif, idx.child( i, 0 ) ) ); + numTris += partitions[i].triangles.size(); + numStrips += partitions[i].tristrips.size(); } - } else { - verts = nif->getArray( iData, "Vertices" ); - norms = nif->getArray( iData, "Normals" ); - colors = nif->getArray( iData, "Vertex Colors" ); + triangles.clear(); + tristrips.clear(); - // Detect if "Has Vertex Colors" is set to Yes in NiTriShape - // Used to compare against SLSF2_Vertex_Colors - hasVertexColors = true; - if ( colors.length() == 0 ) { - hasVertexColors = false; - } + triangles.reserve( numTris ); + tristrips.reserve( numStrips ); - if ( isVertexAlphaAnimation ) { - for ( int i = 0; i < colors.count(); i++ ) - colors[i].setRGBA( colors[i].red(), colors[i].green(), colors[i].blue(), 1 ); + for ( const SkinPartition& part : partitions ) { + triangles << part.getRemappedTriangles(); + tristrips << part.getRemappedTristrips(); } + } + } +} - tangents = nif->getArray( iData, "Tangents" ); - bitangents = nif->getArray( iData, "Bitangents" ); +void Mesh::updateData_NiMesh( const NifModel * nif ) +{ + iData = nif->getIndex( iBlock, "Datastreams" ); + if ( !iData.isValid() ) + return; + int nTotalStreams = nif->rowCount( iData ); + + // All the semantics used by this mesh + NiMesh::SemanticFlags semFlags = NiMesh::HAS_NONE; - if ( norms.count() < verts.count() ) - norms.clear(); + // Loop over the data once for initial setup and validation + // and build the semantic-index maps for each datastream's components + using CompSemIdxMap = QVector>; + QVector compSemanticIndexMaps; + for ( int i = 0; i < nTotalStreams; i++ ) { + auto iStreamEntry = iData.child( i, 0 ); - if ( colors.count() < verts.count() ) - colors.clear(); + auto stream = nif->getLink( iStreamEntry, "Stream" ); + auto iDataStream = nif->getBlock( stream ); - coords.clear(); - QModelIndex uvcoord = nif->getIndex( iData, "UV Sets" ); - if ( uvcoord.isValid() ) { - for ( int r = 0; r < nif->rowCount( uvcoord ); r++ ) { - TexCoords tc = nif->getArray( uvcoord.child( r, 0 ) ); + auto usage = NiMesh::DataStreamUsage( nif->get( iDataStream, "Usage" ) ); + auto access = nif->get( iDataStream, "Access" ); - if ( tc.count() < verts.count() ) - tc.clear(); + // Invalid Usage and Access, abort + if ( usage == access && access == 0 ) + return; - coords.append( tc ); - } + // For each datastream, store the semantic and the index (used for E_TEXCOORD) + auto iComponentSemantics = nif->getIndex( iStreamEntry, "Component Semantics" ); + uint numComponents = nif->get( iStreamEntry, "Num Components" ); + CompSemIdxMap compSemanticIndexMap; + for ( uint j = 0; j < numComponents; j++ ) { + auto iComponentEntry = iComponentSemantics.child( j, 0 ); + + auto name = nif->get( iComponentEntry, "Name" ); + auto sem = NiMesh::semanticStrings.value( name ); + uint idx = nif->get( iComponentEntry, "Index" ); + compSemanticIndexMap.insert( j, {sem, idx} ); + + // Create UV stubs for multi-coord systems + if ( sem == NiMesh::E_TEXCOORD ) + coords.append( TexCoords() ); + + // Assure Index datastream is first and Usage is correct + bool invalidIndex = false; + if ( (sem == NiMesh::E_INDEX && (i != 0 || usage != NiMesh::USAGE_VERTEX_INDEX)) + || (usage == NiMesh::USAGE_VERTEX_INDEX && (i != 0 || sem != NiMesh::E_INDEX)) ) + invalidIndex = true; + + if ( invalidIndex ) { + Message::append( tr( NIMESH_ABORT ), + tr( "[%1] NifSkope requires 'INDEX' datastream be first, with Usage type 'USAGE_VERTEX_INDEX'." ) + .arg( stream ), + QMessageBox::Warning ); + return; } - if ( nif->itemName( iData ) == "NiTriShapeData" ) { - // check indexes - // TODO: check other indexes as well - QVector ftriangles = nif->getArray( iData, "Triangles" ); - triangles.clear(); - int inv_idx = 0; - int inv_cnt = 0; - - for ( int i = 0; i < ftriangles.count(); i++ ) { - Triangle t = ftriangles[i]; - inv_idx = 0; - - for ( int j = 0; j < 3; j++ ) { - if ( t[j] >= verts.count() ) { - inv_idx = 1; - break; - } - } - - if ( !inv_idx ) - triangles.append( t ); - } - - inv_cnt = ftriangles.count() - triangles.count(); - ftriangles.clear(); + semFlags = NiMesh::SemanticFlags(semFlags | (1 << sem)); + } - if ( inv_cnt > 0 ) { - int block_idx = nif->getBlockNumber( nif->getIndex( iData, "Triangles" ) ); - Message::append( tr( "Warnings were generated while rendering mesh." ), - tr( "Block %1: %2 invalid indices in NiTriShapeData.Triangles" ).arg( block_idx ).arg( inv_cnt ) - ); - } + compSemanticIndexMaps << compSemanticIndexMap; + } - tristrips.clear(); - } else if ( nif->itemName( iData ) == "NiTriStripsData" ) { - tristrips.clear(); - QModelIndex points = nif->getIndex( iData, "Points" ); - - if ( points.isValid() ) { - for ( int r = 0; r < nif->rowCount( points ); r++ ) - tristrips.append( nif->getArray( points.child( r, 0 ) ) ); - } else { - Message::append( tr( "Warnings were generated while rendering mesh." ), - tr( "Block %1: Invalid 'Points' array in %2" ) - .arg( nif->getBlockNumber( iData ) ) - .arg( nif->itemName( iData ) ) - ); - } + // This NiMesh does not have vertices, abort + if ( !(semFlags & NiMesh::HAS_POSITION || semFlags & NiMesh::HAS_POSITION_BP) ) + return; - triangles.clear(); - } else if ( iSkinPart.isValid() && doSkinning ) { - triangles.clear(); - tristrips.clear(); + // The number of triangle indices across the submeshes for this NiMesh + quint32 totalIndices = 0; + QVector indices; + // The highest triangle index value in this NiMesh + quint32 maxIndex = 0; + // The Nth component after ignoring DataStreamUsage > 1 + int compIdx = 0; + for ( int i = 0; i < nTotalStreams; i++ ) { + // 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 + auto iStreamEntry = iData.child( i, 0 ); + + QMap submeshMap; + ushort numSubmeshes = nif->get( iStreamEntry, "Num Submeshes" ); + auto iSubmeshMap = nif->getIndex( iStreamEntry, "Submesh To Region Map" ); + for ( ushort j = 0; j < numSubmeshes; j++ ) + submeshMap.insert( j, nif->get( iSubmeshMap.child( j, 0 ) ) ); + + // Get the datastream + quint32 stream = nif->getLink( iStreamEntry, "Stream" ); + auto iDataStream = nif->getBlock( stream ); + + auto usage = NiMesh::DataStreamUsage(nif->get( iDataStream, "Usage" )); + // Only process USAGE_VERTEX and USAGE_VERTEX_INDEX + if ( usage > NiMesh::USAGE_VERTEX ) + continue; + + // Datastream can be split into multiple regions + // Each region has a Start Index which is added as an offset to the index read from the stream + QVector> regions; + quint32 numIndices = 0; + auto iRegions = nif->getIndex( iDataStream, "Regions" ); + if ( iRegions.isValid() ) { + quint32 numRegions = nif->get( iDataStream, "Num Regions" ); + for ( quint32 j = 0; j < numRegions; j++ ) { + auto iRegionEntry = iRegions.child( j, 0 ); + regions.append( { nif->get( iRegionEntry, "Start Index" ), nif->get( iRegionEntry, "Num Indices" ) } ); + + numIndices += regions[j].second; } + } - QModelIndex iExtraData = nif->getIndex( iBlock, "Extra Data List" ); + if ( usage == NiMesh::USAGE_VERTEX_INDEX ) { + totalIndices = numIndices; + // RESERVE not RESIZE + indices.reserve( totalIndices ); + } else if ( compIdx == 1 ) { + // Indices should be built already + if ( indices.size() != totalIndices ) + return; - if ( iExtraData.isValid() ) { - for ( int e = 0; e < nif->rowCount( iExtraData ); e++ ) { - QModelIndex iExtra = nif->getBlock( nif->getLink( iExtraData.child( e, 0 ) ), "NiBinaryExtraData" ); + quint32 maxSize = maxIndex + 1; + // RESIZE + verts.resize( maxSize ); + norms.resize( maxSize ); + tangents.resize( maxSize ); + bitangents.resize( maxSize ); + colors.resize( maxSize ); + weights.resize( maxSize ); + if ( coords.size() == 0 ) + coords.resize( 1 ); + + for ( auto & c : coords ) + c.resize( maxSize ); + } - if ( nif->get( iExtra, "Name" ) == "Tangent space (binormal & tangent vectors)" ) { - iTangentData = iExtra; - QByteArray data = nif->get( iExtra, "Binary Data" ); + // Get the format of each component + QVector datastreamFormats; + uint numStreamComponents = nif->get( iDataStream, "Num Components" ); + auto iComponentFormats = nif->getIndex( iDataStream, "Component Formats" ); + for ( uint j = 0; j < numStreamComponents; j++ ) { + auto format = nif->get( iComponentFormats.child( j, 0 ) ); + datastreamFormats.append( NiMesh::DataStreamFormat(format) ); + } - if ( data.count() == verts.count() * 4 * 3 * 2 ) { - tangents.resize( verts.count() ); - bitangents.resize( verts.count() ); - Vector3 * t = (Vector3 *)data.data(); + Q_ASSERT( compSemanticIndexMaps[i].size() == numStreamComponents ); + + auto tempMdl = std::make_unique( this ); + + QByteArray streamData = nif->get( nif->getIndex( iDataStream, "Data" ).child( 0, 0 ) ); + QBuffer streamBuffer( &streamData ); + streamBuffer.open( QIODevice::ReadOnly ); + + NifIStream tempInput( tempMdl.get(), &streamBuffer ); + NifValue tempValue; + + bool abort = false; + for ( const auto & r : regions ) for ( uint j = 0; j < r.second; j++ ) { + auto off = r.first; + Q_ASSERT( totalIndices >= off + j ); + for ( uint k = 0; k < numStreamComponents; k++ ) { + auto typeK = datastreamFormats[k]; + int typeLength = ( (typeK & 0x000F0000) >> 0x10 ); + + switch ( (typeK & 0x00000FF0) >> 0x04 ) { + case 0x10: + tempValue.changeType( NifValue::tByte ); + break; + case 0x11: + if ( typeK == NiMesh::F_NORMUINT8_4 ) + tempValue.changeType( NifValue::tByteColor4 ); + typeLength = 1; + break; + case 0x13: + if ( typeK == NiMesh::F_NORMUINT8_4_BGRA ) + tempValue.changeType( NifValue::tByteColor4 ); + typeLength = 1; + break; + case 0x21: + tempValue.changeType( NifValue::tShort ); + break; + case 0x23: + if ( typeLength == 3 ) + tempValue.changeType( NifValue::tHalfVector3 ); + else if ( typeLength == 2 ) + tempValue.changeType( NifValue::tHalfVector2 ); + else if ( typeLength == 1 ) + tempValue.changeType( NifValue::tHfloat ); + + typeLength = 1; + + break; + case 0x42: + tempValue.changeType( NifValue::tInt ); + break; + case 0x43: + if ( typeLength == 3 ) + tempValue.changeType( NifValue::tVector3 ); + else if ( typeLength == 2 ) + tempValue.changeType( NifValue::tVector2 ); + else if ( typeLength == 4 ) + tempValue.changeType( NifValue::tVector4 ); + else if ( typeLength == 1 ) + tempValue.changeType( NifValue::tFloat ); + + typeLength = 1; + + break; + } - for ( int c = 0; c < verts.count(); c++ ) - tangents[c] = *t++; + for ( int l = 0; l < typeLength; l++ ) { + tempInput.read( tempValue ); + } - for ( int c = 0; c < verts.count(); c++ ) - bitangents[c] = *t++; - } + auto compType = compSemanticIndexMaps[i].value( k ).first; + switch ( typeK ) + { + case NiMesh::F_FLOAT32_3: + case NiMesh::F_FLOAT16_3: + Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); + switch ( compType ) { + case NiMesh::E_POSITION: + case NiMesh::E_POSITION_BP: + verts[j + off] = tempValue.get(); + break; + case NiMesh::E_NORMAL: + case NiMesh::E_NORMAL_BP: + norms[j + off] = tempValue.get(); + break; + case NiMesh::E_TANGENT: + case NiMesh::E_TANGENT_BP: + tangents[j + off] = tempValue.get(); + break; + case NiMesh::E_BINORMAL: + case NiMesh::E_BINORMAL_BP: + bitangents[j + off] = tempValue.get(); + break; + default: + break; + } + break; + case NiMesh::F_UINT16_1: + if ( compType == NiMesh::E_INDEX ) { + Q_ASSERT( usage == NiMesh::USAGE_VERTEX_INDEX ); + // TODO: The total index value across all submeshes + // is likely allowed to exceed USHRT_MAX. + // For now limit the index. + quint32 ind = tempValue.get() + off; + if ( ind > 0xFFFF ) + qDebug() << QString( "[%1] %2" ).arg( stream ).arg( ind ); + + ind = std::min( ind, (quint32)0xFFFF ); + + // Store the highest index + if ( ind > maxIndex ) + maxIndex = ind; + + indices.append( (quint16)ind ); + } + break; + case NiMesh::F_FLOAT32_2: + case NiMesh::F_FLOAT16_2: + Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); + if ( compType == NiMesh::E_TEXCOORD ) { + quint32 coordSet = compSemanticIndexMaps[i].value( k ).second; + Q_ASSERT( coords.size() > coordSet ); + coords[coordSet][j + off] = tempValue.get(); } + break; + case NiMesh::F_UINT8_4: + // BLENDINDICES, do nothing for now + break; + case NiMesh::F_NORMUINT8_4: + Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); + if ( compType == NiMesh::E_COLOR ) + colors[j + off] = tempValue.get(); + break; + case NiMesh::F_NORMUINT8_4_BGRA: + Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); + if ( compType == NiMesh::E_COLOR ) { + // Swizzle BGRA -> RGBA + auto c = tempValue.get().data(); + colors[j + off] = {c[2], c[1], c[0], c[3]}; + } + break; + default: + Message::append( tr( NIMESH_ABORT ), tr( "[%1] Unsupported Component: %2" ).arg( stream ) + .arg( NifValue::enumOptionName( "ComponentFormat", typeK ) ), + QMessageBox::Warning ); + abort = true; + break; } + + if ( abort == true ) + break; } } - } - if ( updateSkin ) { - updateSkin = false; - isSkinned = false; - weights.clear(); - partitions.clear(); + // Clear is extremely expensive. Must be outside of loop + tempMdl->clear(); - iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), "NiSkinData" ); - iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); - if ( !iSkinPart.isValid() ) - // nif versions < 10.2.0.0 have skin partition linked in the skin data block - iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); + compIdx++; + } - skeletonRoot = nif->getLink( iSkin, "Skeleton Root" ); - skeletonTrans = Transform( nif, iSkinData ); + // Clear unused vertex attributes + // Note: Do not clear normals as this breaks fixed function for some reason + // TODO (Gavrant): figure out why clearing normals "breaks fixed function for some reason" + if ( !(semFlags & NiMesh::HAS_BINORMAL) ) + bitangents.clear(); + if ( !(semFlags & NiMesh::HAS_TANGENT) ) + tangents.clear(); + if ( !(semFlags & NiMesh::HAS_COLOR) ) + colors.clear(); + if ( !(semFlags & NiMesh::HAS_BLENDINDICES) || !(semFlags & NiMesh::HAS_BLENDWEIGHT) ) + weights.clear(); - bones = nif->getLinkArray( iSkin, "Bones" ); + Q_ASSERT( verts.size() == maxIndex + 1 ); + Q_ASSERT( indices.size() == totalIndices ); + + // Make geometry + triangles.resize( indices.size() / 3 ); + auto meshPrimitiveType = nif->get( iBlock, "Primitive Type" ); + switch ( meshPrimitiveType ) { + case NiMesh::PRIMITIVE_TRIANGLES: + for ( int k = 0, t = 0; k < indices.size(); k += 3, t++ ) + triangles[t] = { indices[k], indices[k + 1], indices[k + 2] }; + break; + case NiMesh::PRIMITIVE_TRISTRIPS: + case NiMesh::PRIMITIVE_LINES: + case NiMesh::PRIMITIVE_LINESTRIPS: + case NiMesh::PRIMITIVE_QUADS: + case NiMesh::PRIMITIVE_POINTS: + Message::append( tr( NIMESH_ABORT ), tr( "[%1] Unsupported Primitive: %2" ) + .arg( nif->getBlockNumber( iBlock ) ) + .arg( NifValue::enumOptionName( "MeshPrimitiveType", meshPrimitiveType ) ), + QMessageBox::Warning + ); + break; + } +} - QModelIndex idxBones = nif->getIndex( iSkinData, "Bone List" ); - if ( idxBones.isValid() ) { - bool hvw = nif->get( iSkinData, "Has Vertex Weights" ); - // Ignore weights listed in NiSkinData if NiSkinPartition exists - hvw = hvw && !iSkinPart.isValid(); - int vcnt = hvw ? verts.count() : 0; - for ( int b = 0; b < nif->rowCount( idxBones ) && b < bones.count(); b++ ) { - weights.append( BoneWeights( nif, idxBones.child( b, 0 ), bones[ b ], vcnt ) ); +void Mesh::updateData_NiTriShape( const NifModel * nif ) +{ + // Find iData and iSkin blocks among the children + for ( auto childLink : nif->getChildLinks( id() ) ) { + QModelIndex iChild = nif->getBlock( childLink ); + if ( !iChild.isValid() ) + continue; + + if ( nif->inherits( iChild, "NiTriShapeData" ) || nif->inherits( iChild, "NiTriStripsData" ) ) { + if ( !iData.isValid() ) { + iData = iChild; + } else if ( iData != iChild ) { + Message::append( tr( "Warnings were generated while updating meshes." ), + tr( "Block %1 has multiple data blocks" ).arg( id() ) + ); + } + } else if ( nif->inherits( iChild, "NiSkinInstance" ) ) { + if ( !iSkin.isValid() ) { + iSkin = iChild; + } else if ( iSkin != iChild ) { + Message::append( tr( "Warnings were generated while updating meshes." ), + tr( "Block %1 has multiple skin instances" ).arg( id() ) + ); } } + } + if ( !iData.isValid() ) + return; - if ( iSkinPart.isValid() && doSkinning ) { - QModelIndex idx = nif->getIndex( iSkinPart, "Partitions" ); + // Fill vertex data + verts = nif->getArray( iData, "Vertices" ); + numVerts = verts.count(); - uint numTris = 0; - uint numStrips = 0; - for ( int i = 0; i < nif->rowCount( idx ) && idx.isValid(); i++ ) { - partitions.append( SkinPartition( nif, idx.child( i, 0 ) ) ); - numTris += partitions[i].triangles.size(); - numStrips += partitions[i].tristrips.size(); + norms = nif->getArray( iData, "Normals" ); + if ( norms.count() < numVerts ) + norms.clear(); + + colors = nif->getArray( iData, "Vertex Colors" ); + if ( colors.count() < numVerts ) + colors.clear(); + // Detect if "Has Vertex Colors" is set to Yes in NiTriShape + // Used to compare against SLSF2_Vertex_Colors + hasVertexColors = (colors.count() > 0); + + tangents = nif->getArray( iData, "Tangents" ); + bitangents = nif->getArray( iData, "Bitangents" ); + + QModelIndex iExtraData = nif->getIndex( iBlock, "Extra Data List" ); + if ( iExtraData.isValid() ) { + int nExtra = nif->rowCount( iExtraData ); + for ( int e = 0; e < nExtra; e++ ) { + QModelIndex iExtra = nif->getBlock( nif->getLink( iExtraData.child( e, 0 ) ), "NiBinaryExtraData" ); + if ( nif->get( iExtra, "Name" ) == "Tangent space (binormal & tangent vectors)" ) { + iTangentData = iExtra; + QByteArray data = nif->get( iExtra, "Binary Data" ); + if ( data.count() == numVerts * 4 * 3 * 2 ) { + tangents.resize( numVerts ); + bitangents.resize( numVerts ); + Vector3 * t = (Vector3 *)data.data(); + + for ( int c = 0; c < numVerts; c++ ) + tangents[c] = *t++; + + for ( int c = 0; c < numVerts; c++ ) + bitangents[c] = *t++; + } } + } + } - triangles.clear(); - tristrips.clear(); - - triangles.reserve( numTris ); - tristrips.reserve( numStrips ); + coords.clear(); + QModelIndex iUVSets = nif->getIndex( iData, "UV Sets" ); + if ( iUVSets.isValid() ) { + int nSets = nif->rowCount( iUVSets ); + for ( int r = 0; r < nSets; r++ ) { + TexCoords tc = nif->getArray( iUVSets.child( r, 0 ) ); + if ( tc.count() < numVerts ) + tc.clear(); + coords.append( tc ); + } + } - for ( const SkinPartition& part : partitions ) { - triangles << part.getRemappedTriangles(); - tristrips << part.getRemappedTristrips(); - } + // Fill triangle/strips data + auto dataName = nif->itemName( iData ); + if ( dataName == "NiTriShapeData" ) { + // check indexes + // TODO: check other indexes as well + // TODO (Gavrant): test this! + QVector dataTris = nif->getArray( iData, "Triangles" ); + int nDataTris = dataTris.count(); + + for ( int i = 0; i < nDataTris; i++ ) { + Triangle t = dataTris[i]; + if ( t[0] < numVerts && t[1] < numVerts && t[2] < numVerts ) + triangles.append(t); } - isSkinned = weights.count() || partitions.count(); + int diff = nDataTris - triangles.count(); + if ( diff > 0 ) { + int block_idx = nif->getBlockNumber( nif->getIndex( iData, "Triangles" ) ); + Message::append( tr( "Warnings were generated while rendering mesh." ), + tr( "Block %1: %2 invalid indices in NiTriShapeData.Triangles" ).arg( block_idx ).arg( diff ) + ); + } + } else if ( dataName == "NiTriStripsData" ) { + QModelIndex points = nif->getIndex( iData, "Points" ); + if ( points.isValid() ) { + int nStrips = nif->rowCount( points ); + for ( int r = 0; r < nStrips; r++ ) + tristrips.append( nif->getArray( points.child( r, 0 ) ) ); + } else { + Message::append( tr( "Warnings were generated while rendering mesh." ), + tr( "Block %1: Invalid 'Points' array in %2" ) + .arg( nif->getBlockNumber( iData ) ) + .arg( dataName ) + ); + } } +} - Node::transform(); +QModelIndex Mesh::vertexAt( int idx ) const +{ + auto nif = NifModel::fromIndex( iBlock ); + if ( !nif ) + return QModelIndex(); + + auto iVertexData = nif->getIndex( iData, "Vertices" ); + auto iVertex = iVertexData.child( idx, 0 ); + + return iVertex; +} + +bool compareTriangles( const QPair & tri1, const QPair & tri2 ) +{ + return ( tri1.second < tri2.second ); } void Mesh::transformShapes() @@ -843,7 +791,7 @@ void Mesh::transformShapes() transformRigid = true; - if ( isSkinned && doSkinning ) { + if ( isSkinned && ( weights.count() || partitions.count() ) && scene->hasOption(Scene::DoSkinning) ) { transformRigid = false; int vcnt = verts.count(); @@ -942,7 +890,7 @@ void Mesh::transformShapes() boundSphere = BoundSphere( transVerts ); boundSphere.applyInv( viewTrans() ); - updateBounds = false; + needUpdateBounds = false; } else { transVerts = verts; transNorms = norms; @@ -962,19 +910,18 @@ void Mesh::transformShapes() transColors[c] = colors[c].blend( a ); } else { transColors = colors; - if ( bslsp ) { - if ( !(bslsp->getFlags1() & ShaderFlags::SLSF1_Vertex_Alpha) ) { - for ( int c = 0; c < colors.count(); c++ ) - transColors[c] = Color4( colors[c].red(), colors[c].green(), colors[c].blue(), 1.0f ); - } + // TODO (Gavrant): suspicious code. Should the check be replaced with !bssp.hasVertexAlpha ? + if ( bslsp && !bslsp->hasSF1(ShaderFlags::SLSF1_Vertex_Alpha) ) { + for ( int c = 0; c < colors.count(); c++ ) + transColors[c] = Color4( colors[c].red(), colors[c].green(), colors[c].blue(), 1.0f ); } } } BoundSphere Mesh::bounds() const { - if ( updateBounds ) { - updateBounds = false; + if ( needUpdateBounds ) { + needUpdateBounds = false; boundSphere = BoundSphere( verts ); } @@ -990,7 +937,7 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) if ( !scene->hasOption(Scene::ShowMarkers) && name.startsWith( "EditorMarker" ) ) return; - auto nif = static_cast(iBlock.model()); + auto nif = NifModel::fromIndex( iBlock ); if ( Node::SELECTING ) { if ( scene->isSelModeObject() ) { @@ -1005,12 +952,7 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) presorted |= presort; // Draw translucent meshes in second pass - - AlphaProperty * aprop = findProperty(); - - drawSecond |= (aprop && aprop->blend()); - - if ( secondPass && drawSecond ) { + if ( secondPass && drawInSecondPass ) { secondPass->add( this ); return; } @@ -1048,7 +990,7 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) } // Do VCs if legacy or if either bslsp or bsesp is set - bool doVCs = (!bssp) || (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); + bool doVCs = ( !bssp || bssp->hasSF2(ShaderFlags::SLSF2_Vertex_Colors) ); if ( transColors.count() && scene->hasOption(Scene::DoVertexColors) && doVCs ) { glEnableClientState( GL_COLOR_ARRAY ); @@ -1167,7 +1109,7 @@ void Mesh::drawSelection() const auto idx = scene->currentIndex; auto blk = scene->currentBlock; - auto nif = static_cast(idx.model()); + auto nif = NifModel::fromIndex( idx ); if ( !nif ) return; @@ -1242,7 +1184,7 @@ void Mesh::drawSelection() const if ( n == "Points" ) { glBegin( GL_POINTS ); - const NifModel * nif = static_cast( iData.model() ); + auto nif = NifModel::fromIndex( iData ); QModelIndex points = nif->getIndex( iData, "Points" ); if ( points.isValid() ) { diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 23e989ede..32d86b899 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -58,31 +58,32 @@ class Shape : public Node public: Shape( Scene * s, const QModelIndex & b ); - ~Shape() { clear(); } - void update( const NifModel * nif, const QModelIndex & ) override; + // IControllable + + void clear() override; + void transform() override; + + // end IControllable virtual void drawVerts() const {}; virtual QModelIndex vertexAt( int ) const { return QModelIndex(); }; +protected: int shapeNumber; -protected: - //! Sets the Controller void setController( const NifModel * nif, const QModelIndex & controller ) override; - - void updateShaderProperties( const NifModel * nif ); + void updateImpl( const NifModel * nif, const QModelIndex & index ) override; + virtual void updateData( const NifModel* nif ) = 0; void boneSphere( const NifModel * nif, const QModelIndex & index ) const; - int nifVersion = 0; - //! Shape data QPersistentModelIndex iData; + //! Tangent data + QPersistentModelIndex iTangentData; //! Does the data need updating? - bool updateData = false; - //! Was Skinning enabled last update? - bool doSkinning = false; + bool needUpdateData = false; //! Skin instance QPersistentModelIndex iSkin; @@ -91,6 +92,10 @@ class Shape : public Node //! Skin partition QPersistentModelIndex iSkinPart; + void resetSkinning(); + + int numVerts = 0; + //! Vertices QVector verts; //! Normals @@ -109,8 +114,8 @@ class Shape : public Node QVector tristrips; //! Sorted triangles QVector sortedTriangles; - //! Triangle indices - QVector indices; + + void resetVertexData(); //! Is the transform rigid or weighted? bool transformRigid = true; @@ -125,8 +130,6 @@ class Shape : public Node //! Transformed bitangents QVector transBitangents; - //! Does the skin data need updating? - bool updateSkin = false; //! Toggle for skinning bool isSkinned = false; @@ -136,6 +139,8 @@ class Shape : public Node QVector weights; QVector partitions; + void resetSkeletonData(); + //! Holds the name of the shader, or "" if no shader QString shader = ""; @@ -145,6 +150,9 @@ class Shape : public Node BSLightingShaderProperty * bslsp = nullptr; //! Skyrim effect shader property BSEffectShaderProperty * bsesp = nullptr; + + AlphaProperty * alphaProperty = nullptr; + //! Is shader set to double sided? bool isDoubleSided = false; //! Is shader set to animate using vertex alphas? @@ -154,11 +162,13 @@ class Shape : public Node bool depthTest = true; bool depthWrite = true; - bool drawSecond = false; + bool drawInSecondPass = false; bool translucent = false; + void updateShader(); + mutable BoundSphere boundSphere; - mutable bool updateBounds = false; + mutable bool needUpdateBounds = false; bool isLOD = false; }; @@ -168,16 +178,7 @@ class Mesh : public Shape { public: - Mesh( Scene * s, const QModelIndex & b ); - ~Mesh() { clear(); } - - // IControllable - - void clear() override; - void update( const NifModel * nif, const QModelIndex & ) override; - void transform() override; - - // end IControllable + Mesh( Scene * s, const QModelIndex & b ) : Shape( s, b ) { } // Node @@ -188,8 +189,7 @@ class Mesh : public Shape BoundSphere bounds() const override; - bool isHidden() const override; - QString textStats() const override; + QString textStats() const override; // TODO (Gavrant): move to Shape // end Node @@ -199,10 +199,11 @@ class Mesh : public Shape QModelIndex vertexAt( int ) const override; protected: + void updateImpl( const NifModel * nif, const QModelIndex & index ) override; + void updateData( const NifModel * nif ) override; - //! Tangent data - QPersistentModelIndex iTangentData; + void updateData_NiMesh( const NifModel * nif ); + void updateData_NiTriShape( const NifModel * nif ); }; - #endif diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 218c5ad7d..95ac7e4d9 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -202,7 +202,6 @@ Node::Node( Scene * s, const QModelIndex & index ) : IControllable( s, index ), connect( NifSkope::getOptions(), &SettingsDialog::saveSettings, this, &Node::updateSettings ); } - void Node::updateSettings() { QSettings settings; @@ -282,51 +281,39 @@ Controller * Node::findController( const QString & proptype, const QModelIndex & return c; } -void Node::update( const NifModel * nif, const QModelIndex & index ) +void Node::updateImpl( const NifModel * nif, const QModelIndex & index ) { - IControllable::update( nif, index ); - - if ( !iBlock.isValid() ) { - clear(); - return; - } + IControllable::updateImpl( nif, index ); nodeId = nif->getBlockNumber( iBlock ); if ( iBlock == index ) { flags.bits = nif->get( iBlock, "Flags" ); local = Transform( nif, iBlock ); - } - - if ( iBlock == index || !index.isValid() ) { - PropertyList newProps; - for ( const auto l : nif->getLinkArray( iBlock, "Properties" ) ) { - if ( Property * p = scene->getProperty( nif, nif->getBlock( l ) ) ) - newProps.add( p ); - } - - if ( Property * p = scene->getProperty( nif, nif->getBlock( nif->getLink( iBlock, "Shader Property" ) ) ) ) - newProps.add( p ); - if ( Property * p = scene->getProperty( nif, nif->getBlock( nif->getLink( iBlock, "Alpha Property" ) ) ) ) - newProps.add( p ); + // Properties + properties.clear(); + for ( auto l : nif->getLinkArray(iBlock, "Properties") ) + properties.add( scene->getProperty(nif, nif->getBlock(l)) ); - properties = newProps; + properties.add( scene->getProperty(nif, iBlock, "Shader Property", "BSShaderProperty") ); + properties.add( scene->getProperty(nif, iBlock, "Alpha Property", "NiAlphaProperty") ); + // Children children.clear(); QModelIndex iChildren = nif->getIndex( iBlock, "Children" ); - QList lChildren = nif->getChildLinks( nif->getBlockNumber( iBlock ) ); - if ( iChildren.isValid() ) { - for ( int c = 0; c < nif->rowCount( iChildren ); c++ ) { - qint32 link = nif->getLink( iChildren.child( c, 0 ) ); - - if ( lChildren.contains( link ) ) { - QModelIndex iChild = nif->getBlock( link ); - Node * node = scene->getNode( nif, iChild ); - - if ( node ) { - node->makeParent( this ); + int nChildren = nif->rowCount(iChildren); + if ( nChildren > 0 ) { + QList lChildren = nif->getChildLinks( nodeId ); + for ( int c = 0; c < nChildren; c++ ) { + qint32 link = nif->getLink( iChildren.child( c, 0 ) ); + + if ( lChildren.contains( link ) ) { + QModelIndex iChild = nif->getBlock( link ); + Node * node = scene->getNode( nif, iChild ); + if ( node ) + node->makeParent( this ); } } } @@ -351,24 +338,19 @@ void Node::setController( const NifModel * nif, const QModelIndex & iController if ( cname == "NiTransformController" ) { Controller * ctrl = new TransformController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } else if ( cname == "NiMultiTargetTransformController" ) { Controller * ctrl = new MultiTargetTransformController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } else if ( cname == "NiControllerManager" ) { Controller * ctrl = new ControllerManager( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } else if ( cname == "NiKeyframeController" ) { Controller * ctrl = new KeyframeController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } else if ( cname == "NiVisController" ) { Controller * ctrl = new VisibilityController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } } @@ -476,10 +458,10 @@ bool Node::isHidden() const { if ( scene->hasOption(Scene::ShowHidden) ) return false; - - if ( flags.node.hidden || ( parent && parent->isHidden() ) ) + if ( flags.node.hidden ) + return true; + if ( parent && parent->isHidden() ) return true; - return false; /*!Options::cullExpression().pattern().isEmpty() && name.contains( Options::cullExpression() );*/ } @@ -489,11 +471,10 @@ void Node::transform() // if there's a rigid body attached, then calculate and cache the body's transform // (need this later in the drawing stage for the constraints) - const NifModel * nif = static_cast( iBlock.model() ); - - if ( iBlock.isValid() && nif ) { + auto nif = NifModel::fromValidIndex( iBlock ); + if ( nif && nif->getBSVersion() > 0 ) { QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); - if ( nif->getUserVersion2() > 0 && iObject.isValid() ) { + if ( iObject.isValid() ) { QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); if ( iBody.isValid() ) { @@ -581,7 +562,7 @@ void Node::draw() void Node::drawSelection() const { - auto nif = static_cast(scene->currentIndex.model()); + auto nif = NifModel::fromIndex( scene->currentIndex ); if ( !nif ) return; @@ -1377,9 +1358,8 @@ void Node::drawHavok() node->drawHavok(); } - const NifModel * nif = static_cast( iBlock.model() ); - - if ( !( iBlock.isValid() && nif ) ) + auto nif = NifModel::fromValidIndex(iBlock); + if ( !nif ) return; //Check if there's any old style collision bounding box set @@ -1415,7 +1395,7 @@ void Node::drawHavok() } // Only Bethesda support after this - if ( nif->getUserVersion2() == 0 ) + if ( nif->getBSVersion() == 0 ) return; // Draw BSMultiBound @@ -1773,9 +1753,8 @@ void Node::drawFurn() node->drawFurn(); } - const NifModel * nif = static_cast( iBlock.model() ); - - if ( !( iBlock.isValid() && nif ) ) + auto nif = NifModel::fromValidIndex(iBlock); + if ( !nif ) return; if ( !scene->isSelModeObject() ) @@ -1839,7 +1818,7 @@ void Node::drawShapes( NodeList * secondPass, bool presort ) if ( isHidden() ) return; - const NifModel * nif = static_cast(iBlock.model()); + auto nif = NifModel::fromIndex( iBlock ); // BSOrderedNode support // Only set if true (|=) so that it propagates to all children @@ -1882,9 +1861,8 @@ BoundSphere Node::bounds() const boundsphere |= BoundSphere( worldTrans().translation, 0 ); } - const NifModel * nif = static_cast( iBlock.model() ); - - if ( !( iBlock.isValid() && nif ) ) + auto nif = NifModel::fromValidIndex(iBlock); + if ( !nif ) return boundsphere; // old style collision bounding box @@ -1929,11 +1907,11 @@ void LODNode::clear() ranges.clear(); } -void LODNode::update( const NifModel * nif, const QModelIndex & index ) +void LODNode::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Node::update( nif, index ); + Node::updateImpl( nif, index ); - if ( ( iBlock.isValid() && index == iBlock ) || ( iData.isValid() && index == iData ) ) { + if ( ( index == iBlock ) || ( iData.isValid() && index == iData ) ) { ranges.clear(); iData = nif->getBlock( nif->getLink( iBlock, "LOD Level Data" ), "NiRangeLODData" ); QModelIndex iLevels; @@ -1953,8 +1931,6 @@ void LODNode::update( const NifModel * nif, const QModelIndex & index ) ); } } - - } } diff --git a/src/gl/glnode.h b/src/gl/glnode.h index 0bbcc8a84..23544f5a6 100644 --- a/src/gl/glnode.h +++ b/src/gl/glnode.h @@ -103,7 +103,6 @@ class Node : public IControllable // IControllable void clear() override; - void update( const NifModel * nif, const QModelIndex & block ) override; void transform() override; // end IControllable @@ -149,6 +148,8 @@ public slots: protected: void setController( const NifModel * nif, const QModelIndex & controller ) override; + void updateImpl( const NifModel * nif, const QModelIndex & block ) override; + // Old Options API // TODO: Move away from the GL-like naming void glHighlightColor() const; @@ -198,7 +199,6 @@ class LODNode : public Node // IControllable void clear() override; - void update( const NifModel * nif, const QModelIndex & block ) override; void transform() override; // end IControllable @@ -208,6 +208,8 @@ class LODNode : public Node QPersistentModelIndex iData; Vector3 center; + + void updateImpl( const NifModel * nif, const QModelIndex & block ) override; }; //! A Node that always faces the camera diff --git a/src/gl/glparticles.cpp b/src/gl/glparticles.cpp index d4441756f..bc6cac1c4 100644 --- a/src/gl/glparticles.cpp +++ b/src/gl/glparticles.cpp @@ -52,36 +52,34 @@ void Particles::clear() transVerts.clear(); } -void Particles::update( const NifModel * nif, const QModelIndex & index ) +void Particles::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Node::update( nif, index ); + Node::updateImpl( nif, index ); - if ( !iBlock.isValid() ) - return; - - upData |= ( iData == index ); - - if ( iBlock == index ) { - for ( const auto link : nif->getChildLinks( id() ) ) { - QModelIndex iChild = nif->getBlock( link ); + if ( index == iBlock ) { + for (const auto link : nif->getChildLinks(id())) { + QModelIndex iChild = nif->getBlock(link); - if ( !iChild.isValid() ) + if (!iChild.isValid()) continue; - if ( nif->inherits( iChild, "NiParticlesData" ) ) { - iData = iChild; - upData = true; + if (nif->inherits(iChild, "NiParticlesData")) { + iData = iChild; + updateData = true; } } } + + if ( index == iData ) + updateData = true; } void Particles::setController( const NifModel * nif, const QModelIndex & index ) { - if ( nif->itemName( index ) == "NiParticleSystemController" || nif->itemName( index ) == "NiBSPArrayController" ) { + auto contrName = nif->itemName(index); + if ( contrName == "NiParticleSystemController" || contrName == "NiBSPArrayController" ) { Controller * ctrl = new ParticleController( this, index ); - ctrl->update( nif, index ); - controllers.append( ctrl ); + registerController(nif, ctrl); } else { Node::setController( nif, index ); } @@ -89,15 +87,14 @@ void Particles::setController( const NifModel * nif, const QModelIndex & index ) void Particles::transform() { - const NifModel * nif = static_cast( iBlock.model() ); - - if ( !nif || !iBlock.isValid() ) { + auto nif = NifModel::fromValidIndex(iBlock); + if ( !nif ) { clear(); return; } - if ( upData ) { - upData = false; + if ( updateData ) { + updateData = false; verts = nif->getArray( nif->getIndex( iData, "Vertices" ) ); colors = nif->getArray( nif->getIndex( iData, "Vertex Colors" ) ); diff --git a/src/gl/glparticles.h b/src/gl/glparticles.h index 37a529979..085bbbc29 100644 --- a/src/gl/glparticles.h +++ b/src/gl/glparticles.h @@ -49,7 +49,6 @@ class Particles : public Node Particles( Scene * s, const QModelIndex & b ) : Node( s, b ) {} void clear() override; - void update( const NifModel * nif, const QModelIndex & ) override; void transform() override; void transformShapes() override; @@ -60,9 +59,10 @@ class Particles : public Node protected: void setController( const NifModel * nif, const QModelIndex & controller ) override; + void updateImpl( const NifModel * nif, const QModelIndex & index ) override; QPersistentModelIndex iData; - bool upData = false; + bool updateData = false; QVector verts; QVector colors; diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 4f9dea77c..7725e129c 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -201,11 +201,11 @@ void PropertyList::merge( const PropertyList & other ) } } -void AlphaProperty::update( const NifModel * nif, const QModelIndex & block ) +void AlphaProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Property::update( nif, block ); + Property::updateImpl( nif, index ); - if ( iBlock.isValid() && iBlock == block ) { + if ( index == iBlock ) { unsigned short flags = nif->get( iBlock, "Flags" ); alphaBlend = flags & 1; @@ -226,30 +226,25 @@ void AlphaProperty::update( const NifModel * nif, const QModelIndex & block ) alphaTest = flags & ( 1 << 9 ); alphaFunc = testMap[ ( flags >> 10 ) & 0x7 ]; - alphaThreshold = nif->get( iBlock, "Threshold" ) / 255.0; + alphaThreshold = float( nif->get( iBlock, "Threshold" ) ) / 255.0; alphaSort = ( flags & 0x2000 ) == 0; // Temporary Weapon Blood fix for FO4 - if ( nif->getUserVersion2() >= 130 ) + if ( nif->getBSVersion() >= 130 ) alphaTest |= (flags == 20547); } } void AlphaProperty::setController( const NifModel * nif, const QModelIndex & controller ) { - if ( nif->itemName( controller ) == "BSNiAlphaPropertyTestRefController" ) { + auto contrName = nif->itemName(controller); + if ( contrName == "BSNiAlphaPropertyTestRefController" ) { Controller * ctrl = new AlphaController( this, controller ); - ctrl->update( nif, controller ); - controllers.append( ctrl ); + registerController(nif, ctrl); } } -void AlphaProperty::setThreshold( float threshold ) -{ - alphaThreshold = threshold; -} - void glProperty( AlphaProperty * p ) { if ( p && p->alphaBlend && p->scene->hasOption(Scene::DoBlending) ) { @@ -269,11 +264,11 @@ void glProperty( AlphaProperty * p ) } } -void ZBufferProperty::update( const NifModel * nif, const QModelIndex & block ) +void ZBufferProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Property::update( nif, block ); + Property::updateImpl( nif, index ); - if ( iBlock.isValid() && iBlock == block ) { + if ( index == iBlock ) { int flags = nif->get( iBlock, "Flags" ); depthTest = flags & 1; depthMask = flags & 2; @@ -315,23 +310,23 @@ void glProperty( ZBufferProperty * p ) TexturingProperty */ -void TexturingProperty::update( const NifModel * nif, const QModelIndex & property ) +void TexturingProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Property::update( nif, property ); + Property::updateImpl( nif, index ); - if ( iBlock.isValid() && iBlock == property ) { + if ( index == iBlock ) { static const char * texnames[numTextures] = { "Base Texture", "Dark Texture", "Detail Texture", "Gloss Texture", "Glow Texture", "Bump Map Texture", "Decal 0 Texture", "Decal 1 Texture", "Decal 2 Texture", "Decal 3 Texture" }; for ( int t = 0; t < numTextures; t++ ) { - QModelIndex iTex = nif->getIndex( property, texnames[t] ); + QModelIndex iTex = nif->getIndex( iBlock, texnames[t] ); if ( iTex.isValid() ) { textures[t].iSource = nif->getBlock( nif->getLink( iTex, "Source" ), "NiSourceTexture" ); textures[t].coordset = nif->get( iTex, "UV Set" ); + int filterMode = 0, clampMode = 0; - if ( nif->checkVersion( 0, 0x14010002 ) ) { filterMode = nif->get( iTex, "Filter Mode" ); clampMode = nif->get( iTex, "Clamp Mode" ); @@ -484,10 +479,9 @@ bool TexturingProperty::bind( int id, const QVector > & texcoor QString TexturingProperty::fileName( int id ) const { if ( id >= 0 && id <= (numTextures - 1) ) { - QModelIndex iSource = textures[ id ].iSource; - const NifModel * nif = qobject_cast( iSource.model() ); - - if ( nif && iSource.isValid() ) { + QModelIndex iSource = textures[id].iSource; + auto nif = NifModel::fromValidIndex(iSource); + if ( nif ) { return nif->get( iSource, "File Name" ); } } @@ -508,14 +502,13 @@ int TexturingProperty::coordSet( int id ) const //! Set the appropriate Controller void TexturingProperty::setController( const NifModel * nif, const QModelIndex & iController ) { - if ( nif->itemName( iController ) == "NiFlipController" ) { + auto contrName = nif->itemName(iController); + if ( contrName == "NiFlipController" ) { Controller * ctrl = new TexFlipController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } else if ( nif->itemName( iController ) == "NiTextureTransformController" ) { + registerController(nif, ctrl); + } else if ( contrName == "NiTextureTransformController" ) { Controller * ctrl = new TexTransController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } } @@ -548,11 +541,11 @@ void glProperty( TexturingProperty * p ) TextureProperty */ -void TextureProperty::update( const NifModel * nif, const QModelIndex & property ) +void TextureProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Property::update( nif, property ); + Property::updateImpl( nif, index ); - if ( iBlock.isValid() && iBlock == property ) { + if ( index == iBlock ) { iImage = nif->getBlock( nif->getLink( iBlock, "Image" ), "NiImage" ); } } @@ -589,9 +582,8 @@ bool TextureProperty::bind( const QVector > & texcoords ) QString TextureProperty::fileName() const { - const NifModel * nif = qobject_cast( iImage.model() ); - - if ( nif && iImage.isValid() ) + auto nif = NifModel::fromValidIndex(iImage); + if ( nif ) return nif->get( iImage, "File Name" ); return QString(); @@ -600,10 +592,10 @@ QString TextureProperty::fileName() const void TextureProperty::setController( const NifModel * nif, const QModelIndex & iController ) { - if ( nif->itemName( iController ) == "NiFlipController" ) { + auto contrName = nif->itemName(iController); + if ( contrName == "NiFlipController" ) { Controller * ctrl = new TexFlipController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } } @@ -618,39 +610,36 @@ void glProperty( TextureProperty * p ) MaterialProperty */ -void MaterialProperty::update( const NifModel * nif, const QModelIndex & index ) +void MaterialProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Property::update( nif, index ); - - if ( iBlock.isValid() && iBlock == index ) { - alpha = nif->get( index, "Alpha" ); + Property::updateImpl( nif, index ); + if ( index == iBlock ) { + alpha = nif->get( iBlock, "Alpha" ); if ( alpha < 0.0 ) alpha = 0.0; - if ( alpha > 1.0 ) alpha = 1.0; - ambient = Color4( nif->get( index, "Ambient Color" ) ); - diffuse = Color4( nif->get( index, "Diffuse Color" ) ); - specular = Color4( nif->get( index, "Specular Color" ) ); - emissive = Color4( nif->get( index, "Emissive Color" ) ); + ambient = Color4( nif->get( iBlock, "Ambient Color" ) ); + diffuse = Color4( nif->get( iBlock, "Diffuse Color" ) ); + specular = Color4( nif->get( iBlock, "Specular Color" ) ); + emissive = Color4( nif->get( iBlock, "Emissive Color" ) ); // OpenGL needs shininess clamped otherwise it generates GL_INVALID_VALUE - shininess = std::min( std::max( nif->get( index, "Glossiness" ), 0.0f ), 128.0f ); + shininess = std::min( std::max( nif->get( iBlock, "Glossiness" ), 0.0f ), 128.0f ); } } void MaterialProperty::setController( const NifModel * nif, const QModelIndex & iController ) { - if ( nif->itemName( iController ) == "NiAlphaController" ) { + auto contrName = nif->itemName(iController); + if ( contrName == "NiAlphaController" ) { Controller * ctrl = new AlphaController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } else if ( nif->itemName( iController ) == "NiMaterialColorController" ) { + registerController(nif, ctrl); + } else if ( contrName == "NiMaterialColorController" ) { Controller * ctrl = new MaterialColorController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); + registerController(nif, ctrl); } } @@ -680,20 +669,20 @@ void glProperty( MaterialProperty * p, SpecularProperty * s ) } } -void SpecularProperty::update( const NifModel * nif, const QModelIndex & block ) +void SpecularProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Property::update( nif, block ); + Property::updateImpl( nif, index ); - if ( iBlock.isValid() && iBlock == block ) { + if ( index == iBlock ) { spec = nif->get( iBlock, "Flags" ) != 0; } } -void WireframeProperty::update( const NifModel * nif, const QModelIndex & block ) +void WireframeProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Property::update( nif, block ); + Property::updateImpl( nif, index ); - if ( iBlock.isValid() && iBlock == block ) { + if ( index == iBlock ) { wire = nif->get( iBlock, "Flags" ) != 0; } } @@ -708,11 +697,11 @@ void glProperty( WireframeProperty * p ) } } -void VertexColorProperty::update( const NifModel * nif, const QModelIndex & block ) +void VertexColorProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - Property::update( nif, block ); + Property::updateImpl( nif, index ); - if ( iBlock.isValid() && iBlock == block ) { + if ( index == iBlock ) { if ( nif->checkVersion( 0, 0x14010001 ) ) { vertexmode = nif->get( iBlock, "Vertex Mode" ); // 0 : source ignore @@ -769,12 +758,12 @@ void glProperty( VertexColorProperty * p, bool vertexcolors ) } } -void StencilProperty::update( const NifModel * nif, const QModelIndex & block ) +void StencilProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { using namespace Stencil; - Property::update( nif, block ); + Property::updateImpl( nif, index ); - if ( iBlock.isValid() && iBlock == block ) { + if ( index == iBlock ) { static const GLenum funcMap[8] = { GL_NEVER, GL_GEQUAL, GL_NOTEQUAL, GL_GREATER, GL_LEQUAL, GL_EQUAL, GL_LESS, GL_ALWAYS }; @@ -850,21 +839,40 @@ void glProperty( StencilProperty * p ) BSShaderLightingProperty */ -void BSShaderLightingProperty::update( const NifModel * nif, const QModelIndex & property ) +BSShaderLightingProperty::~BSShaderLightingProperty() { - Property::update( nif, property ); - - if ( iBlock.isValid() && iBlock == property ) { - iTextureSet = nif->getBlock( nif->getLink( iBlock, "Texture Set" ), "BSShaderTextureSet" ); + if ( material ) + delete material; +} - // handle niobject name="BSEffectShaderProperty... - if ( !iTextureSet.isValid() ) - iSourceTexture = iBlock; +void BSShaderLightingProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) +{ + Property::updateImpl( nif, index ); + if ( index == iBlock ) { + iTextureSet = nif->getBlock( nif->getLink( iBlock, "Texture Set" ), "BSShaderTextureSet" ); iWetMaterial = nif->getIndex( iBlock, "Root Material" ); } } +void BSShaderLightingProperty::resetParams() +{ + flags1 = ShaderFlags::SLSF1_ZBuffer_Test; + flags2 = ShaderFlags::SLSF2_ZBuffer_Write; + + uvScale.reset(); + uvOffset.reset(); + clampMode = CLAMP_S_CLAMP_T; + + hasVertexColors = false; + hasVertexAlpha = false; + + depthTest = false; + depthWrite = false; + isDoubleSided = false; + isVertexAlphaAnimation = false; +} + void glProperty( BSShaderLightingProperty * p ) { if ( p && p->scene->hasOption(Scene::DoTexturing) && p->bind(0) ) { @@ -872,6 +880,25 @@ void glProperty( BSShaderLightingProperty * p ) } } +void BSShaderLightingProperty::clear() +{ + Property::clear(); + + setMaterial(nullptr); +} + +void BSShaderLightingProperty::setMaterial( Material * newMaterial ) +{ + if (newMaterial && !newMaterial->isValid()) { + delete newMaterial; + newMaterial = nullptr; + } + if ( material && material != newMaterial ) { + delete material; + } + material = newMaterial; +} + bool BSShaderLightingProperty::bind( int id, const QString & fname, TexClampMode mode ) { GLuint mipmaps = 0; @@ -981,7 +1008,7 @@ QString BSShaderLightingProperty::fileName( int id ) const const NifModel * nif; // Fallout 4 - nif = qobject_cast(iWetMaterial.model()); + nif = NifModel::fromValidIndex(iWetMaterial); if ( nif ) { // BSLSP auto m = static_cast(material); @@ -1035,41 +1062,51 @@ QString BSShaderLightingProperty::fileName( int id ) const } } } + + return QString(); } - nif = qobject_cast(iTextureSet.model()); - if ( nif && iTextureSet.isValid() ) { - int nTextures = nif->get( iTextureSet, "Num Textures" ); - QModelIndex iTextures = nif->getIndex( iTextureSet, "Textures" ); + // From iTextureSet + nif = NifModel::fromValidIndex(iTextureSet); + if ( nif ) { + if ( id >= 0 && id < nif->get(iTextureSet, "Num Textures") ) { + QModelIndex iTextures = nif->getIndex(iTextureSet, "Textures"); + return nif->get( iTextures.child(id, 0) ); + } - if ( id >= 0 && id < nTextures ) - return nif->get( iTextures.child( id, 0 ) ); - } else { - // handle niobject name="BSEffectShaderProperty... - auto m = static_cast(material); + return QString(); + } - nif = qobject_cast(iSourceTexture.model()); - if ( !m && nif && iSourceTexture.isValid() ) { - switch ( id ) { - case 0: - return nif->get( iSourceTexture, "Source Texture" ); - case 1: - return nif->get( iSourceTexture, "Greyscale Texture" ); - case 2: - return nif->get( iSourceTexture, "Env Map Texture" ); - case 3: - return nif->get( iSourceTexture, "Normal Texture" ); - case 4: - return nif->get( iSourceTexture, "Env Mask Texture" ); - case 6: - return nif->get( iSourceTexture, "Reflectance Texture" ); - case 7: - return nif->get( iSourceTexture, "Lighting Texture" ); - } - } else if ( m && m->isValid() ) { + // From material + auto m = static_cast(material); + if ( m ) { + if (m->isValid()) { auto tex = m->textures(); return tex[id]; } + + return QString(); + } + + // Handle niobject name="BSEffectShaderProperty... + nif = NifModel::fromIndex( iBlock ); + if ( nif ) { + switch ( id ) { + case 0: + return nif->get( iBlock, "Source Texture" ); + case 1: + return nif->get( iBlock, "Greyscale Texture" ); + case 2: + return nif->get( iBlock, "Env Map Texture" ); + case 3: + return nif->get( iBlock, "Normal Texture" ); + case 4: + return nif->get( iBlock, "Env Mask Texture" ); + case 6: + return nif->get( iBlock, "Reflectance Texture" ); + case 7: + return nif->get( iBlock, "Lighting Texture" ); + } } return QString(); @@ -1091,243 +1128,136 @@ int BSShaderLightingProperty::getId( const QString & id ) return hash.value( id, -1 ); } -QPersistentModelIndex BSShaderLightingProperty::getTextureSet() const +void BSShaderLightingProperty::setFlags1( const NifModel * nif ) { - return iTextureSet; -} - -unsigned int BSShaderLightingProperty::getFlags1() const -{ - return (unsigned int)flags1; -} - -unsigned int BSShaderLightingProperty::getFlags2() const -{ - return (unsigned int)flags2; -} - -void BSShaderLightingProperty::setFlags1( const NifModel * nif, const QModelIndex & prop ) -{ - flags1 = ShaderFlags::SF1( nif->get( prop, "Shader Flags 1" ) ); - if ( stream == 155 ) { - auto sf1 = nif->getArray( prop, "SF1" ); - auto sf2 = nif->getArray( prop, "SF2" ); + if ( nif->getBSVersion() == 155 ) { + auto sf1 = nif->getArray( iBlock, "SF1" ); + auto sf2 = nif->getArray( iBlock, "SF2" ); sf1.append( sf2 ); uint64_t flags = 0; for ( auto sf : sf1 ) { flags |= ShaderFlags::CRC_TO_FLAG.value( sf, 0 ); } - - flags1 = ShaderFlags::SF1((uint32_t)flags); + flags1 = ShaderFlags::SF1( (uint32_t)flags ); + } else { + flags1 = ShaderFlags::SF1( nif->get(iBlock, "Shader Flags 1") ); } } -void BSShaderLightingProperty::setFlags2( const NifModel * nif, const QModelIndex & prop ) +void BSShaderLightingProperty::setFlags2( const NifModel * nif ) { - flags2 = ShaderFlags::SF2( nif->get( prop, "Shader Flags 2" ) ); - if ( stream == 155 ) { - auto sf1 = nif->getArray( prop, "SF1" ); - auto sf2 = nif->getArray( prop, "SF2" ); + if ( nif->getBSVersion() == 155 ) { + auto sf1 = nif->getArray( iBlock, "SF1" ); + auto sf2 = nif->getArray( iBlock, "SF2" ); sf1.append( sf2 ); uint64_t flags = 0; for ( auto sf : sf1 ) { flags |= ShaderFlags::CRC_TO_FLAG.value( sf, 0 ); } - flags2 = ShaderFlags::SF2( (uint32_t)(flags >> 32) ); + } else { + flags2 = ShaderFlags::SF2( nif->get(iBlock, "Shader Flags 2") ); } } -UVScale BSShaderLightingProperty::getUvScale() const -{ - return uvScale; -} +/* + BSLightingShaderProperty +*/ -UVOffset BSShaderLightingProperty::getUvOffset() const +void BSLightingShaderProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - return uvOffset; -} + BSShaderLightingProperty::updateImpl( nif, index ); -void BSShaderLightingProperty::setUvScale( float x, float y ) -{ - uvScale.x = x; - uvScale.y = y; + if ( index == iBlock ) { + setMaterial(name.endsWith(".bgsm", Qt::CaseInsensitive) ? new ShaderMaterial(name, scene->game) : nullptr); + updateParams(nif); + } + else if ( index == iTextureSet ) { + updateParams(nif); + } } -void BSShaderLightingProperty::setUvOffset( float x, float y ) +void BSLightingShaderProperty::resetParams() { - uvOffset.x = x; - uvOffset.y = y; -} + BSShaderLightingProperty::resetParams(); -TexClampMode BSShaderLightingProperty::getClampMode() const -{ - return clampMode; -} + hasGlowMap = false; + hasEmittance = false; + hasSoftlight = false; + hasBacklight = false; + hasRimlight = false; + hasSpecularMap = false; + hasMultiLayerParallax = false; + hasEnvironmentMap = false; + useEnvironmentMask = false; + hasHeightMap = false; + hasRefraction = false; + hasDetailMask = false; + hasTintMask = false; + hasTintColor = false; + greyscaleColor = false; -void BSShaderLightingProperty::setClampMode( uint mode ) -{ - clampMode = TexClampMode( mode ); -} + emissiveColor = Color3(0, 0, 0); + emissiveMult = 1.0; -Material * BSShaderLightingProperty::mat() const -{ - return material; -} + specularColor = Color3(0, 0, 0); + specularGloss = 0; + specularStrength = 0; -/* - BSLightingShaderProperty -*/ + tintColor = Color3(0, 0, 0); -void BSLightingShaderProperty::update( const NifModel * nif, const QModelIndex & property ) -{ - BSShaderLightingProperty::update( nif, property ); + alpha = 1.0; - if ( name.endsWith( ".bgsm", Qt::CaseInsensitive ) ) - material = new ShaderMaterial( name, scene->game ); + lightingEffect1 = 0.0; + lightingEffect2 = 1.0; - if ( material && !material->isValid() ) - material = nullptr; + environmentReflection = 0.0; - if ( material && name.isEmpty() ) { - delete material; - material = nullptr; - } + // Multi-layer properties + innerThickness = 1.0; + innerTextureScale.reset(); + outerRefractionStrength = 0.0; + outerReflectionStrength = 1.0; + fresnelPower = 5.0; + paletteScale = 1.0; + rimPower = 2.0; + backlightPower = 0.0; } -void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelIndex & prop ) +void BSLightingShaderProperty::updateParams( const NifModel * nif ) { - ShaderMaterial * m = nullptr; - if ( mat() && mat()->isValid() ) - m = static_cast(mat()); - - stream = nif->getUserVersion2(); - auto textures = nif->getArray( getTextureSet(), "Textures" ); + resetParams(); - setShaderType( nif->get( prop, "Shader Type" ) ); - setFlags1( nif, prop ); - setFlags2( nif, prop ); - - hasVertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); - hasVertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); + setFlags1( nif ); + setFlags2( nif ); - if ( stream == 155 ) { + if ( nif->getBSVersion() == 155 ) { shaderType = ShaderFlags::ShaderType::ST_EnvironmentMap; hasVertexAlpha = true; hasVertexColors = true; + } else { + shaderType = ShaderFlags::ShaderType( nif->get(iBlock, "Shader Type") ); + hasVertexAlpha = hasSF1(ShaderFlags::SLSF1_Vertex_Alpha); + hasVertexColors = hasSF2(ShaderFlags::SLSF2_Vertex_Colors); } + isVertexAlphaAnimation = hasSF2(ShaderFlags::SLSF2_Tree_Anim); + ShaderMaterial * m = ( material && material->isValid() ) ? static_cast(material) : nullptr; + if ( m ) { + alpha = m->fAlpha; - if ( !m ) { - isDoubleSided = hasSF2( ShaderFlags::SLSF2_Double_Sided ); - depthTest = hasSF1( ShaderFlags::SLSF1_ZBuffer_Test ); - depthWrite = hasSF2( ShaderFlags::SLSF2_ZBuffer_Write ); - - alpha = nif->get( prop, "Alpha" ); - - auto scale = nif->get( prop, "UV Scale" ); - auto offset = nif->get( prop, "UV Offset" ); - - setUvScale( scale[0], scale[1] ); - setUvOffset( offset[0], offset[1] ); - setClampMode( nif->get( prop, "Texture Clamp Mode" ) ); - - // Specular - if ( hasSF1( ShaderFlags::SLSF1_Specular ) ) { - auto spC = nif->get( prop, "Specular Color" ); - auto spG = nif->get( prop, "Glossiness" ); - // FO4 - if ( spG == 0.0 ) - spG = nif->get( prop, "Smoothness" ); - auto spS = nif->get( prop, "Specular Strength" ); - setSpecular( spC, spG, spS ); - } else { - setSpecular( Color3( 0, 0, 0 ), 0, 0 ); - } - - // Emissive - setEmissive( nif->get( prop, "Emissive Color" ), nif->get( prop, "Emissive Multiple" ) ); - - hasEmittance = hasSF1( ShaderFlags::SLSF1_Own_Emit ); - hasGlowMap = getShaderType() & ShaderFlags::ST_GlowShader && hasSF2( ShaderFlags::SLSF2_Glow_Map ) && !textures.value( 2, "" ).isEmpty(); - - - // Version Dependent settings - if ( stream < 130 ) { - lightingEffect1 = nif->get( prop, "Lighting Effect 1" ); - lightingEffect2 = nif->get( prop, "Lighting Effect 2" ); - - innerThickness = nif->get( prop, "Parallax Inner Layer Thickness" ); - outerRefractionStrength = nif->get( prop, "Parallax Refraction Scale" ); - outerReflectionStrength = nif->get( prop, "Parallax Envmap Strength" ); - auto innerScale = nif->get( prop, "Parallax Inner Layer Texture Scale" ); - setInnerTextureScale( innerScale[0], innerScale[1] ); - - hasSpecularMap = hasSF1( ShaderFlags::SLSF1_Specular ) && !textures.value( 7, "" ).isEmpty(); - hasHeightMap = isST( ShaderFlags::ST_Heightmap ) && hasSF1( ShaderFlags::SLSF1_Parallax ) && !textures.value( 3, "" ).isEmpty(); - hasBacklight = hasSF2( ShaderFlags::SLSF2_Back_Lighting ); - hasRimlight = hasSF2( ShaderFlags::SLSF2_Rim_Lighting ); - hasSoftlight = hasSF2( ShaderFlags::SLSF2_Soft_Lighting ); - hasModelSpaceNormals = hasSF1( ShaderFlags::SLSF1_Model_Space_Normals ); - hasMultiLayerParallax = hasSF2( ShaderFlags::SLSF2_Multi_Layer_Parallax ); - - hasRefraction = hasSF1( ShaderFlags::SLSF1_Refraction ); - hasFireRefraction = hasSF1( ShaderFlags::SLSF1_Fire_Refraction ); - - hasTintColor = false; - hasTintMask = isST( ShaderFlags::ST_FaceTint ); - hasDetailMask = hasTintMask; - - QString tint; - if ( isST( ShaderFlags::ST_HairTint ) ) - tint = "Hair Tint Color"; - else if ( isST( ShaderFlags::ST_SkinTint ) ) - tint = "Skin Tint Color"; - - if ( !tint.isEmpty() ) { - hasTintColor = true; - setTintColor( nif->get( prop, tint ) ); - } - } else { - hasSpecularMap = hasSF1( ShaderFlags::SLSF1_Specular ); - greyscaleColor = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteColor ); - paletteScale = nif->get( prop, "Grayscale to Palette Scale" ); - lightingEffect1 = nif->get( prop, "Subsurface Rolloff" ); - backlightPower = nif->get( prop, "Backlight Power" ); - fresnelPower = nif->get( prop, "Fresnel Power" ); - } - - // Environment Map, Mask and Reflection Scale - hasEnvironmentMap = isST( ShaderFlags::ST_EnvironmentMap ) && hasSF1( ShaderFlags::SLSF1_Environment_Mapping ); - hasEnvironmentMap |= isST( ShaderFlags::ST_EyeEnvmap ) && hasSF1( ShaderFlags::SLSF1_Eye_Environment_Mapping ); - if ( stream == 100 ) - hasEnvironmentMap |= hasMultiLayerParallax; - - hasCubeMap = ( - isST( ShaderFlags::ST_EnvironmentMap ) - || isST( ShaderFlags::ST_EyeEnvmap ) - || isST( ShaderFlags::ST_MultiLayerParallax ) - ) - && hasEnvironmentMap - && !textures.value( 4, "" ).isEmpty(); + uvScale.set(m->fUScale, m->fVScale); + uvOffset.set(m->fUOffset, m->fVOffset); - useEnvironmentMask = hasEnvironmentMap && !textures.value( 5, "" ).isEmpty(); - - if ( isST( ShaderFlags::ST_EnvironmentMap ) ) - environmentReflection = nif->get( prop, "Environment Map Scale" ); - else if ( isST( ShaderFlags::ST_EyeEnvmap ) ) - environmentReflection = nif->get( prop, "Eye Cubemap Scale" ); - - } else { - alpha = m->fAlpha; + specularColor = Color3(m->cSpecularColor); + specularGloss = m->fSmoothness; + specularStrength = m->fSpecularMult; - setUvScale( m->fUScale, m->fVScale ); - setUvOffset( m->fUOffset, m->fVOffset ); - setSpecular( m->cSpecularColor, m->fSmoothness, m->fSpecularMult ); - setEmissive( m->cEmittanceColor, m->fEmittanceMult ); + emissiveColor = Color3(m->cEmittanceColor); + emissiveMult = m->fEmittanceMult; if ( m->bTileU && m->bTileV ) clampMode = TexClampMode::WRAP_S_WRAP_T; @@ -1342,8 +1272,8 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI greyscaleColor = m->bGrayscaleToPaletteColor; paletteScale = m->fGrayscaleToPaletteScale; - hasSpecularMap = m->bSpecularEnabled && (!m->textureList[2].isEmpty() - || (stream == 155 && !m->textureList[7].isEmpty())); + hasSpecularMap = m->bSpecularEnabled && (!m->textureList[2].isEmpty() + || (nif->getBSVersion() == 155 && !m->textureList[7].isEmpty())); hasGlowMap = m->bGlowmap; hasEmittance = m->bEmitEnabled; hasBacklight = m->bBackLighting; @@ -1356,255 +1286,175 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelI depthWrite = m->bZBufferWrite; hasEnvironmentMap = m->bEnvironmentMapping || m->bPBR; - hasCubeMap = m->bEnvironmentMapping && stream == 130 && !m->textureList[4].isEmpty(); useEnvironmentMask = hasEnvironmentMap && !m->bGlowmap && !m->textureList[5].isEmpty(); environmentReflection = m->fEnvironmentMappingMaskScale; if ( hasSoftlight ) - setLightingEffect1( m->fSubsurfaceLightingRolloff ); - } -} - -void BSLightingShaderProperty::setController( const NifModel * nif, const QModelIndex & iController ) -{ - if ( nif->itemName( iController ) == "BSLightingShaderPropertyFloatController" ) { - Controller * ctrl = new LightingFloatController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } else if ( nif->itemName( iController ) == "BSLightingShaderPropertyColorController" ) { - Controller * ctrl = new LightingColorController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } -} - -void BSLightingShaderProperty::setShaderType( unsigned int t ) -{ - shaderType = ShaderFlags::ShaderType( t ); -} - -ShaderFlags::ShaderType BSLightingShaderProperty::getShaderType() -{ - return shaderType; -} - -void BSLightingShaderProperty::setEmissive( const Color3 & color, float mult ) -{ - emissiveColor = color; - emissiveMult = mult; -} - -void BSLightingShaderProperty::setSpecular( const Color3 & color, float gloss, float strength ) -{ - specularColor = color; - specularGloss = gloss; - specularStrength = strength; -} - -Color3 BSLightingShaderProperty::getEmissiveColor() const -{ - return emissiveColor; -} - -Color3 BSLightingShaderProperty::getSpecularColor() const -{ - return specularColor; -} - -float BSLightingShaderProperty::getEmissiveMult() const -{ - return emissiveMult; -} + lightingEffect1 = m->fSubsurfaceLightingRolloff; -float BSLightingShaderProperty::getLightingEffect1() const -{ - return lightingEffect1; -} - -float BSLightingShaderProperty::getLightingEffect2() const -{ - return lightingEffect2; -} + } else { // m == nullptr -void BSLightingShaderProperty::setLightingEffect1( float val ) -{ - lightingEffect1 = val; -} + auto textures = nif->getArray(iTextureSet, "Textures"); -void BSLightingShaderProperty::setLightingEffect2( float val ) -{ - lightingEffect2 = val; -} - -float BSLightingShaderProperty::getSpecularGloss() const -{ - return specularGloss; -} + isDoubleSided = hasSF2( ShaderFlags::SLSF2_Double_Sided ); + depthTest = hasSF1( ShaderFlags::SLSF1_ZBuffer_Test ); + depthWrite = hasSF2( ShaderFlags::SLSF2_ZBuffer_Write ); -float BSLightingShaderProperty::getSpecularStrength() const -{ - return specularStrength; -} + alpha = nif->get( iBlock, "Alpha" ); -float BSLightingShaderProperty::getInnerThickness() const -{ - return innerThickness; -} + uvScale.set( nif->get(iBlock, "UV Scale") ); + uvOffset.set( nif->get(iBlock, "UV Offset") ); + clampMode = TexClampMode( nif->get( iBlock, "Texture Clamp Mode" ) ); -UVScale BSLightingShaderProperty::getInnerTextureScale() const -{ - return innerTextureScale; -} - -float BSLightingShaderProperty::getOuterRefractionStrength() const -{ - return outerRefractionStrength; -} + // Specular + if ( hasSF1( ShaderFlags::SLSF1_Specular ) ) { + specularColor = nif->get(iBlock, "Specular Color"); + specularGloss = nif->get( iBlock, "Glossiness" ); + if ( specularGloss == 0.0f ) // FO4 + specularGloss = nif->get( iBlock, "Smoothness" ); + specularStrength = nif->get(iBlock, "Specular Strength"); + } -float BSLightingShaderProperty::getOuterReflectionStrength() const -{ - return outerReflectionStrength; -} + // Emissive + emissiveColor = nif->get( iBlock, "Emissive Color" ); + emissiveMult = nif->get( iBlock, "Emissive Multiple" ); -void BSLightingShaderProperty::setInnerThickness( float thickness ) -{ - innerThickness = thickness; -} + hasEmittance = hasSF1( ShaderFlags::SLSF1_Own_Emit ); + hasGlowMap = isST(ShaderFlags::ST_GlowShader) && hasSF2( ShaderFlags::SLSF2_Glow_Map ) && !textures.value( 2, "" ).isEmpty(); -void BSLightingShaderProperty::setInnerTextureScale( float x, float y ) -{ - innerTextureScale.x = x; - innerTextureScale.y = y; -} + // Version Dependent settings + if ( nif->getBSVersion() < 130 ) { + lightingEffect1 = nif->get( iBlock, "Lighting Effect 1" ); + lightingEffect2 = nif->get( iBlock, "Lighting Effect 2" ); -void BSLightingShaderProperty::setOuterRefractionStrength( float strength ) -{ - outerRefractionStrength = strength; -} + innerThickness = nif->get( iBlock, "Parallax Inner Layer Thickness" ); + outerRefractionStrength = nif->get( iBlock, "Parallax Refraction Scale" ); + outerReflectionStrength = nif->get( iBlock, "Parallax Envmap Strength" ); + innerTextureScale.set( nif->get(iBlock, "Parallax Inner Layer Texture Scale") ); -void BSLightingShaderProperty::setOuterReflectionStrength( float strength ) -{ - outerReflectionStrength = strength; -} + hasSpecularMap = hasSF1( ShaderFlags::SLSF1_Specular ) && !textures.value( 7, "" ).isEmpty(); + hasHeightMap = isST( ShaderFlags::ST_Heightmap ) && hasSF1( ShaderFlags::SLSF1_Parallax ) && !textures.value( 3, "" ).isEmpty(); + hasBacklight = hasSF2( ShaderFlags::SLSF2_Back_Lighting ); + hasRimlight = hasSF2( ShaderFlags::SLSF2_Rim_Lighting ); + hasSoftlight = hasSF2( ShaderFlags::SLSF2_Soft_Lighting ); + hasMultiLayerParallax = hasSF2( ShaderFlags::SLSF2_Multi_Layer_Parallax ); + hasRefraction = hasSF1( ShaderFlags::SLSF1_Refraction ); -float BSLightingShaderProperty::getEnvironmentReflection() const -{ - return environmentReflection; -} + hasTintMask = isST( ShaderFlags::ST_FaceTint ); + hasDetailMask = hasTintMask; -void BSLightingShaderProperty::setEnvironmentReflection( float strength ) -{ - environmentReflection = strength; -} + if ( isST( ShaderFlags::ST_HairTint ) ) + setTintColor( nif, "Hair Tint Color" ); + else if ( isST( ShaderFlags::ST_SkinTint ) ) + setTintColor( nif, "Skin Tint Color" ); + } else { + hasSpecularMap = hasSF1( ShaderFlags::SLSF1_Specular ); + greyscaleColor = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteColor ); + paletteScale = nif->get( iBlock, "Grayscale to Palette Scale" ); + lightingEffect1 = nif->get( iBlock, "Subsurface Rolloff" ); + backlightPower = nif->get( iBlock, "Backlight Power" ); + fresnelPower = nif->get( iBlock, "Fresnel Power" ); + } -float BSLightingShaderProperty::getAlpha() const -{ - return alpha; -} + // Environment Map, Mask and Reflection Scale + hasEnvironmentMap = + ( isST(ShaderFlags::ST_EnvironmentMap) && hasSF1(ShaderFlags::SLSF1_Environment_Mapping) ) + || ( isST(ShaderFlags::ST_EyeEnvmap) && hasSF1(ShaderFlags::SLSF1_Eye_Environment_Mapping) ) + || ( nif->getBSVersion() == 100 && hasMultiLayerParallax ); + + useEnvironmentMask = hasEnvironmentMap && !textures.value( 5, "" ).isEmpty(); -void BSLightingShaderProperty::setAlpha( float opacity ) -{ - alpha = opacity; + if ( isST( ShaderFlags::ST_EnvironmentMap ) ) + environmentReflection = nif->get( iBlock, "Environment Map Scale" ); + else if ( isST( ShaderFlags::ST_EyeEnvmap ) ) + environmentReflection = nif->get( iBlock, "Eye Cubemap Scale" ); + } } -Color3 BSLightingShaderProperty::getTintColor() const +void BSLightingShaderProperty::setController( const NifModel * nif, const QModelIndex & iController ) { - return tintColor; + auto contrName = nif->itemName(iController); + if ( contrName == "BSLightingShaderPropertyFloatController" ) { + Controller * ctrl = new LightingFloatController( this, iController ); + registerController(nif, ctrl); + } else if ( contrName == "BSLightingShaderPropertyColorController" ) { + Controller * ctrl = new LightingColorController( this, iController ); + registerController(nif, ctrl); + } } -void BSLightingShaderProperty::setTintColor( const Color3 & c ) +void BSLightingShaderProperty::setTintColor( const NifModel* nif, const QString & itemName ) { - tintColor = c; + hasTintColor = true; + tintColor = nif->get(iBlock, itemName); } /* BSEffectShaderProperty */ -void BSEffectShaderProperty::update( const NifModel * nif, const QModelIndex & property ) +void BSEffectShaderProperty::updateImpl( const NifModel * nif, const QModelIndex & index ) { - BSShaderLightingProperty::update( nif, property ); + BSShaderLightingProperty::updateImpl( nif, index ); - if ( name.endsWith( ".bgem", Qt::CaseInsensitive ) ) - material = new EffectMaterial( name, scene->game); - - if ( material && !material->isValid() ) - material = nullptr; - - if ( material && name.isEmpty() ) { - delete material; - material = nullptr; + if ( index == iBlock ) { + setMaterial(name.endsWith(".bgem", Qt::CaseInsensitive) ? new EffectMaterial(name, scene->game) : nullptr); + updateParams(nif); } + else if ( index == iTextureSet ) + updateParams(nif); } -void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelIndex & prop ) +void BSEffectShaderProperty::resetParams() { - EffectMaterial * m = nullptr; - if ( mat() && mat()->isValid() ) - m = static_cast(mat()); - - stream = nif->getUserVersion2(); - - setFlags1( nif, prop ); - setFlags2( nif, prop ); - - hasVertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); - hasVertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); - - if ( !m ) { - setEmissive( nif->get( prop, "Base Color" ), nif->get( prop, "Base Color Scale" ) ); - - hasSourceTexture = !nif->get( prop, "Source Texture" ).isEmpty(); - hasGreyscaleMap = !nif->get( prop, "Greyscale Texture" ).isEmpty(); - - greyscaleAlpha = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteAlpha ); - greyscaleColor = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteColor ); - useFalloff = hasSF1( ShaderFlags::SLSF1_Use_Falloff ); + BSShaderLightingProperty::resetParams(); - depthTest = hasSF1( ShaderFlags::SLSF1_ZBuffer_Test ); - depthWrite = hasSF2( ShaderFlags::SLSF2_ZBuffer_Write ); - isDoubleSided = hasSF2( ShaderFlags::SLSF2_Double_Sided ); - - if ( stream < 130 ) { - hasWeaponBlood = hasSF2( ShaderFlags::SLSF2_Weapon_Blood ); - } else { - hasEnvMap = !nif->get( prop, "Env Map Texture" ).isEmpty(); - hasNormalMap = !nif->get( prop, "Normal Texture" ).isEmpty(); - hasEnvMask = !nif->get( prop, "Env Mask Texture" ).isEmpty(); + hasSourceTexture = false; + hasGreyscaleMap = false; + hasEnvironmentMap = false; + hasEnvironmentMask = false; + hasNormalMap = false; + useFalloff = false; + hasRGBFalloff = false; - environmentReflection = nif->get( prop, "Environment Map Scale" ); + greyscaleColor = false; + greyscaleAlpha = false; - // Receive Shadows -> RGB Falloff for FO4 - hasRGBFalloff = hasSF1( ShaderFlags::SF1( 1 << 8 ) ); - } + hasWeaponBlood = false; - auto scale = nif->get( prop, "UV Scale" ); - auto offset = nif->get( prop, "UV Offset" ); + falloff.startAngle = 1.0f; + falloff.stopAngle = 0.0f; + falloff.startOpacity = 1.0f; + falloff.stopOpacity = 0.0f; + falloff.softDepth = 1.0f; - setUvScale( scale[0], scale[1] ); - setUvOffset( offset[0], offset[1] ); - setClampMode( nif->get( prop, "Texture Clamp Mode" ) ); + lumEmittance = 0.0; - if ( hasSF2( ShaderFlags::SLSF2_Effect_Lighting ) ) - lightingInfluence = (float)nif->get( prop, "Lighting Influence" ) / 255.0; + emissiveColor = Color4(0, 0, 0, 0); + emissiveMult = 1.0; - auto startA = nif->get( prop, "Falloff Start Angle" ); - auto stopA = nif->get( prop, "Falloff Stop Angle" ); - auto startO = nif->get( prop, "Falloff Start Opacity" ); - auto stopO = nif->get( prop, "Falloff Stop Opacity" ); - auto soft = nif->get( prop, "Soft Falloff Depth" ); + lightingInfluence = 0.0; + environmentReflection = 0.0; +} - setFalloff( startA, stopA, startO, stopO, soft ); +void BSEffectShaderProperty::updateParams( const NifModel * nif ) +{ + resetParams(); - } else { + setFlags1( nif ); + setFlags2( nif ); - setEmissive( Color4( m->cBaseColor, m->fAlpha ), m->fBaseColorScale ); + hasVertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); + hasVertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); + isVertexAlphaAnimation = hasSF2(ShaderFlags::SLSF2_Tree_Anim); + EffectMaterial * m = ( material && material->isValid() ) ? static_cast(material) : nullptr; + if ( m ) { hasSourceTexture = !m->textureList[0].isEmpty(); hasGreyscaleMap = !m->textureList[1].isEmpty(); - hasEnvMap = !m->textureList[2].isEmpty(); + hasEnvironmentMap = !m->textureList[2].isEmpty(); hasNormalMap = !m->textureList[3].isEmpty(); - hasEnvMask = !m->textureList[4].isEmpty(); + hasEnvironmentMask = !m->textureList[4].isEmpty(); environmentReflection = m->fEnvironmentMappingMaskScale; @@ -1619,8 +1469,8 @@ void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelInd lumEmittance = m->fLumEmittance; - setUvScale( m->fUScale, m->fVScale ); - setUvOffset( m->fUOffset, m->fVOffset ); + uvScale.set(m->fUScale, m->fVScale); + uvOffset.set(m->fUOffset, m->fVOffset); if ( m->bTileU && m->bTileV ) clampMode = TexClampMode::WRAP_S_WRAP_T; @@ -1631,75 +1481,72 @@ void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelInd else clampMode = TexClampMode::CLAMP_S_CLAMP_T; + emissiveColor = Color4(m->cBaseColor, m->fAlpha); + emissiveMult = m->fBaseColorScale; + if ( m->bEffectLightingEnabled ) lightingInfluence = m->fLightingInfluence; - setFalloff( m->fFalloffStartAngle, m->fFalloffStopAngle, - m->fFalloffStartOpacity, m->fFalloffStopOpacity, m->fSoftDepth ); - } -} + falloff.startAngle = m->fFalloffStartAngle; + falloff.stopAngle = m->fFalloffStopAngle; + falloff.startOpacity = m->fFalloffStartOpacity; + falloff.stopOpacity = m->fFalloffStopOpacity; + falloff.softDepth = m->fSoftDepth; -void BSEffectShaderProperty::setController( const NifModel * nif, const QModelIndex & iController ) -{ - if ( nif->itemName( iController ) == "BSEffectShaderPropertyFloatController" ) { - Controller * ctrl = new EffectFloatController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } else if ( nif->itemName( iController ) == "BSEffectShaderPropertyColorController" ) { - Controller * ctrl = new EffectColorController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } -} + } else { // m == nullptr + + hasSourceTexture = !nif->get( iBlock, "Source Texture" ).isEmpty(); + hasGreyscaleMap = !nif->get( iBlock, "Greyscale Texture" ).isEmpty(); -void BSEffectShaderProperty::setEmissive( const Color4 & color, float mult ) -{ - emissiveColor = color; - emissiveMult = mult; -} + greyscaleAlpha = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteAlpha ); + greyscaleColor = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteColor ); + useFalloff = hasSF1( ShaderFlags::SLSF1_Use_Falloff ); -Color4 BSEffectShaderProperty::getEmissiveColor() const -{ - return emissiveColor; -} + depthTest = hasSF1( ShaderFlags::SLSF1_ZBuffer_Test ); + depthWrite = hasSF2( ShaderFlags::SLSF2_ZBuffer_Write ); + isDoubleSided = hasSF2( ShaderFlags::SLSF2_Double_Sided ); -float BSEffectShaderProperty::getEmissiveMult() const -{ - return emissiveMult; -} + if ( nif->getBSVersion() < 130 ) { + hasWeaponBlood = hasSF2( ShaderFlags::SLSF2_Weapon_Blood ); + } else { + hasEnvironmentMap = !nif->get( iBlock, "Env Map Texture" ).isEmpty(); + hasEnvironmentMask = !nif->get(iBlock, "Env Mask Texture").isEmpty(); + hasNormalMap = !nif->get( iBlock, "Normal Texture" ).isEmpty(); -float BSEffectShaderProperty::getAlpha() const -{ - return emissiveColor.alpha(); -} + environmentReflection = nif->get( iBlock, "Environment Map Scale" ); -void BSEffectShaderProperty::setFalloff( float startA, float stopA, float startO, float stopO, float soft ) -{ - falloff.startAngle = startA; - falloff.stopAngle = stopA; - falloff.startOpacity = startO; - falloff.stopOpacity = stopO; - falloff.softDepth = soft; -} + // Receive Shadows -> RGB Falloff for FO4 + hasRGBFalloff = hasSF1( ShaderFlags::SF1( 1 << 8 ) ); + } -float BSEffectShaderProperty::getEnvironmentReflection() const -{ - return environmentReflection; -} + uvScale.set( nif->get(iBlock, "UV Scale") ); + uvOffset.set( nif->get(iBlock, "UV Offset") ); + clampMode = TexClampMode( nif->get( iBlock, "Texture Clamp Mode" ) ); -void BSEffectShaderProperty::setEnvironmentReflection( float strength ) -{ - environmentReflection = strength; -} + emissiveColor = nif->get(iBlock, "Base Color"); + emissiveMult = nif->get(iBlock, "Base Color Scale"); -float BSEffectShaderProperty::getLightingInfluence() const -{ - return lightingInfluence; + if ( hasSF2( ShaderFlags::SLSF2_Effect_Lighting ) ) + lightingInfluence = (float)nif->get( iBlock, "Lighting Influence" ) / 255.0; + + falloff.startAngle = nif->get( iBlock, "Falloff Start Angle" ); + falloff.stopAngle = nif->get( iBlock, "Falloff Stop Angle" ); + falloff.startOpacity = nif->get( iBlock, "Falloff Start Opacity" ); + falloff.stopOpacity = nif->get( iBlock, "Falloff Stop Opacity" ); + falloff.softDepth = nif->get( iBlock, "Soft Falloff Depth" ); + } } -void BSEffectShaderProperty::setLightingInfluence( float strength ) +void BSEffectShaderProperty::setController( const NifModel * nif, const QModelIndex & iController ) { - lightingInfluence = strength; + auto contrName = nif->itemName(iController); + if ( contrName == "BSEffectShaderPropertyFloatController" ) { + Controller * ctrl = new EffectFloatController( this, iController ); + registerController(nif, ctrl); + } else if ( contrName == "BSEffectShaderPropertyColorController" ) { + Controller * ctrl = new EffectColorController( this, iController ); + registerController(nif, ctrl); + } } /* diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index e12330066..2f2e23c51 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -148,23 +148,19 @@ class AlphaProperty final : public Property Type type() const override final { return Alpha; } QString typeId() const override final { return "NiAlphaProperty"; } - void update( const NifModel * nif, const QModelIndex & block ) override final; - bool blend() const { return alphaBlend; } bool test() const { return alphaTest; } - bool sort() const { return alphaSort; } - float threshold() const { return alphaThreshold; } - void setThreshold( float ); + GLfloat alphaThreshold = 0; friend void glProperty( AlphaProperty * ); protected: void setController( const NifModel * nif, const QModelIndex & controller ) override final; + void updateImpl( const NifModel * nif, const QModelIndex & block ) override final; bool alphaBlend = false, alphaTest = false, alphaSort = false; GLenum alphaSrc = 0, alphaDst = 0, alphaFunc = 0; - GLfloat alphaThreshold = 0; }; REGISTER_PROPERTY( AlphaProperty, Alpha ) @@ -178,8 +174,6 @@ class ZBufferProperty final : public Property Type type() const override final { return ZBuffer; } QString typeId() const override final { return "NiZBufferProperty"; } - void update( const NifModel * nif, const QModelIndex & block ) override final; - bool test() const { return depthTest; } bool mask() const { return depthMask; } @@ -189,6 +183,8 @@ class ZBufferProperty final : public Property bool depthTest = false; bool depthMask = false; GLenum depthFunc = 0; + + void updateImpl( const NifModel * nif, const QModelIndex & block ) override final; }; REGISTER_PROPERTY( ZBufferProperty, ZBuffer ) @@ -225,8 +221,6 @@ class TexturingProperty final : public Property Type type() const override final { return Texturing; } QString typeId() const override final { return "NiTexturingProperty"; } - void update( const NifModel * nif, const QModelIndex & block ) override final; - friend void glProperty( TexturingProperty * ); bool bind( int id, const QString & fname = QString() ); @@ -243,6 +237,7 @@ class TexturingProperty final : public Property TexDesc textures[numTextures]; void setController( const NifModel * nif, const QModelIndex & controller ) override final; + void updateImpl( const NifModel * nif, const QModelIndex & block ) override final; }; REGISTER_PROPERTY( TexturingProperty, Texturing ) @@ -258,8 +253,6 @@ class TextureProperty final : public Property Type type() const override final { return Texture; } QString typeId() const override final { return "NiTextureProperty"; } - void update( const NifModel * nif, const QModelIndex & block ) override final; - friend void glProperty( TextureProperty * ); bool bind(); @@ -271,6 +264,7 @@ class TextureProperty final : public Property QPersistentModelIndex iImage; void setController( const NifModel * nif, const QModelIndex & controller ) override final; + void updateImpl( const NifModel * nif, const QModelIndex & block ) override final; }; REGISTER_PROPERTY( TextureProperty, Texture ) @@ -287,8 +281,6 @@ class MaterialProperty final : public Property Type type() const override final { return MaterialProp; } QString typeId() const override final { return "NiMaterialProperty"; } - void update( const NifModel * nif, const QModelIndex & block ) override final; - friend void glProperty( class MaterialProperty *, class SpecularProperty * ); GLfloat alphaValue() const { return alpha; } @@ -296,9 +288,9 @@ class MaterialProperty final : public Property protected: Color4 ambient, diffuse, specular, emissive; GLfloat shininess = 0, alpha = 0; - bool overridden = false; void setController( const NifModel * nif, const QModelIndex & controller ) override final; + void updateImpl( const NifModel * nif, const QModelIndex & block ) override final; }; REGISTER_PROPERTY( MaterialProperty, MaterialProp ) @@ -312,12 +304,12 @@ class SpecularProperty final : public Property Type type() const override final { return Specular; } QString typeId() const override final { return "NiSpecularProperty"; } - void update( const NifModel * nif, const QModelIndex & index ) override final; - friend void glProperty( class MaterialProperty *, class SpecularProperty * ); protected: bool spec = false; + + void updateImpl( const NifModel * nif, const QModelIndex & index ) override final; }; REGISTER_PROPERTY( SpecularProperty, Specular ) @@ -331,12 +323,12 @@ class WireframeProperty final : public Property Type type() const override final { return Wireframe; } QString typeId() const override final { return "NiWireframeProperty"; } - void update( const NifModel * nif, const QModelIndex & index ) override final; - friend void glProperty( WireframeProperty * ); protected: bool wire = false; + + void updateImpl( const NifModel * nif, const QModelIndex & index ) override final; }; REGISTER_PROPERTY( WireframeProperty, Wireframe ) @@ -350,13 +342,13 @@ class VertexColorProperty final : public Property Type type() const override final { return VertexColor; } QString typeId() const override final { return "NiVertexColorProperty"; } - void update( const NifModel * nif, const QModelIndex & index ) override final; - friend void glProperty( VertexColorProperty *, bool vertexcolors ); protected: int lightmode = 0; int vertexmode = 0; + + void updateImpl( const NifModel * nif, const QModelIndex & index ) override final; }; REGISTER_PROPERTY( VertexColorProperty, VertexColor ) @@ -406,8 +398,6 @@ class StencilProperty final : public Property Type type() const override final { return Stencil; } QString typeId() const override final { return "NiStencilProperty"; } - void update( const NifModel * nif, const QModelIndex & index ) override final; - friend void glProperty( StencilProperty * ); protected: @@ -438,6 +428,8 @@ class StencilProperty final : public Property bool cullEnable = false; GLenum cullMode = 0; + + void updateImpl( const NifModel * nif, const QModelIndex & index ) override final; }; REGISTER_PROPERTY( StencilProperty, Stencil ) @@ -634,14 +626,24 @@ enum TexClampMode : unsigned int struct UVScale { - float x = 1.0f; - float y = 1.0f; + float x; + float y; + + UVScale() { reset(); } + void reset() { x = y = 1.0f; } + void set( float _x, float _y ) { x = _x; y = _y; } + void set( const Vector2 & v) { x = v[0]; y = v[1]; } }; struct UVOffset { - float x = 0.0f; - float y = 0.0f; + float x; + float y; + + UVOffset() { reset(); } + void reset() { x = y = 0.0f; } + void set(float _x, float _y) { x = _x; y = _y; } + void set(const Vector2 & v) { x = v[0]; y = v[1]; } }; @@ -649,56 +651,50 @@ struct UVOffset class BSShaderLightingProperty : public Property { public: - BSShaderLightingProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + BSShaderLightingProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) { } + ~BSShaderLightingProperty(); Type type() const override final { return ShaderLighting; } QString typeId() const override { return "BSShaderLightingProperty"; } - void update( const NifModel * nif, const QModelIndex & block ) override; - friend void glProperty( BSShaderLightingProperty * ); + void clear() override; + bool bind( int id, const QString & fname = QString(), TexClampMode mode = TexClampMode::WRAP_S_WRAP_T ); bool bind( int id, const QVector > & texcoords ); - bool bind( int id, const QVector > & texcoords, int stage ); bool bindCube( int id, const QString & fname = QString() ); + //! Checks if the params of the shader depend on data from block + bool isParamBlock( const QModelIndex & block ) { return ( block == iBlock || block == iTextureSet ); } + QString fileName( int id ) const; //int coordSet( int id ) const; static int getId( const QString & id ); - QPersistentModelIndex getTextureSet() const; - - bool hasSF1( ShaderFlags::SF1 flag ) { return getFlags1() & flag; }; - bool hasSF2( ShaderFlags::SF2 flag ) { return getFlags2() & flag; }; - - unsigned int getFlags1() const; - unsigned int getFlags2() const; - - void setFlags1( const NifModel * nif, const QModelIndex & prop ); - void setFlags2( const NifModel * nif, const QModelIndex & prop ); + //! Has Shader Flag 1 + bool hasSF1( ShaderFlags::SF1 flag ) { return flags1 & flag; }; + //! Has Shader Flag 2 + bool hasSF2( ShaderFlags::SF2 flag ) { return flags2 & flag; }; - UVScale getUvScale() const; - UVOffset getUvOffset() const; + void setFlags1( const NifModel * nif ); + void setFlags2( const NifModel * nif ); - void setUvScale( float, float ); - void setUvOffset( float, float ); - - TexClampMode getClampMode() const; - - void setClampMode( uint mode ); + bool hasVertexColors = false; + bool hasVertexAlpha = false; // TODO Gavrant: it's unused - bool getDepthTest() { return depthTest; } - bool getDepthWrite() { return depthWrite; } - bool getIsDoubleSided() { return isDoubleSided; } - bool getIsTranslucent() { return isTranslucent; } + bool depthTest = false; + bool depthWrite = false; + bool isDoubleSided = false; + bool isVertexAlphaAnimation = false; - bool hasVertexColors = false; - bool hasVertexAlpha = false; + UVScale uvScale; + UVOffset uvOffset; + TexClampMode clampMode = CLAMP_S_CLAMP_T; - Material * mat() const; + Material * getMaterial() const { return material; } protected: ShaderFlags::SF1 flags1 = ShaderFlags::SLSF1_ZBuffer_Test; @@ -706,130 +702,56 @@ class BSShaderLightingProperty : public Property //QVector textures; QPersistentModelIndex iTextureSet; - QPersistentModelIndex iSourceTexture; QPersistentModelIndex iWetMaterial; Material * material = nullptr; + void setMaterial( Material * newMaterial ); - UVScale uvScale; - UVOffset uvOffset; - - TexClampMode clampMode = CLAMP_S_CLAMP_T; - - bool depthTest = false; - bool depthWrite = false; - bool isDoubleSided = false; - bool isTranslucent = false; - - quint32 stream = 83; + void updateImpl( const NifModel * nif, const QModelIndex & block ) override; + virtual void resetParams(); }; REGISTER_PROPERTY( BSShaderLightingProperty, ShaderLighting ) -//! A Property that inherits BSShaderLightingProperty (Skyrim-specific) +//! A Property that inherits BSLightingShaderProperty (Skyrim-specific) class BSLightingShaderProperty final : public BSShaderLightingProperty { public: - BSLightingShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) - { - emissiveMult = 1.0f; - emissiveColor = Color3( 0, 0, 0 ); - - specularStrength = 1.0f; - specularColor = Color3( 1.0f, 1.0f, 1.0f ); - } + BSLightingShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) { } QString typeId() const override final { return "BSLightingShaderProperty"; } - void update( const NifModel * nif, const QModelIndex & block ) override; - void updateParams( const NifModel * nif, const QModelIndex & prop ); - - bool isST( ShaderFlags::ShaderType st ) { return getShaderType() == st; }; - - Color3 getEmissiveColor() const; - Color3 getSpecularColor() const; - - float getEmissiveMult() const; - - float getSpecularGloss() const; - float getSpecularStrength() const; - - float getLightingEffect1() const; - float getLightingEffect2() const; - - float getInnerThickness() const; - UVScale getInnerTextureScale() const; - float getOuterRefractionStrength() const; - float getOuterReflectionStrength() const; - - float getEnvironmentReflection() const; - - float getAlpha() const; - - void setShaderType( unsigned int ); - - void setEmissive( const Color3 & color, float mult = 1.0f ); - void setSpecular( const Color3 & color, float glossiness = 80.0f, float strength = 1.0f ); - - void setLightingEffect1( float ); - void setLightingEffect2( float ); - - void setInnerThickness( float ); - void setInnerTextureScale( float, float ); - void setOuterRefractionStrength( float ); - void setOuterReflectionStrength( float ); - - void setEnvironmentReflection( float ); - - void setAlpha( float ); - - Color3 getTintColor() const; - void setTintColor( const Color3 & ); + //! Is Shader Type + bool isST( ShaderFlags::ShaderType st ) { return shaderType == st; }; bool hasGlowMap = false; bool hasEmittance = false; bool hasSoftlight = false; bool hasBacklight = false; bool hasRimlight = false; - bool hasModelSpaceNormals = false; bool hasSpecularMap = false; bool hasMultiLayerParallax = false; - bool hasCubeMap = false; bool hasEnvironmentMap = false; bool useEnvironmentMask = false; bool hasHeightMap = false; bool hasRefraction = false; - bool hasFireRefraction = false; bool hasDetailMask = false; bool hasTintMask = false; bool hasTintColor = false; bool greyscaleColor = false; - ShaderFlags::ShaderType getShaderType(); - - float fresnelPower = 5.0; - float paletteScale = 1.0; - float rimPower = 2.0; - float backlightPower = 0.0; - -protected: - void setController( const NifModel * nif, const QModelIndex & controller ) override final; - - ShaderFlags::ShaderType shaderType = ShaderFlags::ST_Default; - Color3 emissiveColor; - Color3 specularColor; - Color3 tintColor; - Color3 subsurfaceColor; - - float alpha = 1.0; - float emissiveMult = 1.0; + Color3 specularColor; float specularGloss = 80.0; float specularStrength = 1.0; + Color3 tintColor; + + float alpha = 1.0; + float lightingEffect1 = 0.0; float lightingEffect2 = 1.0; @@ -840,43 +762,40 @@ class BSLightingShaderProperty final : public BSShaderLightingProperty UVScale innerTextureScale; float outerRefractionStrength = 0.0; float outerReflectionStrength = 1.0; + + float fresnelPower = 5.0; + float paletteScale = 1.0; + float rimPower = 2.0; + float backlightPower = 0.0; + +protected: + void setController( const NifModel * nif, const QModelIndex & controller ) override final; + void updateImpl( const NifModel * nif, const QModelIndex & block ) override; + void resetParams() override; + void updateParams( const NifModel * nif ); + void setTintColor( const NifModel* nif, const QString & itemName ); + + ShaderFlags::ShaderType shaderType = ShaderFlags::ST_Default; }; REGISTER_PROPERTY( BSLightingShaderProperty, ShaderLighting ) -//! A Property that inherits BSShaderLightingProperty (Skyrim-specific) +//! A Property that inherits BSEffectShaderProperty (Skyrim-specific) class BSEffectShaderProperty final : public BSShaderLightingProperty { public: - BSEffectShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) - { - } + BSEffectShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) { } QString typeId() const override final { return "BSEffectShaderProperty"; } - void update( const NifModel * nif, const QModelIndex & block ) override; - void updateParams( const NifModel * nif, const QModelIndex & prop ); - - Color4 getEmissiveColor() const; - float getEmissiveMult() const; - - float getAlpha() const; - - void setEmissive( const Color4 & color, float mult = 1.0f ); - void setFalloff( float, float, float, float, float ); - - float getEnvironmentReflection() const; - void setEnvironmentReflection( float ); - - float getLightingInfluence() const; - void setLightingInfluence( float ); + float getAlpha() const { return emissiveColor.alpha(); } bool hasSourceTexture = false; bool hasGreyscaleMap = false; - bool hasEnvMap = false; + bool hasEnvironmentMap = false; bool hasNormalMap = false; - bool hasEnvMask = false; + bool hasEnvironmentMask = false; bool useFalloff = false; bool hasRGBFalloff = false; @@ -894,21 +813,22 @@ class BSEffectShaderProperty final : public BSShaderLightingProperty float stopOpacity = 0.0f; float softDepth = 1.0f; - }; - Falloff falloff; float lumEmittance = 0.0; -protected: - void setController( const NifModel * nif, const QModelIndex & controller ) override final; - Color4 emissiveColor; float emissiveMult = 1.0; float lightingInfluence = 0.0; float environmentReflection = 0.0; + +protected: + void setController( const NifModel * nif, const QModelIndex & controller ) override final; + void updateImpl( const NifModel * nif, const QModelIndex & block ) override; + void resetParams() override; + void updateParams( const NifModel * nif ); }; REGISTER_PROPERTY( BSEffectShaderProperty, ShaderLighting ) @@ -929,13 +849,11 @@ namespace WaterShaderFlags }; } -//! A Property that inherits BSShaderLightingProperty (Skyrim-specific) +//! A Property that inherits BSWaterShaderProperty (Skyrim-specific) class BSWaterShaderProperty final : public BSShaderLightingProperty { public: - BSWaterShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) - { - } + BSWaterShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) { } QString typeId() const override final { return "BSWaterShaderProperty"; } diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index b448770a4..2aeafd440 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -133,36 +133,29 @@ void Scene::update( const NifModel * nif, const QModelIndex & index ) if ( index.isValid() ) { QModelIndex block = nif->getBlock( index ); - if ( !block.isValid() ) return; - for ( Property * prop : properties.list() ) { + for ( Property * prop : properties.list() ) prop->update( nif, block ); - } - for ( Node * node : nodes.list() ) { + for ( Node * node : nodes.list() ) node->update( nif, block ); - } } else { properties.validate(); nodes.validate(); - for ( Node * n : nodes.list() ) { - n->update( nif, QModelIndex() ); - } + for ( Property * p : properties.list() ) + p->update( nif, p->index() ); - for ( Property * p : properties.list() ) { - p->update( nif, QModelIndex() ); - } + for ( Node * n : nodes.list() ) + n->update( nif, n->index() ); roots.clear(); for ( const auto link : nif->getRootLinks() ) { QModelIndex iBlock = nif->getBlock( link ); - if ( iBlock.isValid() ) { Node * node = getNode( nif, iBlock ); - if ( node ) { node->makeParent( 0 ); roots.add( node ); @@ -215,7 +208,7 @@ void Scene::make( NifModel * nif, bool flushTextures ) if ( !nif ) return; - game = Game::GameManager::get_game(nif->getVersionNumber(), nif->getUserVersion(), nif->getUserVersion2()); + game = Game::GameManager::get_game(nif->getVersionNumber(), nif->getUserVersion(), nif->getBSVersion()); if ( game == Game::FALLOUT_76 ) emit disableSave(); @@ -233,7 +226,7 @@ void Scene::make( NifModel * nif, bool flushTextures ) Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) { - if ( !( nif && iNode.isValid() ) ) + if ( !nif || !iNode.isValid() ) return 0; Node * node = nodes.get( iNode ); @@ -241,22 +234,18 @@ Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) if ( node ) return node; + auto nodeName = nif->itemName(iNode); if ( nif->inherits( iNode, "NiNode" ) ) { - if ( nif->itemName( iNode ) == "NiLODNode" ) + if ( nodeName == "NiLODNode" ) node = new LODNode( this, iNode ); - else if ( nif->itemName( iNode ) == "NiBillboardNode" ) + else if ( nodeName == "NiBillboardNode" ) node = new BillboardNode( this, iNode ); else node = new Node( this, iNode ); - } else if ( nif->itemName( iNode ) == "NiTriShape" - || nif->itemName( iNode ) == "NiTriStrips" - || nif->inherits( iNode, "NiTriBasedGeom" ) ) - { + } else if ( nodeName == "NiTriShape" || nodeName == "NiTriStrips" || nif->inherits( iNode, "NiTriBasedGeom" ) ) { node = new Mesh( this, iNode ); shapes += static_cast(node); - } else if ( nif->checkVersion( 0x14050000, 0 ) - && nif->itemName( iNode ) == "NiMesh" ) - { + } else if ( nif->checkVersion( 0x14050000, 0 ) && nodeName == "NiMesh" ) { node = new Mesh( this, iNode ); } //else if ( nif->inherits( iNode, "AParticleNode" ) || nif->inherits( iNode, "AParticleSystem" ) ) @@ -267,7 +256,7 @@ Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) node = new BSShape( this, iNode ); shapes += static_cast(node); } else if ( nif->inherits( iNode, "NiAVObject" ) ) { - if ( nif->itemName( iNode ) == "BSTreeNode" ) + if ( nodeName == "BSTreeNode" ) node = new Node( this, iNode ); } @@ -282,18 +271,23 @@ Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) Property * Scene::getProperty( const NifModel * nif, const QModelIndex & iProperty ) { Property * prop = properties.get( iProperty ); - if ( prop ) return prop; prop = Property::create( this, nif, iProperty ); - if ( prop ) properties.add( prop ); - return prop; } +Property * Scene::getProperty( const NifModel * nif, const QModelIndex & iParentBlock, const QString & itemName, const QString & mustInherit ) +{ + QModelIndex iPropertyBlock = nif->getBlock( nif->getLink(iParentBlock, itemName) ); + if ( iPropertyBlock.isValid() && nif->inherits(iPropertyBlock, mustInherit) ) + return getProperty( nif, iPropertyBlock ); + return nullptr; +} + void Scene::setSequence( const QString & seqname ) { animGroup = seqname; diff --git a/src/gl/glscene.h b/src/gl/glscene.h index bcc23f015..2066d5b28 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -69,7 +69,6 @@ class Scene final : public QObject void clear( bool flushTextures = true ); void make( NifModel * nif, bool flushTextures = false ); - void make( NifModel * nif, int blockNumber, QStack & nodestack ); void update( const NifModel * nif, const QModelIndex & index ); @@ -91,6 +90,7 @@ class Scene final : public QObject Node * getNode( const NifModel * nif, const QModelIndex & iNode ); Property * getProperty( const NifModel * nif, const QModelIndex & iProperty ); + Property * getProperty( const NifModel * nif, const QModelIndex & iParentBlock, const QString & itemName, const QString & mustInherit ); Game::GameMode game = Game::OTHER; diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index f861dd288..86e61f6b6 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -410,9 +410,8 @@ int TexCache::bind( const QString & fname, Game::GameMode game ) int TexCache::bind( const QModelIndex & iSource, Game::GameMode game ) { - const NifModel * nif = qobject_cast( iSource.model() ); - - if ( nif && iSource.isValid() ) { + auto nif = NifModel::fromValidIndex(iSource); + if ( nif ) { if ( nif->get( iSource, "Use External" ) == 0 ) { QModelIndex iData = nif->getBlock( nif->getLink( iSource, "Pixel Data" ) ); @@ -478,9 +477,9 @@ void TexCache::setNifFolder( const QString & folder ) QString TexCache::info( const QModelIndex & iSource ) { QString temp; - const NifModel * nif = qobject_cast( iSource.model() ); - - if ( nif && iSource.isValid() ) { + + auto nif = NifModel::fromValidIndex(iSource); + if ( nif ) { if ( nif->get( iSource, "Use External" ) == 0 ) { QModelIndex iData = nif->getBlock( nif->getLink( iSource, "Pixel Data" ) ); @@ -524,7 +523,7 @@ bool TexCache::exportFile( const QModelIndex & iSource, QString & filepath ) bool TexCache::importFile( NifModel * nif, const QModelIndex & iSource, QModelIndex & iData ) { - //const NifModel * nif = qobject_cast( iSource.model() ); + // auto nif = NifModel::fromIndex(iSource); if ( nif && iSource.isValid() ) { if ( nif->get( iSource, "Use External" ) == 1 ) { QString filename = nif->get( iSource, "File Name" ); diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 2afbe8cf6..1f04dea7e 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -659,9 +659,9 @@ GLuint texLoadDDS( const QString & filepath, QString & format, GLenum & target, bool texLoad( const QModelIndex & iData, QString & texformat, GLenum & target, GLuint & width, GLuint & height, GLuint & mipmaps, GLuint & id ) { bool ok = false; - const NifModel * nif = qobject_cast( iData.model() ); - - if ( nif && iData.isValid() ) { + + auto nif = NifModel::fromValidIndex(iData); + if ( nif ) { mipmaps = nif->get( iData, "Num Mipmaps" ); QModelIndex iMipmaps = nif->getIndex( iData, "Mipmaps" ); @@ -1203,7 +1203,7 @@ bool texCanLoad( const QString & filepath ) bool texSaveDDS( const QModelIndex & index, const QString & filepath, const GLuint & width, const GLuint & height, const GLuint & mipmaps ) { - const NifModel * nif = qobject_cast( index.model() ); + auto nif = NifModel::fromIndex( index ); quint32 format = nif->get( index, "Pixel Format" ); // can't dump palettised textures yet @@ -1469,7 +1469,7 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, const GLui bool texSaveTGA( const QModelIndex & index, const QString & filepath, const GLuint & width, const GLuint & height ) { Q_UNUSED( index ); - //const NifModel * nif = qobject_cast( index.model() ); + // auto nif = NifModel::fromIndex(index); QString filename = filepath; if ( !filename.toLower().endsWith( ".tga" ) ) diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index bb3559090..91e5ea86f 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -321,17 +321,17 @@ const float hkScale2010 = 1.0 / 1.42875 * 100.0; float bhkScale( const NifModel * nif ) { - return (nif->getUserVersion2() < 47) ? hkScale660 : hkScale2010; + return (nif->getBSVersion() < 47) ? hkScale660 : hkScale2010; } float bhkInvScale( const NifModel * nif ) { - return (nif->getUserVersion2() < 47) ? 1.0 / hkScale660 : 1.0 / hkScale2010; + return (nif->getBSVersion() < 47) ? 1.0 / hkScale660 : 1.0 / hkScale2010; } float bhkScaleMult( const NifModel * nif ) { - return (nif->getUserVersion2() < 47) ? 1.0 : 10.0; + return (nif->getBSVersion() < 47) ? 1.0 : 10.0; } Transform bhkBodyTrans( const NifModel * nif, const QModelIndex & index ) diff --git a/src/gl/icontrollable.h b/src/gl/icontrollable.h index 8627db517..466935947 100644 --- a/src/gl/icontrollable.h +++ b/src/gl/icontrollable.h @@ -61,7 +61,7 @@ class IControllable : public QObject virtual void clear(); - virtual void update( const NifModel * nif, const QModelIndex & index ) = 0; + void update( const NifModel * nif, const QModelIndex & index ); virtual void transform(); @@ -78,12 +78,17 @@ class IControllable : public QObject //! Sets the Controller virtual void setController( const NifModel * nif, const QModelIndex & iController ); + //! Actual implementation of update, with the validation check taken care of by update(...) + virtual void updateImpl( const NifModel * nif, const QModelIndex & index ); + Scene * scene; QPersistentModelIndex iBlock; QList controllers; + void registerController( const NifModel* nif, Controller *ctrl ); + QString name; }; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 36e9cce15..89f0492ad 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -476,10 +476,14 @@ QString Renderer::setupProgram( Shape * mesh, const QString & hint ) PropertyList props; mesh->activeProperties( props ); - if ( !shader_ready || hint.isNull() + auto nif = NifModel::fromValidIndex(mesh->index()); + if ( !shader_ready + || hint.isNull() || mesh->scene->hasOption(Scene::DisableShaders) || mesh->scene->hasVisMode(Scene::VisSilhouette) - || (mesh->nifVersion == 0) ) { + || !nif + || (nif->getBSVersion() == 0) + ) { setupFixedFunction( mesh, props ); return {}; } @@ -607,11 +611,7 @@ static QString cube = "shaders/cubemap.dds"; bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & props, const QVector & iBlocks, bool eval ) { - auto meshidx = mesh->index(); - if ( !meshidx.isValid() ) - return false; - - const NifModel * nif = qobject_cast(meshidx.model()); + auto nif = NifModel::fromValidIndex(mesh->index()); if ( !nif ) return false; @@ -620,33 +620,35 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & fn->glUseProgram( prog->id ); + auto nifVersion = nif->getBSVersion(); auto scene = mesh->scene; auto lsp = mesh->bslsp; auto esp = mesh->bsesp; Material * mat = nullptr; if ( lsp ) - mat = lsp->mat(); - if ( esp && !mat ) - mat = esp->mat(); + mat = lsp->getMaterial(); + else if ( esp ) + mat = esp->getMaterial(); - QString default_n = (mesh->nifVersion == 155) ? ::default_ns : ::default_n; + QString default_n = (nifVersion == 155) ? ::default_ns : ::default_n; // texturing TexturingProperty * texprop = props.get(); - BSShaderLightingProperty * bsprop = props.get(); + BSShaderLightingProperty * bsprop = mesh->bssp; + // BSShaderLightingProperty * bsprop = props.get(); // TODO: BSLSP has been split off from BSShaderLightingProperty so it needs // to be accessible from here TexClampMode clamp = TexClampMode::WRAP_S_WRAP_T; if ( lsp ) - clamp = lsp->getClampMode(); + clamp = lsp->clampMode; int texunit = 0; if ( bsprop ) { QString forced; - if ( scene->hasOption(Scene::DoLighting) && scene->hasVisMode(Scene::VisNormalsOnly) ) + if ( scene->hasOption(Scene::DoLighting) && scene->hasVisMode(Scene::VisNormalsOnly) ) forced = white; QString alt = white; @@ -724,16 +726,13 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // BSLightingShaderProperty if ( lsp ) { - prog->uni1f( LIGHT_EFF1, lsp->getLightingEffect1() ); - prog->uni1f( LIGHT_EFF2, lsp->getLightingEffect2() ); + prog->uni1f( LIGHT_EFF1, lsp->lightingEffect1 ); + prog->uni1f( LIGHT_EFF2, lsp->lightingEffect2 ); - prog->uni1f( ALPHA, lsp->getAlpha() ); + prog->uni1f( ALPHA, lsp->alpha ); - auto uvS = lsp->getUvScale(); - prog->uni2f( UV_SCALE, uvS.x, uvS.y ); - - auto uvO = lsp->getUvOffset(); - prog->uni2f( UV_OFFSET, uvO.x, uvO.y ); + prog->uni2f( UV_SCALE, lsp->uvScale.x, lsp->uvScale.y ); + prog->uni2f( UV_OFFSET, lsp->uvOffset.x, lsp->uvOffset.y ); prog->uni4m( MAT_VIEW, mesh->viewTrans().toMatrix4() ); prog->uni4m( MAT_WORLD, mesh->worldTrans().toMatrix4() ); @@ -743,8 +742,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & prog->uni1i( HAS_TINT_COLOR, lsp->hasTintColor ); if ( lsp->hasTintColor ) { - auto tC = lsp->getTintColor(); - prog->uni3f( TINT_COLOR, tC.red(), tC.green(), tC.blue() ); + prog->uni3f( TINT_COLOR, lsp->tintColor.red(), lsp->tintColor.green(), lsp->tintColor.blue() ); } prog->uni1i( HAS_MAP_DETAIL, lsp->hasDetailMask ); @@ -768,40 +766,35 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // Glow params - if ( scene->hasOption(Scene::DoGlow) && scene->hasOption(Scene::DoLighting) && (lsp->hasEmittance || mesh->nifVersion == 155) ) - prog->uni1f( GLOW_MULT, lsp->getEmissiveMult() ); + if ( scene->hasOption(Scene::DoGlow) && scene->hasOption(Scene::DoLighting) && (lsp->hasEmittance || nifVersion == 155) ) + prog->uni1f( GLOW_MULT, lsp->emissiveMult ); else prog->uni1f( GLOW_MULT, 0 ); prog->uni1i( HAS_EMIT, lsp->hasEmittance ); prog->uni1i( HAS_MAP_GLOW, lsp->hasGlowMap ); - auto emC = lsp->getEmissiveColor(); - prog->uni3f( GLOW_COLOR, emC.red(), emC.green(), emC.blue() ); + prog->uni3f( GLOW_COLOR, lsp->emissiveColor.red(), lsp->emissiveColor.green(), lsp->emissiveColor.blue() ); // Specular params - float s = (scene->hasOption(Scene::DoSpecular) && scene->hasOption(Scene::DoLighting)) ? lsp->getSpecularStrength() : 0.0; + float s = ( scene->hasOption(Scene::DoSpecular) && scene->hasOption(Scene::DoLighting) ) ? lsp->specularStrength : 0.0; prog->uni1f( SPEC_SCALE, s ); // Assure specular power does not break the shaders - auto gloss = lsp->getSpecularGloss(); - prog->uni1f( SPEC_GLOSS, gloss ); - - auto spec = lsp->getSpecularColor(); - prog->uni3f( SPEC_COLOR, spec.red(), spec.green(), spec.blue() ); - + prog->uni1f( SPEC_GLOSS, lsp->specularGloss); + prog->uni3f( SPEC_COLOR, lsp->specularColor.red(), lsp->specularColor.green(), lsp->specularColor.blue() ); prog->uni1i( HAS_MAP_SPEC, lsp->hasSpecularMap ); - if ( mesh->nifVersion <= 130 ) { - if ( mesh->nifVersion == 130 || (lsp->hasSpecularMap && !lsp->hasBacklight) ) + if ( nifVersion <= 130 ) { + if ( nifVersion == 130 || (lsp->hasSpecularMap && !lsp->hasBacklight) ) prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, white, clamp ); else prog->uniSampler( bsprop, SAMP_SPECULAR, 7, texunit, black, clamp ); } - if ( mesh->nifVersion >= 130 ) { - prog->uni1i( DOUBLE_SIDE, lsp->getIsDoubleSided() ); + if ( nifVersion >= 130 ) { + prog->uni1i( DOUBLE_SIDE, lsp->isDoubleSided ); prog->uni1f( G2P_SCALE, lsp->paletteScale ); - prog->uni1f( SS_ROLLOFF, lsp->getLightingEffect1() ); + prog->uni1f( SS_ROLLOFF, lsp->lightingEffect1 ); prog->uni1f( POW_FRESNEL, lsp->fresnelPower ); prog->uni1f( POW_RIM, lsp->rimPower ); prog->uni1f( POW_BACK, lsp->backlightPower ); @@ -811,14 +804,11 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & prog->uniSampler( bsprop, SAMP_INNER, 6, texunit, default_n, clamp ); if ( lsp->hasMultiLayerParallax ) { + prog->uni2f( INNER_SCALE, lsp->innerTextureScale.x, lsp->innerTextureScale.y ); + prog->uni1f( INNER_THICK, lsp->innerThickness ); - auto inS = lsp->getInnerTextureScale(); - prog->uni2f( INNER_SCALE, inS.x, inS.y ); - - prog->uni1f( INNER_THICK, lsp->getInnerThickness() ); - - prog->uni1f( OUTER_REFR, lsp->getOuterRefractionStrength() ); - prog->uni1f( OUTER_REFL, lsp->getOuterReflectionStrength() ); + prog->uni1f( OUTER_REFR, lsp->outerRefractionStrength ); + prog->uni1f( OUTER_REFL, lsp->outerReflectionStrength ); } // Environment Mapping @@ -827,7 +817,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & prog->uni1i( HAS_MASK_ENV, lsp->useEnvironmentMask ); float refl = 0.0; if ( lsp->hasEnvironmentMap && scene->hasOption(Scene::DoCubeMapping) && scene->hasOption(Scene::DoLighting) ) - refl = lsp->getEnvironmentReflection(); + refl = lsp->environmentReflection; prog->uni1f( ENV_REFLECTION, refl ); @@ -847,7 +837,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // Always bind mask regardless of shader settings prog->uniSampler( bsprop, SAMP_ENV_MASK, 5, texunit, white, clamp ); - if ( mesh->nifVersion == 155 ) { + if ( nifVersion == 155 ) { prog->uniSampler( bsprop, SAMP_REFLECTIVITY, 8, texunit, black, clamp ); prog->uniSampler( bsprop, SAMP_LIGHTING, 9, texunit, lighting, clamp ); } @@ -863,17 +853,14 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & prog->uni4m( MAT_WORLD, mesh->worldTrans().toMatrix4() ); - clamp = esp->getClampMode(); + clamp = esp->clampMode; prog->uniSampler( bsprop, SAMP_BASE, 0, texunit, white, clamp ); - prog->uni1i( DOUBLE_SIDE, esp->getIsDoubleSided() ); - - auto uvS = esp->getUvScale(); - prog->uni2f( UV_SCALE, uvS.x, uvS.y ); + prog->uni1i( DOUBLE_SIDE, esp->isDoubleSided ); - auto uvO = esp->getUvOffset(); - prog->uni2f( UV_OFFSET, uvO.x, uvO.y ); + prog->uni2f( UV_SCALE, esp->uvScale.x, esp->uvScale.y ); + prog->uni2f( UV_OFFSET, esp->uvOffset.x, esp->uvOffset.y ); prog->uni1i( HAS_MAP_BASE, esp->hasSourceTexture ); prog->uni1i( HAS_MAP_G2P, esp->hasGreyscaleMap ); @@ -888,9 +875,8 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // Glow params - auto emC = esp->getEmissiveColor(); - prog->uni4f( GLOW_COLOR, emC.red(), emC.green(), emC.blue(), emC.alpha() ); - prog->uni1f( GLOW_MULT, esp->getEmissiveMult() ); + prog->uni4f( GLOW_COLOR, esp->emissiveColor.red(), esp->emissiveColor.green(), esp->emissiveColor.blue(), esp->emissiveColor.alpha() ); + prog->uni1f( GLOW_MULT, esp->emissiveMult ); // Falloff params @@ -904,19 +890,19 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // BSEffectShader textures prog->uniSampler( bsprop, SAMP_GRAYSCALE, 1, texunit, "", TexClampMode::MIRRORED_S_MIRRORED_T ); - if ( mesh->nifVersion >= 130 ) { + if ( nifVersion >= 130 ) { - prog->uni1f( LIGHT_INF, esp->getLightingInfluence() ); + prog->uni1f( LIGHT_INF, esp->lightingInfluence ); prog->uni1i( HAS_MAP_NORMAL, esp->hasNormalMap && scene->hasOption(Scene::DoLighting) ); prog->uniSampler( bsprop, SAMP_NORMAL, 3, texunit, default_n, clamp ); - prog->uni1i( HAS_MAP_CUBE, esp->hasEnvMap ); - prog->uni1i( HAS_MASK_ENV, esp->hasEnvMask ); + prog->uni1i( HAS_MAP_CUBE, esp->hasEnvironmentMap ); + prog->uni1i( HAS_MASK_ENV, esp->hasEnvironmentMask ); float refl = 0.0; - if ( esp->hasEnvMap && scene->hasOption(Scene::DoCubeMapping) && scene->hasOption(Scene::DoLighting) ) - refl = esp->getEnvironmentReflection(); + if ( esp->hasEnvironmentMap && scene->hasOption(Scene::DoCubeMapping) && scene->hasOption(Scene::DoLighting) ) + refl = esp->environmentReflection; prog->uni1f( ENV_REFLECTION, refl ); @@ -934,7 +920,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & fn->glUniform1i( uniCubeMap, texunit++ ); } prog->uniSampler( bsprop, SAMP_SPECULAR, 4, texunit, white, clamp ); - if ( mesh->nifVersion == 155 ) { + if ( nifVersion == 155 ) { prog->uniSampler( bsprop, SAMP_REFLECTIVITY, 6, texunit, black, clamp ); prog->uniSampler( bsprop, SAMP_LIGHTING, 7, texunit, lighting, clamp ); } @@ -1012,7 +998,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // setup blending - glProperty( props.get() ); + glProperty( mesh->alphaProperty ); if ( mat && scene->hasOption(Scene::DoBlending) ) { static const GLenum blendMap[11] = { @@ -1053,7 +1039,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & glDisable( GL_COLOR_MATERIAL ); - if ( mesh->nifVersion < 83 ) { + if ( nifVersion < 83 ) { // setup vertex colors //glProperty( props.get< VertexColorProperty >(), glIsEnabled( GL_COLOR_ARRAY ) ); @@ -1107,7 +1093,7 @@ void Renderer::setupFixedFunction( Shape * mesh, const PropertyList & props ) // setup blending - glProperty( props.get() ); + glProperty( mesh->alphaProperty ); // setup vertex colors diff --git a/src/glview.cpp b/src/glview.cpp index cd5a53948..2b703363e 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -404,7 +404,6 @@ void GLView::paintGL() { #endif - // Save GL state glPushAttrib( GL_ALL_ATTRIB_BITS ); glMatrixMode( GL_PROJECTION ); diff --git a/src/io/material.h b/src/io/material.h index 9b8d2f494..69f609e5f 100644 --- a/src/io/material.h +++ b/src/io/material.h @@ -49,15 +49,12 @@ class Material : public QObject Q_OBJECT friend class Renderer; - friend class BSShape; - friend class BSShaderLightingProperty; - friend class BSLightingShaderProperty; - friend class BSEffectShaderProperty; public: Material( QString name, Game::GameMode game ); bool isValid() const; + bool hasDecal() const { return (bDecal != 0); } QStringList textures() const; QString getPath() const; @@ -129,7 +126,6 @@ class ShaderMaterial : public Material Q_OBJECT friend class Renderer; - friend class BSShape; friend class BSShaderLightingProperty; friend class BSLightingShaderProperty; @@ -213,7 +209,6 @@ class EffectMaterial : public Material Q_OBJECT friend class Renderer; - friend class BSShape; friend class BSShaderLightingProperty; friend class BSEffectShaderProperty; diff --git a/src/lib/importex/importex.cpp b/src/lib/importex/importex.cpp index d362a0a96..e95c2edf2 100644 --- a/src/lib/importex/importex.cpp +++ b/src/lib/importex/importex.cpp @@ -76,20 +76,20 @@ void NifSkope::sltImportExport( QAction * a ) } } - if ( nif && nif->getVersionNumber() >= 0x14050000 ) { + if ( !nif || nif->getVersionNumber() >= 0x14050000 ) { mExport->setDisabled( true ); mImport->setDisabled( true ); return; - } else { - mImport->setDisabled( false ); - mExport->setDisabled( false ); - - if ( nif->getUserVersion2() >= 100 ) - mImport->actions().at(0)->setDisabled( true ); - else if ( nif->getUserVersion2() == 0 ) - mImport->actions().at(1)->setDisabled( true ); } + mImport->setDisabled( false ); + mExport->setDisabled( false ); + + if ( nif->getBSVersion() >= 100 ) + mImport->actions().at(0)->setDisabled( true ); + else if ( nif->getBSVersion() == 0 ) + mImport->actions().at(1)->setDisabled( true ); + if ( a->text() == tr( "Export .OBJ" ) ) exportObj( nif, index ); else if ( a->text() == tr( "Import .OBJ" ) ) diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index beb923feb..9753360c2 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -62,7 +62,7 @@ static void writeData( const NifModel * nif, const QModelIndex & iData, QTextStr { // copy vertices - if ( nif->getUserVersion2() < 100 ) { + if ( nif->getBSVersion() < 100 ) { QVector verts = nif->getArray( iData, "Vertices" ); foreach( Vector3 v, verts ) { @@ -284,7 +284,7 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS obj << "\r\n# " << name << "\r\n\r\ng " << name << "\r\n" << "usemtl " << matn << "\r\n\r\n"; - if ( nif->getUserVersion2() < 100 ) + if ( nif->getBSVersion() < 100 ) writeData( nif, nif->getBlock( nif->getLink( iShape, "Data" ) ), obj, ofs, t ); else writeData( nif, iShape, obj, ofs, t ); @@ -790,7 +790,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) QModelIndex shaderProp; // add material property, for non-Skyrim versions - if ( nif->getUserVersion2() <= 34 ) { + if ( nif->getBSVersion() <= 34 ) { bool newiMaterial = false; if ( iMaterial.isValid() == false || first_tri_shape == false ) { @@ -821,11 +821,11 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) } if ( !mtl.map_Kd.isEmpty() ) { - if ( nif->getUserVersion2() > 34 && shaderProp.isValid() ) { + if ( nif->getBSVersion() > 34 && shaderProp.isValid() ) { auto textureSet = nif->insertNiBlock( "BSShaderTextureSet" ); nif->setLink( shaderProp, "Texture Set", nif->getBlockNumber( textureSet ) ); - if ( nif->getUserVersion2() == 130 ) + if ( nif->getBSVersion() == 130 ) nif->set( textureSet, "Num Textures", 10 ); else nif->set( textureSet, "Num Textures", 9 ); @@ -941,7 +941,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) nif->set( iData, "Data Flags", 4097 ); nif->set( iData, "BS Data Flags", 4097 ); - if ( nif->getUserVersion2() > 34 ) { + if ( nif->getBSVersion() > 34 ) { nif->set( iData, "Has Vertex Colors", 1 ); nif->updateArray( iData, "Vertex Colors" ); } @@ -997,7 +997,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) nif->set( iData, "Radius", radius ); nif->set( iData, "Unknown Short 2", 0x4000 ); - } else if ( nif->getUserVersion2() > 0 ) { + } else if ( nif->getBSVersion() > 0 ) { // create experimental havok collision mesh QVector verts; QVector norms; diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index b2d76b4c5..945ae5773 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -219,6 +219,7 @@ void NifModel::clear() fileinfo = QFileInfo(); filename = QString(); folder = QString(); + bsVersion = 0; root->killChildren(); NifData headerData = NifData( "NiHeader", "Header" ); @@ -238,8 +239,9 @@ void NifModel::clear() } endResetModel(); - NifItem * item = getItem( getHeaderItem(), "Version" ); + NifItem * headerItem = getHeaderItem(); + NifItem * item = getItem( headerItem, "Version" ); if ( item ) item->value().setFileVersion( version ); @@ -253,14 +255,14 @@ void NifModel::clear() header_string += version2string( version ); - set( getHeaderItem(), "Header String", header_string ); + set( headerItem, "Header String", header_string ); if ( version >= 0x14000005 ) { - set( getHeaderItem(), "User Version", cfg.userVersion ); - set( getHeaderItem(), "User Version 2", cfg.userVersion2 ); + set( headerItem, "User Version", cfg.userVersion ); + set( headerItem, "User Version 2", cfg.userVersion2 ); } - //set( getHeaderItem(), "Unknown Int 3", 11 ); + //set( headerItem, "Unknown Int 3", 11 ); if ( version < 0x0303000D ) { QVector copyright( 3 ); @@ -271,6 +273,8 @@ void NifModel::clear() setArray( getHeader(), "Copyright", copyright ); } + cacheBSVersion( headerItem ); + lockUpdates = false; needUpdates = utNone; } @@ -695,7 +699,7 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at ) branch->prepareInsert( block->types.count() ); - if ( getUserVersion2() == 155 && identifier.startsWith( "BSLighting" ) ) { + if ( getBSVersion() == 155 && identifier.startsWith( "BSLighting" ) ) { for ( const NifData& data : block->types ) { insertType( branch, data ); } @@ -793,7 +797,7 @@ QMap NifModel::moveAllNiBlocks( NifModel * targetnif, bool updat { int bcnt = getBlockCount(); - bool doStringUpdate = ( this->getVersionNumber() >= 0x14010003 || targetnif->getVersionNumber() >= 0x14010003 ); + bool doStringUpdate = ( this->getVersionNumber() >= 0x14010003 || targetnif->getVersionNumber() >= 0x14010003 ); QMap map; @@ -918,7 +922,7 @@ QString NifModel::getBlockType( const QModelIndex & idx ) const int NifModel::getBlockNumber( const QModelIndex & idx ) const { - if ( !( idx.isValid() && idx.model() == this ) ) + if ( !idx.isValid() || idx.model() != this ) return -1; const NifItem * block = static_cast( idx.internalPointer() ); @@ -1632,7 +1636,7 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r parent = parent->parent(); if ( parent && parent->type() == "NiBlock" && parent->name() == "NiSourceTexture" ) - emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); + emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); } } else if ( item->name() == "Name" ) { NifItem * parent = item->parent(); @@ -2275,7 +2279,9 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) return false; bool stopRead = false; - if ( getUserVersion2() == 155 && parent->parent() == root ) { + // Be advised, getBSVersion returns 0 if it's the file's header that is being loaded. + // Though for shader properties loadItem happens after the header is fully processed, so the check below should work w/o issues. + if ( getBSVersion() == 155 && parent->parent() == root ) { if ( parent->name() == "BSLightingShaderProperty" || parent->name() == "BSEffectShaderProperty" ) stopRead = true; } @@ -2333,10 +2339,13 @@ bool NifModel::loadHeader( NifItem * header, NifIStream & stream ) // Reset Stream Device stream.reset(); + bsVersion = 0; set( header, "User Version", 0 ); set( getItem(header, "BS Header"), "BS Version", 0 ); invalidateConditions(header, true); - return loadItem(header, stream); + bool result = loadItem(header, stream); + cacheBSVersion( header ); + return result; } bool NifModel::saveItem( NifItem * parent, NifOStream & stream ) const @@ -3066,6 +3075,10 @@ void NifModel::updateModel( UpdateType value ) emit linksChanged(); } +void NifModel::cacheBSVersion( NifItem * headerItem ) +{ + bsVersion = get( getItem( headerItem, "BS Header" ), "BS Version" ); +} /* * NifModelEval diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index c9a03578a..e10a56d90 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -71,6 +71,9 @@ class NifModel final : public BaseModel public: NifModel( QObject * parent = 0 ); + static const NifModel * fromIndex( const QModelIndex & index ); + static const NifModel * fromValidIndex( const QModelIndex & index ); + //! Find and parse the XML file static bool loadXML(); @@ -284,7 +287,7 @@ class NifModel final : public BaseModel bool checkVersion( quint32 since, quint32 until ) const; quint32 getUserVersion() const { return get( getHeader(), "User Version" ); } - quint32 getUserVersion2() const { return get( getItem( getHeaderItem(), "BS Header" ), "BS Version" ); } + quint32 getBSVersion() const { return bsVersion; } QString string( const QModelIndex & index, bool extraInfo = false ) const; QString string( const QModelIndex & index, const QString & name, bool extraInfo = false ) const; @@ -378,6 +381,9 @@ public slots: void updateModel( UpdateType value = utAll ); + quint32 bsVersion; + void cacheBSVersion(NifItem* headerItem); + //! Parse the XML file using a NifXmlHandler static QString parseXmlDescription( const QString & filename ); @@ -416,6 +422,15 @@ class NifModelEval // Inlines +inline const NifModel * NifModel::fromIndex( const QModelIndex& index ) +{ + return static_cast(index.model()); // qobject_cast +} + +inline const NifModel * NifModel::fromValidIndex( const QModelIndex& index ) +{ + return index.isValid() ? NifModel::fromIndex( index ) : nullptr; +} inline QStringList NifModel::allNiBlocks() { diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 53e52adc7..04fba4b22 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -783,9 +783,9 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) } else { mExport->setDisabled( false ); mImport->setDisabled( false ); - if ( nif->getUserVersion2() >= 100 ) + if ( nif->getBSVersion() >= 100 ) mImport->actions().at(0)->setDisabled(true); - else if ( nif->getUserVersion2() == 0 ) + else if ( nif->getBSVersion() == 0 ) mImport->actions().at(1)->setDisabled(true); } diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 8e75832b5..47bff239d 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -552,7 +552,7 @@ void blockFilter( NifModel * nif, std::list& blocks, const QString & ty && nif->getVersionNumber() > 0x0a010000 ) // Bethesda || ( (s.startsWith( "bhk" ) || s.startsWith( "hk" ) || s.startsWith( "BS" ) - || s.endsWith( "ShaderProperty" )) && nif->getUserVersion2() == 0 ) + || s.endsWith( "ShaderProperty" )) && nif->getBSVersion() == 0 ) // Introduced in 20.2.0.8 || (( s.startsWith( "NiPhysX" ) && nif->getVersionNumber() < 0x14020008 )) // Introduced in 20.5 @@ -580,8 +580,8 @@ QMap blockMenu( NifModel * nif, const std::list & blo bool firstCat = false; for ( const QString& id : ids ) { QString alph( "Other" ); - QString beth = (nif->getUserVersion2() == 0) ? alph : "Bethesda"; - QString hk = (nif->getUserVersion2() == 0) ? alph : "Havok"; + QString beth = (nif->getBSVersion() == 0) ? alph : "Bethesda"; + QString hk = (nif->getBSVersion() == 0) ? alph : "Havok"; bool alphabetized = false; // Group Old Particles @@ -727,8 +727,7 @@ class spAttachProperty final : public Spell QStringList ids = nif->allNiBlocks(); ids.sort(); for ( const QString& id : ids ) { - if ( (nif->inherits( index, "NiGeometry" ) || nif->inherits( index, "BSTriShape" )) - && nif->getUserVersion2() > 34 ) { + if ( (nif->inherits(index, "NiGeometry") || nif->inherits(index, "BSTriShape")) && nif->getBSVersion() > 34 ) { if ( !(id == "BSLightingShaderProperty" || id == "BSEffectShaderProperty" || id == "NiAlphaProperty") ) continue; } @@ -868,7 +867,7 @@ class spAddNewRef final : public Spell // Block-to-Controller Mapping ctlrFilter( ctlrMapping ); // Bethesda Controllers - if ( nif->getUserVersion2() > 0 ) + if ( nif->getBSVersion() > 0 ) ctlrFilter( ctlrMappingBS ); } else if ( nif->inherits( iBlock, "NiTimeController" ) diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index c183c2e72..0be88fcbc 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -977,7 +977,6 @@ class spEditVertexDesc final : public spEditFlags QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { auto desc = nif->get( index ); - uint stream = nif->getUserVersion2(); bool dynamic = nif->inherits( index.parent(), "BSDynamicTriShape" ); QStringList flagNames { @@ -1022,7 +1021,7 @@ class spEditVertexDesc final : public spEditFlags } // Make sure sizes and offsets in rest of vertexDesc are updated from flags - desc.ResetAttributeOffsets( stream ); + desc.ResetAttributeOffsets( nif->getBSVersion() ); if ( dynamic ) desc.MakeDynamic(); diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index 4c719f82f..a7dfc6799 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -37,7 +37,7 @@ class spCreateCVS final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { if ( !(nif->inherits( index, "NiTriBasedGeom" ) || nif->inherits( index, "BSTriShape" )) - || !nif->getUserVersion2() ) + || !nif->getBSVersion() ) return false; QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); @@ -67,7 +67,7 @@ class spCreateCVS final : public Spell QVector verts = nif->getArray( iData, "Vertices" ); QVector vertsTrans; - if ( nif->getUserVersion2() < 100 ) { + if ( nif->getBSVersion() < 100 ) { verts = nif->getArray( iData, "Vertices" ); } else { int numVerts = nif->get( index, "Num Vertices" ); diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index ea1739e28..9377752e7 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -724,10 +724,7 @@ class spUpdateAllBounds final : public Spell if ( !nif || idx.isValid() ) return false; - if ( nif->getUserVersion2() >= 130 ) - return true; - - return false; + return ( nif->getBSVersion() >= 130 ); } QModelIndex cast( NifModel * nif, const QModelIndex & ) override final diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index 35362152b..5104f05c5 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -35,7 +35,7 @@ class spFaceNormals final : public Spell if ( nif->isNiBlock( index, { "BSTriShape", "BSMeshLODTriShape", "BSSubIndexTriShape", "BSDynamicTriShape" } ) ) { auto vf = nif->get( index, "Vertex Desc" ); - if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { + if ( (vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( index, "Skin" ) ); auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); @@ -76,7 +76,7 @@ class spFaceNormals final : public Spell } }; - if ( nif->getUserVersion2() < 100 ) { + if ( nif->getBSVersion() < 100 ) { QVector verts = nif->getArray( iData, "Vertices" ); QVector triangles; QModelIndex iPoints = nif->getIndex( iData, "Points" ); @@ -104,7 +104,7 @@ class spFaceNormals final : public Spell QVector triangles; int numVerts; auto vf = nif->get( index, "Vertex Desc" ); - if ( !((vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100) ) { + if ( !((vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100) ) { numVerts = nif->get( index, "Num Vertices" ); triangles = nif->getArray( index, "Triangles" ); } else { @@ -204,12 +204,12 @@ class spSmoothNormals final : public Spell int numVerts = 0; - if ( nif->getUserVersion2() < 100 ) { + if ( nif->getBSVersion() < 100 ) { verts = nif->getArray( iData, "Vertices" ); norms = nif->getArray( iData, "Normals" ); } else { auto vf = nif->get( index, "Vertex Desc" ); - if ( !((vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100) ) { + if ( !((vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100) ) { numVerts = nif->get( index, "Num Vertices" ); } else { // Skinned SSE @@ -304,7 +304,7 @@ class spSmoothNormals final : public Spell for ( int i = 0; i < verts.count(); i++ ) snorms[i].normalize(); - if ( nif->getUserVersion2() < 100 ) { + if ( nif->getBSVersion() < 100 ) { nif->setArray( iData, "Normals", snorms ); } else { // Pause updates between model/view diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index 2b7b6d083..4dab31aca 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -26,7 +26,7 @@ class spReorderLinks : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override { - return ( !index.isValid() && ( nif->getVersionNumber() >= 0x14000004 && nif->getUserVersion2() > 0 ) ); + return ( !index.isValid() && ( nif->getVersionNumber() >= 0x14000004 && nif->getBSVersion() > 0 ) ); } //! Comparator for link sort. @@ -70,7 +70,7 @@ class spReorderLinks : public Spell } auto compareFn = compareChildLinksShapeBtm; - if ( nif->getUserVersion2() < 83 ) + if ( nif->getBSVersion() < 83 ) compareFn = compareChildLinksShapeTop; std::stable_sort( links.begin(), links.end(), compareFn ); @@ -715,13 +715,13 @@ REGISTER_SPELL( spErrorInvalidPaths ) bool spWarningEnvironmentMapping::isApplicable(const NifModel * nif, const QModelIndex & index) { - return nif->getUserVersion2() > 0 && !index.isValid(); + return nif->getBSVersion() > 0 && !index.isValid(); } QModelIndex spWarningEnvironmentMapping::cast(NifModel * nif, const QModelIndex & idx) { for ( int i = 0; i < nif->getBlockCount(); i++ ) { - if ( nif->getUserVersion2() < 83 ) { + if ( nif->getBSVersion() < 83 ) { auto iBSSP = nif->getBlock(i, "BSShaderPPLightingProperty"); if ( iBSSP.isValid() ) { auto sf1 = nif->get(iBSSP, "Shader Flags"); diff --git a/src/spells/sanitize.h b/src/spells/sanitize.h index 751e78b65..a11b9aa47 100644 --- a/src/spells/sanitize.h +++ b/src/spells/sanitize.h @@ -35,12 +35,12 @@ class spChecker : public Spell bool constant() const override { return true; } bool checker() const override { return true; } - bool isApplicable(const NifModel *, const QModelIndex & index) override + bool isApplicable(const NifModel *, const QModelIndex &) override { return false; } - QModelIndex cast(NifModel * nif, const QModelIndex &) override { return {}; } + QModelIndex cast(NifModel *, const QModelIndex &) override { return {}; } static QString message() { return {}; } }; diff --git a/src/spells/tangentspace.cpp b/src/spells/tangentspace.cpp index 2b7d57336..9723fb59b 100644 --- a/src/spells/tangentspace.cpp +++ b/src/spells/tangentspace.cpp @@ -43,11 +43,11 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) QPersistentModelIndex iShape = iBlock; QModelIndex iData; QModelIndex iPartBlock; - if ( nif->getUserVersion2() < 100 ) { + if ( nif->getBSVersion() < 100 ) { iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); } else { auto vf = nif->get( iShape, "Vertex Desc" ); - if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { + if ( (vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( iShape, "Skin" ) ); auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); @@ -63,7 +63,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) QVector norms; QVector texco; - if ( nif->getUserVersion2() < 100 ) { + if ( nif->getBSVersion() < 100 ) { verts = nif->getArray( iData, "Vertices" ); norms = nif->getArray( iData, "Normals" ); } else { @@ -88,7 +88,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) QVector vxcol = nif->getArray( iData, "Vertex Colors" ); - if ( nif->getUserVersion2() < 100 ) { + if ( nif->getBSVersion() < 100 ) { QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); iTexCo = iTexCo.child( 0, 0 ); texco = nif->getArray( iTexCo ); @@ -105,9 +105,9 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); triangles = triangulate( strips ); - } else if ( nif->getUserVersion2() < 100 ) { + } else if ( nif->getBSVersion() < 100 ) { triangles = nif->getArray( iData, "Triangles" ); - } else if ( nif->getUserVersion2() >= 100 ) { + } else if ( nif->getBSVersion() >= 100 ) { if ( iPartBlock.isValid() ) { // Get triangles from all partitions auto numParts = nif->get( iPartBlock, "Num Partitions" ); @@ -270,14 +270,14 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) } nif->set( iTSpace, "Binary Data", QByteArray( (const char *)tan.data(), tan.count() * sizeof( Vector3 ) ) + QByteArray( (const char *)bin.data(), bin.count() * sizeof( Vector3 ) ) ); - } else if ( nif->getUserVersion2() < 100 ) { + } else if ( nif->getBSVersion() < 100 ) { QModelIndex iBinorms = nif->getIndex( iData, "Bitangents" ); QModelIndex iTangents = nif->getIndex( iData, "Tangents" ); nif->updateArray( iBinorms ); nif->updateArray( iTangents ); nif->setArray( iBinorms, bin ); nif->setArray( iTangents, tan ); - } else if ( nif->getUserVersion2() >= 100 ) { + } else if ( nif->getBSVersion() >= 100 ) { int numVerts; // "Num Vertices" does not exist in the partition if ( iPartBlock.isValid() ) diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index 97b1d6468..8d29144fb 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -532,7 +532,7 @@ class spTextureTemplate final : public Spell { QModelIndex iUVs = getUV( nif, index ); auto iTriData = nif->getIndex( index, "Num Triangles" ); - bool bstri = nif->getUserVersion2() >= 100 && nif->inherits( index, "BSTriShape" ) && iTriData.isValid(); + bool bstri = nif->getBSVersion() >= 100 && nif->inherits( index, "BSTriShape" ) && iTriData.isValid(); return (iUVs.isValid() && nif->rowCount( iUVs ) >= 1) || bstri; } @@ -540,7 +540,7 @@ class spTextureTemplate final : public Spell { QModelIndex iUVs = getUV( nif, index ); - if ( nif->rowCount( iUVs ) <= 0 && nif->getUserVersion2() < 100 ) + if ( nif->rowCount( iUVs ) <= 0 && nif->getBSVersion() < 100 ) return index; // fire up a dialog to set the user parameters @@ -628,10 +628,10 @@ class spTextureTemplate final : public Spell QVector uv; QVector tri; - if ( nif->getUserVersion2() >= 100 ) { + if ( nif->getBSVersion() >= 100 ) { QModelIndex iVertData; auto vf = nif->get( index, "Vertex Desc" ); - if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { + if ( (vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( index, "Skin" ) ); auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); @@ -670,7 +670,7 @@ class spTextureTemplate final : public Spell strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); tri = triangulate( strips ); - } else if ( nif->getUserVersion2() < 100 ) { + } else if ( nif->getBSVersion() < 100 ) { tri = nif->getArray( nif->getIndex( getData( nif, index ), "Triangles" ) ); } diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 84258ea75..5b6cacc5e 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -808,7 +808,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) newTitle += tr(" - ") + nif->getFileInfo().fileName(); setWindowTitle(newTitle); - game = Game::GameManager::get_game(nif->getVersionNumber(), nif->getUserVersion(), nif->getUserVersion2()); + game = Game::GameManager::get_game(nif->getVersionNumber(), nif->getUserVersion(), nif->getBSVersion()); // Version dependent actions if ( nif && nif->getVersionNumber() != 0x14020007 ) { @@ -840,11 +840,11 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) textures->setNifFolder( nif->getFolder() ); iShapeData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - if ( nif->getVersionNumber() == 0x14020007 && nif->getUserVersion2() >= 100 ) { + if ( nif->getVersionNumber() == 0x14020007 && nif->getBSVersion() >= 100 ) { iShapeData = nif->getIndex( iShape, "Vertex Data" ); auto vf = nif->get( iShape, "Vertex Desc" ); - if ( (vf & VertexFlags::VF_SKINNED) && nif->getUserVersion2() == 100 ) { + if ( (vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( iShape, "Skin" ) ); auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index 8c582cce9..822cdb888 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -440,7 +440,7 @@ void TestThread::run() bool loaded = (headerOnly) ? nif.loadHeaderOnly(filepath) : model->loadFromFile(filepath); result = QString( "%1 (%2, %3, %4)" ) - .arg( filepath, model->getVersion() ).arg( nif.getUserVersion() ).arg( nif.getUserVersion2() ); + .arg( filepath, model->getVersion() ).arg( nif.getUserVersion() ).arg( nif.getBSVersion() ); QList messages = model->getMessages(); bool blk_match = false; From d2bdf13d5713d11eb4e781f1abbbd7108ce805e5 Mon Sep 17 00:00:00 2001 From: gavrant Date: Sun, 18 Jun 2023 17:24:58 +0300 Subject: [PATCH 057/118] Fixed invisibility/flickering of shapes with alpha property. Also fixed the wireframe flickering on the same shapes with alpha. Skyrim SE or newer nifs used to be especially prone to these bugs. --- src/gl/bsshape.cpp | 21 ++++++++++----------- src/gl/glmesh.cpp | 40 ++++++++++++++++++++++++++++------------ src/gl/glparticles.cpp | 2 +- src/gl/glproperty.cpp | 2 -- src/gl/glproperty.h | 4 ++-- src/gl/renderer.cpp | 14 +++----------- src/io/material.h | 2 ++ 7 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index b96739dcd..4cc38792a 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -262,6 +262,12 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) if ( !scene->hasOption(Scene::ShowMarkers) && name.contains( "EditorMarker" ) ) return; + // Draw translucent meshes in second pass + if ( secondPass && drawInSecondPass ) { + secondPass->add( this ); + return; + } + auto nif = NifModel::fromIndex( iBlock ); if ( Node::SELECTING ) { @@ -273,22 +279,17 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) } } - // Draw translucent meshes in second pass - if ( secondPass && drawInSecondPass ) { - secondPass->add( this ); - return; - } - if ( transformRigid ) { glPushMatrix(); glMultMatrix( viewTrans() ); } // Render polygon fill slightly behind alpha transparency and wireframe - if ( !drawInSecondPass ) { - glEnable( GL_POLYGON_OFFSET_FILL ); + glEnable( GL_POLYGON_OFFSET_FILL ); + if ( drawInSecondPass ) + glPolygonOffset( 0.5f, 1.0f ); + else glPolygonOffset( 1.0f, 2.0f ); - } glEnableClientState( GL_VERTEX_ARRAY ); glVertexPointer( 3, GL_FLOAT, 0, transVerts.constData() ); @@ -314,7 +315,6 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) } } - if ( !Node::SELECTING ) { if ( nif->getBSVersion() == 155 ) glEnable( GL_FRAMEBUFFER_SRGB ); @@ -370,7 +370,6 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) glDisable( GL_POLYGON_OFFSET_FILL ); - if ( scene->isSelModeVertex() ) { drawVerts(); } diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index c9560528a..478deca75 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -213,8 +213,16 @@ void Shape::updateShader() else translucent = false; - Material * mat = (bssp) ? bssp->getMaterial() : nullptr; - drawInSecondPass = translucent || ( alphaProperty && alphaProperty->blend() ) || ( mat && mat->hasDecal() ); + drawInSecondPass = false; + if ( translucent ) + drawInSecondPass = true; + else if ( alphaProperty && (alphaProperty->hasAlphaBlend() || alphaProperty->hasAlphaTest()) ) + drawInSecondPass = true; + else if ( bssp ) { + Material * mat = bssp->getMaterial(); + if ( mat && (mat->hasAlphaBlend() || mat->hasAlphaTest() || mat->hasDecal()) ) + drawInSecondPass = true; + } if ( bssp ) { depthTest = bssp->depthTest; @@ -937,6 +945,15 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) if ( !scene->hasOption(Scene::ShowMarkers) && name.startsWith( "EditorMarker" ) ) return; + // BSOrderedNode + presorted |= presort; + + // Draw translucent meshes in second pass + if ( secondPass && drawInSecondPass ) { + secondPass->add( this ); + return; + } + auto nif = NifModel::fromIndex( iBlock ); if ( Node::SELECTING ) { @@ -948,15 +965,6 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) } } - // BSOrderedNode - presorted |= presort; - - // Draw translucent meshes in second pass - if ( secondPass && drawInSecondPass ) { - secondPass->add( this ); - return; - } - // TODO: Option to hide Refraction and other post effects // rigid mesh? then pass the transformation on to the gl layer @@ -978,7 +986,10 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) // Render polygon fill slightly behind alpha transparency and wireframe glEnable( GL_POLYGON_OFFSET_FILL ); - glPolygonOffset( 1.0f, 2.0f ); + if ( drawInSecondPass ) + glPolygonOffset( 0.5f, 1.0f ); + else + glPolygonOffset( 1.0f, 2.0f ); glEnableClientState( GL_VERTEX_ARRAY ); glVertexPointer( 3, GL_FLOAT, 0, transVerts.constData() ); @@ -1136,6 +1147,9 @@ void Mesh::drawSelection() const glDisable( GL_CULL_FACE ); + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( -1.0f, -2.0f ); + glLineWidth( 1.0 ); glPointSize( 3.5 ); @@ -1445,6 +1459,8 @@ void Mesh::drawSelection() const } } + glDisable( GL_POLYGON_OFFSET_FILL ); + if ( transformRigid ) glPopMatrix(); } diff --git a/src/gl/glparticles.cpp b/src/gl/glparticles.cpp index bc6cac1c4..0c0199f3c 100644 --- a/src/gl/glparticles.cpp +++ b/src/gl/glparticles.cpp @@ -135,7 +135,7 @@ void Particles::drawShapes( NodeList * secondPass, bool presort ) AlphaProperty * aprop = findProperty(); - if ( aprop && aprop->blend() && secondPass ) { + if ( aprop && aprop->hasAlphaBlend() && secondPass ) { secondPass->add( this ); return; } diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 7725e129c..30cad74dd 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -248,7 +248,6 @@ void AlphaProperty::setController( const NifModel * nif, const QModelIndex & con void glProperty( AlphaProperty * p ) { if ( p && p->alphaBlend && p->scene->hasOption(Scene::DoBlending) ) { - glDisable( GL_POLYGON_OFFSET_FILL ); glEnable( GL_BLEND ); glBlendFunc( p->alphaSrc, p->alphaDst ); } else { @@ -256,7 +255,6 @@ void glProperty( AlphaProperty * p ) } if ( p && p->alphaTest && p->scene->hasOption(Scene::DoBlending) ) { - glDisable( GL_POLYGON_OFFSET_FILL ); glEnable( GL_ALPHA_TEST ); glAlphaFunc( p->alphaFunc, p->alphaThreshold ); } else { diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 2f2e23c51..a0a5df242 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -148,8 +148,8 @@ class AlphaProperty final : public Property Type type() const override final { return Alpha; } QString typeId() const override final { return "NiAlphaProperty"; } - bool blend() const { return alphaBlend; } - bool test() const { return alphaTest; } + bool hasAlphaBlend() const { return alphaBlend; } + bool hasAlphaTest() const { return alphaTest; } GLfloat alphaThreshold = 0; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 89f0492ad..5226e410e 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -611,7 +611,7 @@ static QString cube = "shaders/cubemap.dds"; bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & props, const QVector & iBlocks, bool eval ) { - auto nif = NifModel::fromValidIndex(mesh->index()); + auto nif = NifModel::fromValidIndex( mesh->index() ); if ( !nif ) return false; @@ -723,7 +723,6 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & } } - // BSLightingShaderProperty if ( lsp ) { prog->uni1f( LIGHT_EFF1, lsp->lightingEffect1 ); @@ -1007,26 +1006,19 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA_SATURATE }; - if ( mat && mat->bAlphaBlend ) { - glDisable( GL_POLYGON_OFFSET_FILL ); + if ( mat->hasAlphaBlend() ) { glEnable( GL_BLEND ); glBlendFunc( blendMap[mat->iAlphaSrc], blendMap[mat->iAlphaDst] ); } else { glDisable( GL_BLEND ); } - if ( mat && mat->bAlphaTest ) { - glDisable( GL_POLYGON_OFFSET_FILL ); + if ( mat->hasAlphaTest() ) { glEnable( GL_ALPHA_TEST ); glAlphaFunc( GL_GREATER, float( mat->iAlphaTestRef ) / 255.0 ); } else { glDisable( GL_ALPHA_TEST ); } - - if ( mat && mat->bDecal ) { - glEnable( GL_POLYGON_OFFSET_FILL ); - glPolygonOffset( -1.0f, -1.0f ); - } } // BSESP/BSLSP do not always need an NiAlphaProperty, and appear to override it at times diff --git a/src/io/material.h b/src/io/material.h index 69f609e5f..a7cec3dea 100644 --- a/src/io/material.h +++ b/src/io/material.h @@ -54,6 +54,8 @@ class Material : public QObject Material( QString name, Game::GameMode game ); bool isValid() const; + bool hasAlphaBlend() const { return (bAlphaBlend != 0); } + bool hasAlphaTest() const { return (bAlphaTest != 0); } bool hasDecal() const { return (bDecal != 0); } QStringList textures() const; QString getPath() const; From 69423353e6cbc506b3e4eee568e4f7f51c6f2b70 Mon Sep 17 00:00:00 2001 From: gavrant Date: Sun, 18 Jun 2023 18:14:33 +0300 Subject: [PATCH 058/118] Fixed a few rough spots in my previous commits. --- src/gl/bsshape.cpp | 9 +++------ src/gl/glmesh.cpp | 5 ++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 4cc38792a..f5cfe7001 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -50,12 +50,10 @@ void BSShape::updateData( const NifModel * nif ) } } - bool hasDataOnSkinPart = ( isSkinned && iSkinPart.isValid() ); - // Fill vertex data resetVertexData(); numVerts = 0; - if ( hasDataOnSkinPart ) { + if ( isSkinned && iSkinPart.isValid() ) { // For skinned geometry, the vertex data is stored in the NiSkinPartition // The triangles are split up among the partitions iData = nif->getIndex( iSkinPart, "Vertex Data" ); @@ -113,15 +111,14 @@ void BSShape::updateData( const NifModel * nif ) numVerts = verts.count(); // Fill triangle data - if ( hasDataOnSkinPart ) { + if ( isSkinned && iSkinPart.isValid() ) { auto iPartitions = nif->getIndex( iSkinPart, "Partitions" ); if ( iPartitions.isValid() ) { int n = nif->rowCount( iPartitions ); for ( int i = 0; i < n; i++ ) triangles << nif->getArray( nif->index( i, 0, iPartitions ), "Triangles" ); } - } - else { + } else { auto iTriData = nif->getIndex( iBlock, "Triangles" ); if ( iTriData.isValid() ) triangles = nif->getArray( iTriData ); diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 478deca75..901d1303e 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -70,6 +70,7 @@ void Shape::clear() transColors.clear(); transTangents.clear(); transBitangents.clear(); + sortedTriangles.clear(); bssp = nullptr; bslsp = nullptr; @@ -189,9 +190,6 @@ void Shape::resetVertexData() bitangents.clear(); triangles.clear(); tristrips.clear(); - weights.clear(); - partitions.clear(); - sortedTriangles.clear(); } void Shape::resetSkeletonData() @@ -630,6 +628,7 @@ void Mesh::updateData_NiMesh( const NifModel * nif ) Q_ASSERT( verts.size() == maxIndex + 1 ); Q_ASSERT( indices.size() == totalIndices ); + numVerts = verts.count(); // Make geometry triangles.resize( indices.size() / 3 ); From 6b88c92426a1ec312d3e900fb63d614d8b8e2d66 Mon Sep 17 00:00:00 2001 From: gavrant Date: Mon, 19 Jun 2023 10:23:49 +0300 Subject: [PATCH 059/118] Optimized NifItem::getArray. Added pre-allocation of the result QVector. --- src/data/nifitem.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/data/nifitem.h b/src/data/nifitem.h index c5c03aeda..a27f1fa89 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -685,8 +685,12 @@ class NifItem template QVector getArray() const { QVector array; - for ( NifItem * child : childItems ) { - array.append( child->itemData.value.get() ); + int nSize = childItems.count(); + if ( nSize > 0 ) { + array.reserve( nSize ); + for ( NifItem * child : childItems ) { + array.append( child->itemData.value.get() ); + } } return array; } From 9389918300b9dab7b3873a6fc8d576a2584d4a7f Mon Sep 17 00:00:00 2001 From: gavrant Date: Fri, 14 Jul 2023 15:43:27 +0300 Subject: [PATCH 060/118] Split Shape class into a separate pair of .h + .cpp files. 2 big(-ish) classes sharing a single glmesh file has become unwieldy. --- NifSkope.pro | 2 + src/gl/bsshape.h | 3 +- src/gl/controllers.cpp | 3 +- src/gl/glmesh.cpp | 184 --------------------------------- src/gl/glmesh.h | 138 +------------------------ src/gl/glshape.cpp | 224 +++++++++++++++++++++++++++++++++++++++++ src/gl/glshape.h | 175 ++++++++++++++++++++++++++++++++ src/gl/renderer.cpp | 2 +- src/glview.cpp | 2 +- 9 files changed, 406 insertions(+), 327 deletions(-) create mode 100644 src/gl/glshape.cpp create mode 100644 src/gl/glshape.h diff --git a/NifSkope.pro b/NifSkope.pro index ee4770dee..2f157529d 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -152,6 +152,7 @@ HEADERS += \ src/gl/glparticles.h \ src/gl/glproperty.h \ src/gl/glscene.h \ + src/gl/glshape.h \ src/gl/gltex.h \ src/gl/gltexloaders.h \ src/gl/gltools.h \ @@ -218,6 +219,7 @@ SOURCES += \ src/gl/glparticles.cpp \ src/gl/glproperty.cpp \ src/gl/glscene.cpp \ + src/gl/glshape.cpp \ src/gl/gltex.cpp \ src/gl/gltexloaders.cpp \ src/gl/gltools.cpp \ diff --git a/src/gl/bsshape.h b/src/gl/bsshape.h index 72badbbf1..514a88f49 100644 --- a/src/gl/bsshape.h +++ b/src/gl/bsshape.h @@ -1,8 +1,7 @@ #ifndef BSSHAPE_H #define BSSHAPE_H -#include "gl/glmesh.h" -#include "gl/gltools.h" +#include "gl/glshape.h" class NifModel; diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 9240b36ae..39fc8a0f7 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -32,8 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "controllers.h" -#include "gl/glmesh.h" -#include "gl/glnode.h" +#include "gl/glshape.h" #include "gl/glparticles.h" #include "gl/glproperty.h" #include "gl/glscene.h" diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 901d1303e..919e78b5c 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -51,190 +51,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. const char * NIMESH_ABORT = QT_TR_NOOP( "NiMesh rendering encountered unsupported types. Rendering may be broken." ); - -Shape::Shape( Scene * s, const QModelIndex & b ) : Node( s, b ) -{ - shapeNumber = s->shapes.count(); -} - -void Shape::clear() -{ - Node::clear(); - - resetSkinning(); - resetVertexData(); - resetSkeletonData(); - - transVerts.clear(); - transNorms.clear(); - transColors.clear(); - transTangents.clear(); - transBitangents.clear(); - sortedTriangles.clear(); - - bssp = nullptr; - bslsp = nullptr; - bsesp = nullptr; - alphaProperty = nullptr; - - isLOD = false; - isDoubleSided = false; -} - -void Shape::transform() -{ - if ( needUpdateData ) { - needUpdateData = false; - - auto nif = NifModel::fromValidIndex( iBlock ); - if ( nif ) { - needUpdateBounds = true; // Force update bounds - updateData(nif); - - if ( isVertexAlphaAnimation ) { - int nColors = colors.count(); - for ( int i = 0; i < nColors; i++ ) - colors[i].setRGBA( colors[i].red(), colors[i].green(), colors[i].blue(), 1 ); - } - } else { - clear(); - return; - } - } - - Node::transform(); -} - -void Shape::setController( const NifModel * nif, const QModelIndex & iController ) -{ - QString contrName = nif->itemName(iController); - if ( contrName == "NiGeomMorpherController" ) { - Controller * ctrl = new MorphController( this, iController ); - registerController(nif, ctrl); - } else if ( contrName == "NiUVController" ) { - Controller * ctrl = new UVController( this, iController ); - registerController(nif, ctrl); - } else { - Node::setController( nif, iController ); - } -} - -void Shape::updateImpl( const NifModel * nif, const QModelIndex & index ) -{ - Node::updateImpl( nif, index ); - - if ( index == iBlock ) { - shader = ""; // Reset stored shader so it can reassess conditions - - bslsp = nullptr; - bsesp = nullptr; - bssp = properties.get(); - if ( bssp ) { - auto shaderType = bssp->typeId(); - if ( shaderType == "BSLightingShaderProperty" ) - bslsp = bssp->cast(); - else if ( shaderType == "BSEffectShaderProperty" ) - bsesp = bssp->cast(); - } - - alphaProperty = properties.get(); - - needUpdateData = true; - updateShader(); - - } else if ( isSkinned && (index == iSkin || index == iSkinData || index == iSkinPart) ) { - needUpdateData = true; - - } else if ( (bssp && bssp->isParamBlock(index)) || (alphaProperty && index == alphaProperty->index()) ) { - updateShader(); - - } -} - -void Shape::boneSphere( const NifModel * nif, const QModelIndex & index ) const -{ - Node * root = findParent( 0 ); - Node * bone = root ? root->findChild( bones.value( index.row() ) ) : 0; - if ( !bone ) - return; - - Transform boneT = Transform( nif, index ); - Transform t = scene->hasOption(Scene::DoSkinning) ? viewTrans() : Transform(); - t = t * skeletonTrans * bone->localTrans( 0 ) * boneT; - - auto bSphere = BoundSphere( nif, index ); - if ( bSphere.radius > 0.0 ) { - glColor4f( 1, 1, 1, 0.33f ); - auto pos = boneT.rotation.inverted() * (bSphere.center - boneT.translation); - drawSphereSimple( t * pos, bSphere.radius, 36 ); - } -} - -void Shape::resetSkinning() -{ - isSkinned = false; - iSkin = iSkinData = iSkinPart = QModelIndex(); -} - -void Shape::resetVertexData() -{ - numVerts = 0; - - iData = iTangentData = QModelIndex(); - - verts.clear(); - norms.clear(); - colors.clear(); - coords.clear(); - tangents.clear(); - bitangents.clear(); - triangles.clear(); - tristrips.clear(); -} - -void Shape::resetSkeletonData() -{ - skeletonRoot = 0; - skeletonTrans = Transform(); - - bones.clear(); - weights.clear(); - partitions.clear(); -} - -void Shape::updateShader() -{ - if ( bslsp ) - translucent = (bslsp->alpha < 1.0) || bslsp->hasRefraction; - else if ( bsesp ) - translucent = (bsesp->getAlpha() < 1.0) && !alphaProperty; - else - translucent = false; - - drawInSecondPass = false; - if ( translucent ) - drawInSecondPass = true; - else if ( alphaProperty && (alphaProperty->hasAlphaBlend() || alphaProperty->hasAlphaTest()) ) - drawInSecondPass = true; - else if ( bssp ) { - Material * mat = bssp->getMaterial(); - if ( mat && (mat->hasAlphaBlend() || mat->hasAlphaTest() || mat->hasDecal()) ) - drawInSecondPass = true; - } - - if ( bssp ) { - depthTest = bssp->depthTest; - depthWrite = bssp->depthWrite; - isDoubleSided = bssp->isDoubleSided; - isVertexAlphaAnimation = bssp->isVertexAlphaAnimation; - } else { - depthTest = true; - depthWrite = true; - isDoubleSided = false; - isVertexAlphaAnimation = false; - } -} - void Mesh::updateImpl( const NifModel * nif, const QModelIndex & index ) { Shape::updateImpl(nif, index); diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index 32d86b899..6bcf9108e 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -33,146 +33,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLMESH_H #define GLMESH_H -#include "gl/glnode.h" // Inherited -#include "gl/gltools.h" - -#include -#include -#include - +#include "gl/glshape.h" // Inherited //! @file glmesh.h Mesh -using TriStrip = QVector; -Q_DECLARE_TYPEINFO( TriStrip, Q_MOVABLE_TYPE ); -using TexCoords = QVector; -Q_DECLARE_TYPEINFO( TexCoords, Q_MOVABLE_TYPE ); - -class NifModel; - -class Shape : public Node -{ - friend class MorphController; - friend class UVController; - friend class Renderer; - -public: - Shape( Scene * s, const QModelIndex & b ); - - // IControllable - - void clear() override; - void transform() override; - - // end IControllable - - virtual void drawVerts() const {}; - virtual QModelIndex vertexAt( int ) const { return QModelIndex(); }; - -protected: - int shapeNumber; - - void setController( const NifModel * nif, const QModelIndex & controller ) override; - void updateImpl( const NifModel * nif, const QModelIndex & index ) override; - virtual void updateData( const NifModel* nif ) = 0; - - void boneSphere( const NifModel * nif, const QModelIndex & index ) const; - - //! Shape data - QPersistentModelIndex iData; - //! Tangent data - QPersistentModelIndex iTangentData; - //! Does the data need updating? - bool needUpdateData = false; - - //! Skin instance - QPersistentModelIndex iSkin; - //! Skin data - QPersistentModelIndex iSkinData; - //! Skin partition - QPersistentModelIndex iSkinPart; - - void resetSkinning(); - - int numVerts = 0; - - //! Vertices - QVector verts; - //! Normals - QVector norms; - //! Vertex colors - QVector colors; - //! Tangents - QVector tangents; - //! Bitangents - QVector bitangents; - //! UV coordinate sets - QVector coords; - //! Triangles - QVector triangles; - //! Strip points - QVector tristrips; - //! Sorted triangles - QVector sortedTriangles; - - void resetVertexData(); - - //! Is the transform rigid or weighted? - bool transformRigid = true; - //! Transformed vertices - QVector transVerts; - //! Transformed normals - QVector transNorms; - //! Transformed colors (alpha blended) - QVector transColors; - //! Transformed tangents - QVector transTangents; - //! Transformed bitangents - QVector transBitangents; - - //! Toggle for skinning - bool isSkinned = false; - - int skeletonRoot = 0; - Transform skeletonTrans; - QVector bones; - QVector weights; - QVector partitions; - - void resetSkeletonData(); - - //! Holds the name of the shader, or "" if no shader - QString shader = ""; - - //! Shader property - BSShaderLightingProperty * bssp = nullptr; - //! Skyrim shader property - BSLightingShaderProperty * bslsp = nullptr; - //! Skyrim effect shader property - BSEffectShaderProperty * bsesp = nullptr; - - AlphaProperty * alphaProperty = nullptr; - - //! Is shader set to double sided? - bool isDoubleSided = false; - //! Is shader set to animate using vertex alphas? - bool isVertexAlphaAnimation = false; - //! Is "Has Vertex Colors" set to Yes - bool hasVertexColors = false; - - bool depthTest = true; - bool depthWrite = true; - bool drawInSecondPass = false; - bool translucent = false; - - void updateShader(); - - mutable BoundSphere boundSphere; - mutable bool needUpdateBounds = false; - - bool isLOD = false; -}; - //! A mesh class Mesh : public Shape { diff --git a/src/gl/glshape.cpp b/src/gl/glshape.cpp new file mode 100644 index 000000000..cc71b670b --- /dev/null +++ b/src/gl/glshape.cpp @@ -0,0 +1,224 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#include "glshape.h" + +#include "gl/controllers.h" +#include "gl/glscene.h" +#include "model/nifmodel.h" +#include "io/material.h" + +#include +#include + +Shape::Shape( Scene * s, const QModelIndex & b ) : Node( s, b ) +{ + shapeNumber = s->shapes.count(); +} + +void Shape::clear() +{ + Node::clear(); + + resetSkinning(); + resetVertexData(); + resetSkeletonData(); + + transVerts.clear(); + transNorms.clear(); + transColors.clear(); + transTangents.clear(); + transBitangents.clear(); + sortedTriangles.clear(); + + bssp = nullptr; + bslsp = nullptr; + bsesp = nullptr; + alphaProperty = nullptr; + + isLOD = false; + isDoubleSided = false; +} + +void Shape::transform() +{ + if ( needUpdateData ) { + needUpdateData = false; + + auto nif = NifModel::fromValidIndex( iBlock ); + if ( nif ) { + needUpdateBounds = true; // Force update bounds + updateData(nif); + + if ( isVertexAlphaAnimation ) { + int nColors = colors.count(); + for ( int i = 0; i < nColors; i++ ) + colors[i].setRGBA( colors[i].red(), colors[i].green(), colors[i].blue(), 1 ); + } + } else { + clear(); + return; + } + } + + Node::transform(); +} + +void Shape::setController( const NifModel * nif, const QModelIndex & iController ) +{ + QString contrName = nif->itemName(iController); + if ( contrName == "NiGeomMorpherController" ) { + Controller * ctrl = new MorphController( this, iController ); + registerController(nif, ctrl); + } else if ( contrName == "NiUVController" ) { + Controller * ctrl = new UVController( this, iController ); + registerController(nif, ctrl); + } else { + Node::setController( nif, iController ); + } +} + +void Shape::updateImpl( const NifModel * nif, const QModelIndex & index ) +{ + Node::updateImpl( nif, index ); + + if ( index == iBlock ) { + shader = ""; // Reset stored shader so it can reassess conditions + + bslsp = nullptr; + bsesp = nullptr; + bssp = properties.get(); + if ( bssp ) { + auto shaderType = bssp->typeId(); + if ( shaderType == "BSLightingShaderProperty" ) + bslsp = bssp->cast(); + else if ( shaderType == "BSEffectShaderProperty" ) + bsesp = bssp->cast(); + } + + alphaProperty = properties.get(); + + needUpdateData = true; + updateShader(); + + } else if ( isSkinned && (index == iSkin || index == iSkinData || index == iSkinPart) ) { + needUpdateData = true; + + } else if ( (bssp && bssp->isParamBlock(index)) || (alphaProperty && index == alphaProperty->index()) ) { + updateShader(); + + } +} + +void Shape::boneSphere( const NifModel * nif, const QModelIndex & index ) const +{ + Node * root = findParent( 0 ); + Node * bone = root ? root->findChild( bones.value( index.row() ) ) : 0; + if ( !bone ) + return; + + Transform boneT = Transform( nif, index ); + Transform t = scene->hasOption(Scene::DoSkinning) ? viewTrans() : Transform(); + t = t * skeletonTrans * bone->localTrans( 0 ) * boneT; + + auto bSphere = BoundSphere( nif, index ); + if ( bSphere.radius > 0.0 ) { + glColor4f( 1, 1, 1, 0.33f ); + auto pos = boneT.rotation.inverted() * (bSphere.center - boneT.translation); + drawSphereSimple( t * pos, bSphere.radius, 36 ); + } +} + +void Shape::resetSkinning() +{ + isSkinned = false; + iSkin = iSkinData = iSkinPart = QModelIndex(); +} + +void Shape::resetVertexData() +{ + numVerts = 0; + + iData = iTangentData = QModelIndex(); + + verts.clear(); + norms.clear(); + colors.clear(); + coords.clear(); + tangents.clear(); + bitangents.clear(); + triangles.clear(); + tristrips.clear(); +} + +void Shape::resetSkeletonData() +{ + skeletonRoot = 0; + skeletonTrans = Transform(); + + bones.clear(); + weights.clear(); + partitions.clear(); +} + +void Shape::updateShader() +{ + if ( bslsp ) + translucent = (bslsp->alpha < 1.0) || bslsp->hasRefraction; + else if ( bsesp ) + translucent = (bsesp->getAlpha() < 1.0) && !alphaProperty; + else + translucent = false; + + drawInSecondPass = false; + if ( translucent ) + drawInSecondPass = true; + else if ( alphaProperty && (alphaProperty->hasAlphaBlend() || alphaProperty->hasAlphaTest()) ) + drawInSecondPass = true; + else if ( bssp ) { + Material * mat = bssp->getMaterial(); + if ( mat && (mat->hasAlphaBlend() || mat->hasAlphaTest() || mat->hasDecal()) ) + drawInSecondPass = true; + } + + if ( bssp ) { + depthTest = bssp->depthTest; + depthWrite = bssp->depthWrite; + isDoubleSided = bssp->isDoubleSided; + isVertexAlphaAnimation = bssp->isVertexAlphaAnimation; + } else { + depthTest = true; + depthWrite = true; + isDoubleSided = false; + isVertexAlphaAnimation = false; + } +} diff --git a/src/gl/glshape.h b/src/gl/glshape.h new file mode 100644 index 000000000..cf6cc62ec --- /dev/null +++ b/src/gl/glshape.h @@ -0,0 +1,175 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#ifndef GLSHAPE_H +#define GLSHAPE_H + +#include "gl/glnode.h" // Inherited +#include "gl/gltools.h" + +#include +#include +#include + +//! @file glshape.h Shape + +using TriStrip = QVector; +Q_DECLARE_TYPEINFO( TriStrip, Q_MOVABLE_TYPE ); +using TexCoords = QVector; +Q_DECLARE_TYPEINFO( TexCoords, Q_MOVABLE_TYPE ); + +class NifModel; + +class Shape : public Node +{ + friend class MorphController; + friend class UVController; + friend class Renderer; + +public: + Shape( Scene * s, const QModelIndex & b ); + + // IControllable + + void clear() override; + void transform() override; + + // end IControllable + + virtual void drawVerts() const {}; + virtual QModelIndex vertexAt( int ) const { return QModelIndex(); }; + +protected: + int shapeNumber; + + void setController( const NifModel * nif, const QModelIndex & controller ) override; + void updateImpl( const NifModel * nif, const QModelIndex & index ) override; + virtual void updateData( const NifModel* nif ) = 0; + + void boneSphere( const NifModel * nif, const QModelIndex & index ) const; + + //! Shape data + QPersistentModelIndex iData; + //! Tangent data + QPersistentModelIndex iTangentData; + //! Does the data need updating? + bool needUpdateData = false; + + //! Skin instance + QPersistentModelIndex iSkin; + //! Skin data + QPersistentModelIndex iSkinData; + //! Skin partition + QPersistentModelIndex iSkinPart; + + void resetSkinning(); + + int numVerts = 0; + + //! Vertices + QVector verts; + //! Normals + QVector norms; + //! Vertex colors + QVector colors; + //! Tangents + QVector tangents; + //! Bitangents + QVector bitangents; + //! UV coordinate sets + QVector coords; + //! Triangles + QVector triangles; + //! Strip points + QVector tristrips; + //! Sorted triangles + QVector sortedTriangles; + + void resetVertexData(); + + //! Is the transform rigid or weighted? + bool transformRigid = true; + //! Transformed vertices + QVector transVerts; + //! Transformed normals + QVector transNorms; + //! Transformed colors (alpha blended) + QVector transColors; + //! Transformed tangents + QVector transTangents; + //! Transformed bitangents + QVector transBitangents; + + //! Toggle for skinning + bool isSkinned = false; + + int skeletonRoot = 0; + Transform skeletonTrans; + QVector bones; + QVector weights; + QVector partitions; + + void resetSkeletonData(); + + //! Holds the name of the shader, or "" if no shader + QString shader = ""; + + //! Shader property + BSShaderLightingProperty * bssp = nullptr; + //! Skyrim shader property + BSLightingShaderProperty * bslsp = nullptr; + //! Skyrim effect shader property + BSEffectShaderProperty * bsesp = nullptr; + + AlphaProperty * alphaProperty = nullptr; + + //! Is shader set to double sided? + bool isDoubleSided = false; + //! Is shader set to animate using vertex alphas? + bool isVertexAlphaAnimation = false; + //! Is "Has Vertex Colors" set to Yes + bool hasVertexColors = false; + + bool depthTest = true; + bool depthWrite = true; + bool drawInSecondPass = false; + bool translucent = false; + + void updateShader(); + + mutable BoundSphere boundSphere; + mutable bool needUpdateBounds = false; + + bool isLOD = false; +}; + +#endif diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 5226e410e..c6bc6304d 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -34,7 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "message.h" #include "nifskope.h" -#include "gl/glmesh.h" +#include "gl/glshape.h" #include "gl/glproperty.h" #include "gl/glscene.h" #include "gl/gltex.h" diff --git a/src/glview.cpp b/src/glview.cpp index 2b703363e..5526694ab 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -35,7 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "message.h" #include "nifskope.h" #include "gl/renderer.h" -#include "gl/glmesh.h" +#include "gl/glshape.h" #include "gl/gltex.h" #include "model/nifmodel.h" #include "ui/settingsdialog.h" From 4efc97ef36620946369964bcdab2d3732f0ac6d0 Mon Sep 17 00:00:00 2001 From: gavrant Date: Fri, 14 Jul 2023 19:19:51 +0300 Subject: [PATCH 061/118] Model refactoring, part 1: NifItem Main goals of the whole model refactoring: - For NifItem and *Model (BaseModel, NifModel, etc.) methods with a QString parameter provide QLatin1String and 'const char *' overloads. QLatin1String("foo") is much faster than "foo" with its under-the-hood conversion to QString because in Qt 5 QLatin1String("foo") could be done at compile time. And this will make something like reading Skyrim SE shapes several (4 to 8) times faster. - For *Model methods working with QModelIndex child items provide convenient overloads to work with 'NifItem *' instead. Overall, gradually migrate from using QModelIndex to 'NifItem *', at least on low level, to avoid back-and-forth conversions between QModelIndex and 'NifItem *' and to simplify the code overall. Bonuses in this commit: - NifItem: name(), type(), ... getters now return 'const QString &' instead of QString, to avoid wasting resources on creating a QString copy when it's not needed. - NifItem: new hasName and hasType methods as a replacement for 'name() == "foo"' and 'type() == "foo"' ('const char *' overloads - for performance (see main goal #1), QString and QLatin1String - for uniformity). - NifItem: new get method as a shortcut for this->itemData.value.get(). - NifItem: static versions of get and getArray methods, with a nullptr check for the item. --- src/data/nifitem.h | 71 +++++++++++++++++++++++++++-------------- src/model/basemodel.cpp | 4 +-- src/model/nifmodel.cpp | 47 +++++++++++++-------------- src/spells/blocks.cpp | 2 +- 4 files changed, 73 insertions(+), 51 deletions(-) diff --git a/src/data/nifitem.h b/src/data/nifitem.h index a27f1fa89..d37fee81c 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -448,23 +448,21 @@ class NifItem } //! Return the child item with the specified name - NifItem * child( const QString & name ) + // Does NOT evaluate children's conditions! + const NifItem * child( const QLatin1String & name ) const { - for ( NifItem * child : childItems ) { - if ( child->name() == name ) + for ( const NifItem * child : childItems ) { + if ( child && child->hasName(name) ) return child; } return nullptr; } //! Return the child item with the specified name - const NifItem * child( const QString & name ) const + // Does NOT evaluate children's conditions! + inline const NifItem * child( const char * name ) const { - for ( const NifItem * child : childItems ) { - if ( child->name() == name ) - return child; - } - return nullptr; + return child( QLatin1String(name) ); } //! Return a count of the number of child items @@ -605,36 +603,36 @@ class NifItem inline NifValue & value() { return itemData.value; } //! Return the name of the data - inline QString name() const { return itemData.name(); } + inline const QString & name() const { return itemData.name(); } //! Return the type of the data - inline QString type() const { return itemData.type(); } + inline const QString & type() const { return itemData.type(); } //! Return the template type of the data - inline QString temp() const { return itemData.temp(); } + inline const QString & temp() const { return itemData.temp(); } //! Return the argument attribute of the data - inline QString arg() const { return itemData.arg(); } + inline const QString & arg() const { return itemData.arg(); } //! Return the first array length of the data - inline QString arr1() const { return itemData.arr1(); } + inline const QString & arr1() const { return itemData.arr1(); } //! Return the second array length of the data - inline QString arr2() const { return itemData.arr2(); } + inline const QString & arr2() const { return itemData.arr2(); } //! Return the condition attribute of the data - inline QString cond() const { return itemData.cond(); } + inline const QString & cond() const { return itemData.cond(); } //! Return the earliest version attribute of the data - inline quint32 ver1() const { return itemData.ver1(); } + inline quint32 ver1() const { return itemData.ver1(); } //! Return the latest version attribute of the data - inline quint32 ver2() const { return itemData.ver2(); } + inline quint32 ver2() const { return itemData.ver2(); } //! Return the description text of the data - inline QString text() const { return itemData.text(); } + inline const QString & text() const { return itemData.text(); } //! Return the condition attribute of the data, as an expression inline const NifExpr & argexpr() const { return itemData.argexpr(); } //! Return the condition attribute of the data, as an expression - inline const NifExpr & condexpr() const { return itemData.condexpr(); } + inline const NifExpr & condexpr() const { return itemData.condexpr(); } //! Return the arr1 attribute of the data, as an expression - inline const NifExpr & arr1expr() const { return itemData.arr1expr(); } + inline const NifExpr & arr1expr() const { return itemData.arr1expr(); } //! Return the version condition attribute of the data - inline QString vercond() const { return itemData.vercond(); } + inline QString vercond() const { return itemData.vercond(); } //! Return the version condition attribute of the data, as an expression - inline const NifExpr & verexpr() const { return itemData.verexpr(); } + inline const NifExpr & verexpr() const { return itemData.verexpr(); } //! Return the abstract attribute of the data inline bool isAbstract() const { return itemData.isAbstract(); } //! Is the item data binary. Binary means the data is being treated as one blob. @@ -650,6 +648,22 @@ class NifItem //! Is the item data conditionless. Conditionless means no expression evaluation is necessary. inline bool isConditionless() const { return itemData.isConditionless(); } + //! Does the item's name matches testName? + inline bool hasName( const QString & testName ) const { return itemData.name() == testName; } + //! Does the item's name matches testName? + inline bool hasName( const QLatin1String & testName ) const { return itemData.name() == testName; } + //! Does the item's name matches testName? + // item->hasName("Foo") is much faster than item->name() == "Foo" + inline bool hasName( const char * testName ) const { return itemData.name() == QLatin1String(testName); } + + //! Does the item's name matches testName? + inline bool hasType( const QString & testName ) const { return itemData.type() == testName; } + //! Does the item's name matches testName? + inline bool hasType( const QLatin1String & testName ) const { return itemData.type() == testName; } + //! Does the item's name matches testName? + // item->hasType("Foo") is much faster than item->type() == "Foo" + inline bool hasType( const char * testName ) const { return itemData.type() == QLatin1String(testName); } + //! Set the name inline void setName( const QString & name ) { itemData.setName( name ); } //! Set the type @@ -681,6 +695,15 @@ class NifItem return ( ( ver1() == 0 || ver1() <= v ) && ( ver2() == 0 || v <= ver2() ) ); } + //! Get the value of an item if it's not nullptr + template static inline T get( const NifItem * item ) { return item ? item->get() : T(); } + + //! Get the value of the item + template inline T get() const { return itemData.value.get(); } + + //! Get the child items of arrayRoot as an array if arrayRoot is not nullptr + template static inline QVector getArray( const NifItem * arrayRoot ) { return arrayRoot ? arrayRoot->getArray() : QVector(); } + //! Get the child items as an array template QVector getArray() const { @@ -689,7 +712,7 @@ class NifItem if ( nSize > 0 ) { array.reserve( nSize ); for ( NifItem * child : childItems ) { - array.append( child->itemData.value.get() ); + array.append( NifItem::get( child ) ); } } return array; diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 751c404a9..f3f555148 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -706,7 +706,7 @@ NifItem * BaseModel::getItem( NifItem * item, const QString & name ) const for ( int c = 0; c < item->childCount(); c++ ) { NifItem * child = item->child( c ); - if ( child->name() == name && evalCondition( child ) ) + if ( child->hasName(name) && evalCondition( child ) ) return child; } @@ -726,7 +726,7 @@ NifItem * BaseModel::getItemX( NifItem * item, const QString & name ) const for ( int c = item->row() - 1; c >= 0; c-- ) { NifItem * child = parent->child( c ); - if ( child && child->name() == name && evalCondition( child ) ) + if ( child && child->hasName(name) && evalCondition( child ) ) return child; } diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 945ae5773..3096c4742 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -350,14 +350,13 @@ QString NifModel::createRTTIName( const NifItem * block ) const if ( !block ) return {}; - QString blockName = block->name(); - if ( blockName == QLatin1String( "NiDataStream" ) ) { - blockName = QString( "NiDataStream\x01%1\x01%2" ) - .arg( block->child( "Usage" )->value().get() ) - .arg( block->child( "Access" )->value().get() ); + if ( block->hasName("NiDataStream") ) { + return QString( "NiDataStream\x01%1\x01%2" ) + .arg( NifItem::get( block->child("Usage") ) ) + .arg( NifItem::get( block->child("Access") ) ); } - return blockName; + return block->name(); } QModelIndex NifModel::getHeader() const @@ -488,7 +487,7 @@ NifItem * NifModel::getItem( NifItem * item, const QString & name ) const //} for ( auto child : item->children() ) { - if ( child && child->name() == name && evalCondition( child ) ) + if ( child && child->hasName(name) && evalCondition( child ) ) return child; } @@ -783,7 +782,7 @@ void NifModel::updateStrings( NifModel * src, NifModel * tgt, NifItem * item ) NifValue::Type vt = item->value().type(); - if ( vt == NifValue::tStringIndex || vt == NifValue::tSizedString || item->type() == "string" ) { + if ( vt == NifValue::tStringIndex || vt == NifValue::tSizedString || item->hasType("string") ) { QString str = src->string( src->createIndex( 0, 0, item ) ); tgt->assignString( tgt->createIndex( 0, 0, item ), str, false ); } @@ -994,7 +993,7 @@ bool NifModel::isNiBlock( const QModelIndex & index, const QString & name ) cons if ( name.isEmpty() ) return true; - return item->name() == name; + return item->hasName(name); } return false; @@ -1228,7 +1227,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const else if ( isArray(item->parent()) ) { auto arrayName = arrayPseudonyms.value(iname); if ( arrayName.isEmpty() ) { - if (iname == "UV Sets") + if ( item->hasName("UV Sets") ) arrayName = QString( (item->value().type() == NifValue::tVector2) ? "UV" : "UV Set" ); else arrayName = iname; @@ -1332,7 +1331,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const { QString optId = NifValue::enumOptionName( item->type(), value.toCount() ); - if ( item->type() == "BSVertexDesc" ) + if ( item->hasType("BSVertexDesc") ) return BSVertexDesc(value.toCount()).toString(); if ( optId.isEmpty() ) @@ -1629,19 +1628,19 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r // reverse buddy lookup if ( index.column() == ValueCol ) { - if ( item->name() == "File Name" ) { + if ( item->hasName("File Name") ) { NifItem * parent = item->parent(); - if ( parent && ( parent->name() == "Texture Source" || parent->name() == "NiImage" ) ) { + if ( parent && ( parent->hasName("Texture Source") || parent->hasName("NiImage") ) ) { parent = parent->parent(); - if ( parent && parent->type() == "NiBlock" && parent->name() == "NiSourceTexture" ) + if ( parent && parent->hasType("NiBlock") && parent->hasName("NiSourceTexture") ) emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); } - } else if ( item->name() == "Name" ) { + } else if ( item->hasName("Name") ) { NifItem * parent = item->parent(); - if ( parent && parent->type() == "NiBlock" ) + if ( parent && parent->hasType("NiBlock") ) emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); } } @@ -1700,11 +1699,11 @@ QModelIndex NifModel::buddy( const QModelIndex & index ) const return QModelIndex(); QModelIndex buddy; - if ( index.column() == ValueCol && item->parent() == root && item->type() == "NiBlock" ) { + if ( index.column() == ValueCol && item->parent() == root && item->hasType("NiBlock") ) { - if ( item->name() == "NiSourceTexture" || item->name() == "NiImage" ) { + if ( item->hasName("NiSourceTexture") || item->hasName("NiImage") ) { buddy = getIndex( index, "File Name" ); - } else if ( item->name() == "NiStringExtraData" ) { + } else if ( item->hasName("NiStringExtraData") ) { buddy = getIndex( index, "String Data" ); } else { buddy = getIndex( index, "Name" ); @@ -1717,7 +1716,7 @@ QModelIndex NifModel::buddy( const QModelIndex & index ) const return buddy; } else if ( index.column() == ValueCol && item->parent() != root ) { - if ( item->type() == "ControlledBlock" && item->name() == "Controlled Blocks" ) { + if ( item->hasType("ControlledBlock") && item->hasName("Controlled Blocks") ) { if ( version >= 0x14010003 ) { buddy = getIndex( index, "Node Name" ); } else if ( version <= 0x14000005 ) { @@ -2282,7 +2281,7 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) // Be advised, getBSVersion returns 0 if it's the file's header that is being loaded. // Though for shader properties loadItem happens after the header is fully processed, so the check below should work w/o issues. if ( getBSVersion() == 155 && parent->parent() == root ) { - if ( parent->name() == "BSLightingShaderProperty" || parent->name() == "BSEffectShaderProperty" ) + if ( parent->hasName("BSLightingShaderProperty") || parent->hasName("BSEffectShaderProperty") ) stopRead = true; } @@ -2310,7 +2309,7 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) } } - if ( stopRead && child->name() == "Name" ) { + if ( stopRead && child->hasName("Name") ) { auto idx = child->value().get(); if ( idx != -1 ) { NifItem * header = getHeaderItem(); @@ -2319,7 +2318,7 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) } } - if ( stopRead && child->name() == "Controller" && !name.isEmpty() ) + if ( stopRead && child->hasName("Controller") && !name.isEmpty() ) break; } @@ -2899,7 +2898,7 @@ bool NifModel::assignString( NifItem * item, const QString & string, bool replac idx = get( pItem = item ); break; case NifValue::tSizedString: - if ( item->type() == "string" ) { + if ( item->hasType("string") ) { pItem = item; idx = -1; break; diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 47bff239d..683bd32ec 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -858,7 +858,7 @@ class spAddNewRef final : public Spell if ( nif->inherits( type, "NiTimeController" ) ) { // Show only applicable types for controller links for the given block - if ( nif->inherits( iBlock, "NiTimeController" ) && item->name() == "Next Controller" ) + if ( nif->inherits( iBlock, "NiTimeController" ) && item->hasName("Next Controller") ) iBlock = nif->getBlock( nif->getLink( index.parent(), "Target" ) ); if ( nif->getVersionNumber() > 0x14050000 ) { From 83fa4ea37183c1c5456dd4fd21f32c05334c503f Mon Sep 17 00:00:00 2001 From: gavrant Date: Fri, 14 Jul 2023 23:38:25 +0300 Subject: [PATCH 062/118] BaseModel::isArray(NifItem * item) now accepts a const pointer + added nullptr check. --- src/model/basemodel.cpp | 6 ++++-- src/model/basemodel.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index f3f555148..6cdec1be5 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -148,9 +148,11 @@ bool BaseModel::isArray( const QModelIndex & index ) const return !itemArr1( index ).isEmpty(); } -bool BaseModel::isArray( NifItem * item ) const +bool BaseModel::isArray( const NifItem * item ) const { - return item->isArray() || (item->parent() && item->parent()->isMultiArray()); + if ( !item ) + return false; + return item->isArray() || ( item->parent() && item->parent()->isMultiArray() ); } int BaseModel::getArraySize( NifItem * array ) const diff --git a/src/model/basemodel.h b/src/model/basemodel.h index fa0d50e6b..1ccc6c1fd 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -153,7 +153,7 @@ class BaseModel : public QAbstractItemModel * @param array The item to check. * @return true if the index is an array. */ - bool isArray( NifItem * item ) const; + bool isArray( const NifItem * item ) const; /*! Update the size of an array (append or remove items). * From 1c927d2e02514c960e17dfce0b3634c368d8cda2 Mon Sep 17 00:00:00 2001 From: gavrant Date: Mon, 24 Jul 2023 10:05:47 +0300 Subject: [PATCH 063/118] Fixed a crash on calling Message::append (message box with "Show Details...") if it has been called before in this session and that message box has been closed with "X" button instead of "OK". Bonuses for Message::append: - Set the minimum width of the message box to about a quarter of the screen resolution. - The details are shown by default now (saves one click on "Show Details..."). - For each main window of NifSkope, its own message box is now created instead of dumping messages from all main windows into a single box. --- src/message.cpp | 93 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/src/message.cpp b/src/message.cpp index 0f4d0abc6..58a56f303 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include Q_LOGGING_CATEGORY( ns, "nifskope" ) @@ -135,49 +137,112 @@ void Message::info( QWidget * parent, const QString & str, const QString & err ) * */ -static QMap messageBoxes; +class DetailsMessageBox : public QMessageBox +{ +public: + explicit DetailsMessageBox( QWidget * parent, const QString & txt ) + : QMessageBox( parent ), m_key( txt ) { } + + ~DetailsMessageBox(); + + const QString & key() const { return m_key; } + +protected: + void closeEvent( QCloseEvent * event ) override; + + QString m_key; +}; + +static QVector messageBoxes; + +void unregisterMessageBox( DetailsMessageBox * msgBox ) +{ + messageBoxes.removeOne( msgBox ); +} void Message::append( QWidget * parent, const QString & str, const QString & err, QMessageBox::Icon icon ) { if ( !parent ) parent = qApp->activeWindow(); - // Create one box per error string, accumulate messages - auto box = messageBoxes[str]; - if ( box ) { + // Create one box per parent widget and error string, accumulate messages + DetailsMessageBox * msgBox = nullptr; + for ( auto box : messageBoxes ) { + if ( box->parentWidget() == parent && box->key() == str ) { + msgBox = box; + break; + } + } + + if ( msgBox ) { // Append strings to existing message box's Detailed Text // Show box if it has been closed before - box->show(); - box->setDetailedText( box->detailedText().append( err + "\n" ) ); + msgBox->show(); + if ( !err.isEmpty() ) + msgBox->setDetailedText( msgBox->detailedText().append( err + "\n" ) ); + } else { // Create new message box - auto msgBox = new QMessageBox( parent ); + msgBox = new DetailsMessageBox( parent, str ); + messageBoxes.append( msgBox ); + msgBox->setAttribute( Qt::WA_DeleteOnClose ); msgBox->setWindowModality( Qt::NonModal ); msgBox->setWindowFlags( msgBox->windowFlags() | Qt::Tool ); + // Set the min. width of the label containing str to a quarter of the screen resolution. + // This makes the detailed text more readable even when str is short. + auto screen = QGuiApplication::primaryScreen(); + if ( screen ) { + msgBox->setStyleSheet( + QString(" QLabel[objectName^=\"qt_msgbox_label\"]{min-width: %1px;}" ) + .arg ( screen->size().width() / 4 ) + ); + } + msgBox->setText( str ); msgBox->setIcon( icon ); - msgBox->setDetailedText( err + "\n" ); - msgBox->show(); - msgBox->activateWindow(); + if ( !err.isEmpty() ) { + msgBox->setDetailedText( err + "\n" ); + + // Auto-show detailed text on first show. + // https://stackoverflow.com/questions/36083551/qmessagebox-show-details + for ( auto btn : msgBox->buttons() ) { + if ( msgBox->buttonRole( btn ) == QMessageBox::ActionRole) { + btn->click(); // "Click" it to expand the detailed text + break; + } + } + } - messageBoxes[str] = msgBox; + msgBox->show(); + msgBox->activateWindow(); // Clear Detailed Text with each confirmation - connect( msgBox, &QMessageBox::buttonClicked, [msgBox, str]( QAbstractButton * button ) { + connect( msgBox, &QMessageBox::buttonClicked, [msgBox]( QAbstractButton * button ) { Q_UNUSED( button ); - if ( messageBoxes.contains( str ) ) - messageBoxes.remove( str ); + unregisterMessageBox( msgBox ); } ); } } + void Message::append( const QString & str, const QString & err, QMessageBox::Icon icon ) { append( nullptr, str, err, icon ); } +DetailsMessageBox::~DetailsMessageBox() +{ + unregisterMessageBox( this ); // Just in case if buttonClicked or closeEvent fail +} + +void DetailsMessageBox::closeEvent( QCloseEvent * event ) { + + QMessageBox::closeEvent(event); + if ( event->isAccepted() ) + unregisterMessageBox( this ); +} /* From 06b461dab37544b3314e070a7e9d5bbf7793b759 Mon Sep 17 00:00:00 2001 From: gavrant Date: Sun, 30 Jul 2023 10:55:17 +0300 Subject: [PATCH 064/118] Model refactoring, part 2: carpet bombing - NifItem: Reworked cached data (row index, child link items) to make it more reliably updated. - NifItem: Added a lot of shortcut functions to its itemData.value methods. - BaseModel/NifModel/KfmModel: Expanded and standardized overloads for various item getters, setters and checkers. With putting more emphasis on working with NifItem * instead of QModelIndex and with some string tricks (const char * -> QLatin1String), it should result in a smaller and faster code overall. - Many NifItem getters and setters now show a message box if they detect a problem instead of silently returning a default value or false. This is more for debug purposes, to catch and polish any rough spots in the code. - Got rid of a bunch of repeating code (for example, in item condition evaluation). - Gave some functions better names (for example, updateArray -> updateArraySize). --- NifSkope.pro | 1 + src/data/nifitem.cpp | 150 +++ src/data/nifitem.h | 561 ++++++----- src/data/nifvalue.cpp | 168 +++- src/data/nifvalue.h | 389 ++++---- src/gl/bsshape.cpp | 20 +- src/gl/controllers.cpp | 30 +- src/gl/glcontroller.cpp | 20 +- src/gl/glmesh.cpp | 36 +- src/gl/glnode.cpp | 56 +- src/gl/glparticles.cpp | 4 +- src/gl/glproperty.cpp | 6 +- src/gl/glscene.cpp | 20 +- src/gl/gltex.cpp | 4 +- src/gl/gltexloaders.cpp | 18 +- src/gl/gltools.cpp | 6 +- src/gl/renderer.cpp | 32 +- src/glview.cpp | 14 +- src/lib/importex/3ds.cpp | 20 +- src/lib/importex/col.cpp | 32 +- src/lib/importex/obj.cpp | 84 +- src/model/basemodel.cpp | 663 +++++++------ src/model/basemodel.h | 781 ++++++++++++--- src/model/kfmmodel.cpp | 113 +-- src/model/kfmmodel.h | 6 +- src/model/nifdelegate.cpp | 7 +- src/model/nifmodel.cpp | 1698 +++++++++++++------------------- src/model/nifmodel.h | 1295 ++++++++++++++++++++---- src/model/nifproxymodel.cpp | 2 +- src/model/undocommands.cpp | 4 +- src/nifskope.cpp | 6 +- src/nifskope_ui.cpp | 8 +- src/spellbook.cpp | 2 +- src/spells/animation.cpp | 44 +- src/spells/blocks.cpp | 138 +-- src/spells/bounds.cpp | 2 +- src/spells/color.cpp | 4 +- src/spells/flags.cpp | 64 +- src/spells/fo3only.cpp | 8 +- src/spells/havok.cpp | 66 +- src/spells/headerstring.cpp | 17 +- src/spells/light.cpp | 6 +- src/spells/materialedit.cpp | 4 +- src/spells/mesh.cpp | 56 +- src/spells/misc.cpp | 24 +- src/spells/moppcode.cpp | 10 +- src/spells/morphctrl.cpp | 14 +- src/spells/normals.cpp | 10 +- src/spells/optimize.cpp | 66 +- src/spells/sanitize.cpp | 75 +- src/spells/skeleton.cpp | 101 +- src/spells/stringpalette.cpp | 16 +- src/spells/strippify.cpp | 54 +- src/spells/tangentspace.cpp | 26 +- src/spells/texture.cpp | 78 +- src/spells/transform.cpp | 18 +- src/ui/widgets/inspect.cpp | 2 +- src/ui/widgets/nifeditors.cpp | 64 +- src/ui/widgets/nifview.cpp | 70 +- src/ui/widgets/nifview.h | 4 + src/ui/widgets/refrbrowser.cpp | 4 +- src/ui/widgets/uvedit.cpp | 44 +- src/ui/widgets/valueedit.cpp | 82 +- src/ui/widgets/xmlcheck.cpp | 19 +- src/xml/nifxml.cpp | 4 +- 65 files changed, 4417 insertions(+), 3033 deletions(-) create mode 100644 src/data/nifitem.cpp diff --git a/NifSkope.pro b/NifSkope.pro index 2f157529d..7e3076037 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -208,6 +208,7 @@ HEADERS += \ lib/half.h SOURCES += \ + src/data/nifitem.cpp \ src/data/niftypes.cpp \ src/data/nifvalue.cpp \ src/gl/bsshape.cpp \ diff --git a/src/data/nifitem.cpp b/src/data/nifitem.cpp new file mode 100644 index 000000000..f5f16120b --- /dev/null +++ b/src/data/nifitem.cpp @@ -0,0 +1,150 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#include "nifitem.h" +#include "model/basemodel.h" + +void NifItem::registerChild( NifItem * item, int at ) +{ + int nOldChildren = childItems.count(); + if ( at < 0 || at >= nOldChildren ) { + at = nOldChildren; + childItems.append( item ); + item->rowIdx = at; + updateLinkCache( at, false ); + } else { + childItems.insert( at, item ); + item->rowIdx = at; + updateChildRows( at + 1 ); + updateLinkCache( at, true ); + } +} + +NifItem * NifItem::unregisterChild( int at ) +{ + if ( at >= 0 && at < childItems.count() ) { + NifItem * item = childItems.at( at ); + childItems.remove( at ); + updateChildRows( at ); + updateLinkCache( at, true ); + return item; + } + + return nullptr; +} + +void NifItem::registerInParentLinkCache() +{ + NifItem * c = this; + NifItem * p = parentItem; + while( p ) { + bool bOldHasChildLinks = p->hasChildLinks(); + p->linkAncestorRows.append( c->row() ); + if ( bOldHasChildLinks ) + break; // Do NOT register p in its parent (again) if c is NOT a first registered child link for p + c = p; + p = c->parentItem; + } +} + +void NifItem::unregisterInParentLinkCache() +{ + NifItem * c = this; + NifItem * p = parentItem; + while( p ) { + int iRemove = p->linkAncestorRows.indexOf( c->row() ); + if ( iRemove < 0 ) + break; // c is not even registered in p... + p->linkAncestorRows.remove( iRemove ); + if ( p->hasChildLinks() ) + break; // Do NOT unregister p in its parent if p still has other registered child links + c = p; + p = c->parentItem; + } +} + +static void cleanupChildIndexVector( QVector v, int iStartChild ) +{ + for ( int i = v.count() - 1; i >= 0; i-- ) { + if ( v.at(i) >= iStartChild ) + v.remove( i ); + } +} + +void NifItem::updateLinkCache( int iStartChild, bool bDoCleanup ) +{ + bool bOldHasChildLinks = hasChildLinks(); + + // Clear outdated links + if ( bDoCleanup ) { + cleanupChildIndexVector( linkRows, iStartChild ); + cleanupChildIndexVector( linkAncestorRows, iStartChild ); + } + + // Add new links + for ( int i = iStartChild; i < childItems.count(); i++ ) { + const NifItem * c = childItems.at( i ); + if ( c->valueIsLink() ) + linkRows.append( i ); + if ( c->hasChildLinks() ) + linkAncestorRows.append( i ); + } + + // Update parent link caches if needed + if ( hasChildLinks() ) { + if ( !bOldHasChildLinks ) + registerInParentLinkCache(); + } else { // not hasChildLinks + if ( bOldHasChildLinks ) + unregisterInParentLinkCache(); + } +} + +void NifItem::onParentItemChange() +{ + parentModel = parentItem->parentModel; + vercondStatus = -1; + conditionStatus = -1; + + for ( NifItem * c : childItems ) + c->onParentItemChange(); +} + +void NifItem::reportError( const QString & msg ) const +{ + parentModel->reportError( this, msg ); +} + +void NifItem::reportError( const QString & funcName, const QString & msg ) const +{ + parentModel->reportError( this, funcName, msg ); +} diff --git a/src/data/nifitem.h b/src/data/nifitem.h index d37fee81c..cf0d51b5f 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -245,6 +245,47 @@ class NifData //! Sets the mixin data flag. Mixin is a specialized compound which creates no nesting. inline void setIsMixin( bool flag ) { setFlag( NifSharedData::Mixin, flag ); } + //! Gets the data's value type (NifValue::Type). + inline NifValue::Type valueType() const { return value.type(); } + //! Check if the type of the data's value is a color type (Color3 or Color4 in xml). + inline bool valueIsColor() const { return value.isColor(); } + //! Check if the type of the data's value is a count. + inline bool valueIsCount() const { return value.isCount(); } + //! Check if the type of the data's value is a flag type (Flags in xml). + inline bool valueIsFlags() const { return value.isFlags(); } + //! Check if the type of the data's value is a float type (Float in xml). + inline bool valueIsFloat() const { return value.isFloat(); } + //! Check if the type of the data's value is of a link type (Ref or Ptr in xml). + inline bool valueIsLink() const { return value.isLink(); } + //! Check if the type of the data's value is a 3x3 matrix type (Matrix33 in xml). + inline bool valueIsMatrix() const { return value.isMatrix(); } + //! Check if the type of the data's value is a 4x4 matrix type (Matrix44 in xml). + inline bool valueIsMatrix4() const { return value.isMatrix4(); } + //! Check if the type of the data's value is a quaternion type. + inline bool valueIsQuat() const { return value.isQuat(); } + //! Check if the type of the data's value is a string type. + inline bool valueIsString() const { return value.isString(); } + //! Check if the type of the data's value is a Vector 4. + inline bool valueIsVector4() const { return value.isVector4(); } + //! Check if the type of the data's value is a Vector 3. + inline bool valueIsVector3() const { return value.isVector3(); } + //! Check if the type of the data's value is a Half Vector3. + inline bool valueIsHalfVector3() const { return value.isHalfVector3(); } + //! Check if the type of the data's value is a Byte Vector3. + inline bool valueIsByteVector3() const { return value.isByteVector3(); } + //! Check if the type of the data's value is a HalfVector2. + inline bool valueIsHalfVector2() const { return value.isHalfVector2(); } + //! Check if the type of the data's value is a Vector 2. + inline bool valueIsVector2() const { return value.isVector2(); } + //! Check if the type of the data's value is a triangle type. + inline bool valueIsTriangle() const { return value.isTriangle(); } + //! Check if the type of the data's value is a byte array. + inline bool valueIsByteArray() const { return value.isByteArray(); } + //! Check if the type of the data's value is a File Version. + inline bool valueIsFileVersion() const { return value.isFileVersion(); } + //! Check if the type of the data's value is a byte matrix. + inline bool valueIsByteMatrix() const { return value.isByteMatrix(); } + protected: //! The internal shared data. QSharedDataPointer d; @@ -273,31 +314,34 @@ struct NifBlock class NifItem { public: - NifItem( NifItem * parent ) - : parentItem( parent ) {} + NifItem( BaseModel * model, NifItem * parent ) + : parentModel( model ), parentItem( parent ) {} - NifItem( const NifData & data, NifItem * parent ) - : itemData( data ), parentItem( parent ) {} + NifItem( BaseModel * model, const NifData & data, NifItem * parent ) + : parentModel( model ), itemData( data ), parentItem( parent ) {} ~NifItem() { qDeleteAll( childItems ); } + //! Return the parent model. + const BaseModel * model() const { return parentModel; } + + //! Return the parent model. + BaseModel * model() { return parentModel; } + //! Return the parent item. - NifItem * parent() const - { - return parentItem; - } + const NifItem * parent() const { return parentItem; } + + //! Return the parent item. + NifItem * parent() { return parentItem; } //! Return the row that this item is at. int row() const { - if ( !parentItem ) - return 0; - if ( rowIdx < 0 ) - rowIdx = parentItem->childItems.indexOf( const_cast(this) ); + rowIdx = parentItem ? parentItem->childItems.indexOf( const_cast(this) ) : 0; return rowIdx; } @@ -311,12 +355,52 @@ class NifItem childItems.reserve( childItems.count() + e ); } - //! Get child items - const QVector & children() + template struct _ChildIterator { - return childItems; - } + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + + _ChildIterator(NifItem * const * ptr) : m_ptr( const_cast( ptr ) ) {} + + T & operator *() const { return *m_ptr; } + T * operator ->() { return m_ptr; } + _ChildIterator & operator++() { m_ptr++; return *this; } + _ChildIterator operator++( int ) { _ChildIterator tmp = *this; ++(*this); return tmp; } + friend bool operator==( const _ChildIterator & a, const _ChildIterator & b ) { return a.m_ptr == b.m_ptr; }; + friend bool operator!=( const _ChildIterator & a, const _ChildIterator & b ) { return a.m_ptr != b.m_ptr; }; + + private: + T * m_ptr; + }; + + template struct ChildIterator + { + ChildIterator( const QVector & children ) : m_children( children ) {} + + _ChildIterator begin() { return _ChildIterator(m_children.begin()); } + _ChildIterator end() { return _ChildIterator(m_children.end()); } + + private: + const QVector & m_children; + }; + const QVector & childIter() { return childItems; } + + ChildIterator childIter() const { return ChildIterator(childItems); } + + + //! Get child items + const QVector & children() { return childItems; } + + //! Return a count of the number of child items + int childCount() const { return childItems.count(); } + +private: + void registerChild( NifItem * item, int at ); + + NifItem * unregisterChild( int at ); + +public: /*! Insert child data item * * @param data The data to insert @@ -325,21 +409,23 @@ class NifItem */ NifItem * insertChild( const NifData & data, int at = -1 ) { - NifItem * item = new NifItem( data, this ); - - if ( data.isConditionless() ) - item->setCondition( true ); - - if ( at < 0 || at > childItems.count() ) { - childItems.append( item ); - } else { - if ( at < childItems.size() - 1 ) - invalidateRowCounts(); - childItems.insert( at, item ); - } - - populateLinksUp( item ); + NifItem * item = new NifItem( parentModel, data, this ); + registerChild( item, at ); + return item; + } + /*! Insert child data item with forcing a value type + * + * @param data The data to insert + * @param forceVType The value type to force + * @param at The position to insert at; append if not specified + * @return An item containing the inserted data + */ + NifItem * insertChild( const NifData & data, NifValue::Type forceVType, int at = -1 ) + { + NifItem * item = new NifItem( parentModel, data, this ); + item->valueChangeType( forceVType ); + registerChild( item, at ); return item; } @@ -351,40 +437,15 @@ class NifItem */ int insertChild( NifItem * child, int at = -1 ) { - child->parentItem = this; + if ( child ) { + child->parentItem = this; + child->onParentItemChange(); + registerChild( child, at ); - if ( at < 0 || at > childItems.count() ) { - childItems.append( child ); - } else { - invalidateRowCounts(); - childItems.insert( at, child ); + return child->row(); } - populateLinksUp( child ); - - return child->row(); - } - - //! Inform the parent and its ancestors of any links - void populateLinksUp( NifItem * item ) - { - if ( item->value().type() == NifValue::tLink || item->value().type() == NifValue::tUpLink ) { - // Add this child's row to the item's link vector - linkRows << item->row(); - - // Inform the parent that this item's rows have links - auto p = parentItem; - auto c = this; - while ( p ) { - // Add this item's row to the parent item - if ( !p->linkAncestorRows.contains( c->row() ) ) - p->linkAncestorRows << c->row(); - - // Recurse up - c = p; - p = p->parentItem; - } - } + return -1; } /*! Take child item at row @@ -394,13 +455,11 @@ class NifItem */ NifItem * takeChild( int row ) { - NifItem * item = child( row ); - invalidateRowCounts(); + NifItem * item = unregisterChild( row ); if ( item ) { - childItems.remove( row ); - item->parentItem = 0; + item->parentItem = nullptr; + item->invalidateRow(); } - return item; } @@ -410,12 +469,9 @@ class NifItem */ void removeChild( int row ) { - NifItem * item = child( row ); - invalidateRowCounts(); - if ( item ) { - childItems.remove( row ); + NifItem * item = unregisterChild( row ); + if ( item ) delete item; - } } /*! Remove several child items @@ -425,178 +481,104 @@ class NifItem */ void removeChildren( int row, int count ) { - invalidateRowCounts(); - for ( int c = row; c < row + count; c++ ) { - NifItem * item = childItems.value( c ); - if ( item ) - delete item; + int iStart = std::max( row, 0 ); + int iEnd = std::min( row + count, childItems.count() ); + if ( iStart < iEnd ) { + for ( int i = iStart; i < iEnd; i++ ) { + NifItem * item = childItems.at( i ); + if ( item ) + delete item; + } + childItems.remove( iStart, iEnd - iStart ); + updateChildRows( iStart ); + updateLinkCache( iStart, true ); } - - childItems.remove( row, count ); } //! Return the child item at the specified row - NifItem * child( int row ) - { - return childItems.value( row ); - } + NifItem * child( int row ) { return childItems.value( row ); } //! Return the child item at the specified row - const NifItem * child( int row ) const - { - return childItems.value( row ); - } - - //! Return the child item with the specified name - // Does NOT evaluate children's conditions! - const NifItem * child( const QLatin1String & name ) const - { - for ( const NifItem * child : childItems ) { - if ( child && child->hasName(name) ) - return child; - } - return nullptr; - } - - //! Return the child item with the specified name - // Does NOT evaluate children's conditions! - inline const NifItem * child( const char * name ) const - { - return child( QLatin1String(name) ); - } - - //! Return a count of the number of child items - int childCount() const - { - return childItems.count(); - } + const NifItem * child( int row ) const { return childItems.value( row ); } //! Remove all child items void killChildren() { qDeleteAll( childItems ); childItems.clear(); - } - const QVector & getLinkAncestorRows() const - { - return linkAncestorRows; - } - - const QVector & getLinkRows() const - { - return linkRows; - } - - //! Conditions for each child in the array (if fixed) - const QVector & arrayConditions() - { - return arrConds; - } - - //! Reset array conditions based on size of children - void resetArrayConditions() - { - if ( childItems.isEmpty() ) - return; - - arrConds.clear(); - arrConds.resize( childItems.at( 0 )->childCount() ); - arrConds.fill( false ); - } - - //! Reset array conditions based on provided size - void resetArrayConditions( int size ) - { - arrConds.clear(); - arrConds.resize( size ); - arrConds.fill( false ); + if ( hasChildLinks() ) { + linkRows.clear(); + linkAncestorRows.clear(); + unregisterInParentLinkCache(); + } } - //! Update array condition at specified index - void updateArrayCondition( bool cond, int at ) - { - if ( arrConds.count() > at ) - arrConds[at] = cond; - } + const QVector & getLinkAncestorRows() const { return linkAncestorRows; } + + const QVector & getLinkRows() const { return linkRows; } //! Cached result of cond expression - bool condition() - { - return conditionStatus == 1; - } + bool condition() const { return conditionStatus == 1; } //! Cached result of vercond expression - bool versionCondition() - { - return vercondStatus == 1; - } + bool versionCondition() const { return vercondStatus == 1; } //! Has the condition been cached - bool isConditionValid() - { - return conditionStatus != -1; - } + bool isConditionCached() const { return conditionStatus != -1; } //! Has the version condition been cached - bool isVercondValid() - { - return vercondStatus != -1; - } + bool isVersionConditionCached() const { return vercondStatus != -1; } //! Cache the cond expression - void setCondition( bool status ) - { - conditionStatus = status; - } + void setCondition( bool status ) const { conditionStatus = status; } //! Cache the vercond expression - void setVersionCondition( bool status ) - { - vercondStatus = status; - } + void setVersionCondition( bool status ) const { vercondStatus = status; } - //! Invalidate the cached cond expression + //! Invalidate the cached cond expression in the item and its children. void invalidateCondition() { - conditionStatus = -1; + if ( isConditionCached() ) { + conditionStatus = -1; + + for ( NifItem * c : childItems ) + c->invalidateCondition(); + } } - //! Invalidate the cached vercond expression + //! Invalidate the cached vercond expression in the item and its children. void invalidateVersionCondition() { - vercondStatus = -1; - } + if ( isVersionConditionCached() ) { + vercondStatus = -1; - //! Invalidate the cached row index - void invalidateRow() - { - rowIdx = -1; + for ( NifItem * c : childItems ) + c->invalidateVersionCondition(); + } } - //! Invalidate the cached row index for this item and its children - void invalidateRowCounts() +private: + //! Invalidate the cached at index + void invalidateRow() { rowIdx = -1; } + + void updateChildRows( int iStartChild = 0 ) { - invalidateRow(); - for ( NifItem * c : childItems ) { - c->invalidateRow(); - } + for ( int i = iStartChild; i < childItems.count(); i++ ) + childItems.at(i)->rowIdx = i; } - //! Invalidate the cached row index for this item and its children starting at the given index - void invalidateRowCounts( int at ) - { - if ( at < childCount() ) { - invalidateRow(); - for ( int i = at; i < childCount(); i++ ) { - childItems.value( i )->invalidateRow(); - } - } else { - invalidateRowCounts(); - } + void registerInParentLinkCache(); - } + void unregisterInParentLinkCache(); + + bool hasChildLinks() const { return ( linkAncestorRows.count() > 0 ) || ( linkRows.count() > 0 ); } + + void updateLinkCache( int iStartChild, bool bDoCleanup ); + + void onParentItemChange(); +public: //! Return the value of the item data (const version) inline const NifValue & value() const { return itemData.value; } //! Return the value of the item data @@ -689,20 +671,94 @@ class NifItem //! Set the version condition attribute inline void setVerCond( const QString & cond ) { itemData.setVerCond( cond ); } + inline void setIsConditionless( bool flag ) { itemData.setIsConditionless( flag ); } + //! Determine if this item is present in the specified version - inline bool evalVersion( quint32 v ) + inline bool evalVersion( quint32 v ) const { return ( ( ver1() == 0 || ver1() <= v ) && ( ver2() == 0 || v <= ver2() ) ); } - //! Get the value of an item if it's not nullptr - template static inline T get( const NifItem * item ) { return item ? item->get() : T(); } + //! Gets the item's value type (NifValue::Type). + inline NifValue::Type valueType() const { return itemData.valueType(); } + //! Check if the type of the item's value is a color type (Color3 or Color4 in xml). + inline bool valueIsColor() const { return itemData.valueIsColor(); } + //! Check if the type of the item's value is a count. + inline bool valueIsCount() const { return itemData.valueIsCount(); } + //! Check if the type of the item's value is a flag type (Flags in xml). + inline bool valueIsFlags() const { return itemData.valueIsFlags(); } + //! Check if the type of the item's value is a float type (Float in xml). + inline bool valueIsFloat() const { return itemData.valueIsFloat(); } + //! Check if the type of the item's value is of a link type (Ref or Ptr in xml). + inline bool valueIsLink() const { return itemData.valueIsLink(); } + //! Check if the type of the item's value is a 3x3 matrix type (Matrix33 in xml). + inline bool valueIsMatrix() const { return itemData.valueIsMatrix(); } + //! Check if the type of the item's value is a 4x4 matrix type (Matrix44 in xml). + inline bool valueIsMatrix4() const { return itemData.valueIsMatrix4(); } + //! Check if the type of the item's value is a quaternion type. + inline bool valueIsQuat() const { return itemData.valueIsQuat(); } + //! Check if the type of the item's value is a string type. + inline bool valueIsString() const { return itemData.valueIsString(); } + //! Check if the type of the item's value is a Vector 4. + inline bool valueIsVector4() const { return itemData.valueIsVector4(); } + //! Check if the type of the item's value is a Vector 3. + inline bool valueIsVector3() const { return itemData.valueIsVector3(); } + //! Check if the type of the item's value is a Half Vector3. + inline bool valueIsHalfVector3() const { return itemData.valueIsHalfVector3(); } + //! Check if the type of the item's value is a Byte Vector3. + inline bool valueIsByteVector3() const { return itemData.valueIsByteVector3(); } + //! Check if the type of the item's value is a HalfVector2. + inline bool valueIsHalfVector2() const { return itemData.valueIsHalfVector2(); } + //! Check if the type of the item's value is a Vector 2. + inline bool valueIsVector2() const { return itemData.valueIsVector2(); } + //! Check if the type of the item's value is a triangle type. + inline bool valueIsTriangle() const { return itemData.valueIsTriangle(); } + //! Check if the type of the item's value is a byte array. + inline bool valueIsByteArray() const { return itemData.valueIsByteArray(); } + //! Check if the type of the item's value is a File Version. + inline bool valueIsFileVersion() const { return itemData.valueIsFileVersion(); } + //! Check if the type of the item's value is a byte matrix. + inline bool valueIsByteMatrix() const { return itemData.valueIsByteMatrix(); } + + //! Return the item's value as a count, if applicable. + inline quint64 valueToCount() const { return itemData.value.toCount( parentModel, this ); } + //! Return the value of an item as a count if the item is not null and if it's applicable. + static inline quint64 valueToCount( const NifItem * item ) { return item ? item->valueToCount() : 0; } + + //! Return the item's value as a float, if applicable. + inline float valueToFloat() const { return itemData.value.toFloat( parentModel, this ); } + //! Return the value of an item as a float if the item is not null and if it's applicable. + static inline float valueToFloat( const NifItem * item ) { return item ? item->valueToFloat() : 0.0f; } + + //! Return the item's value as a link, if applicable. + inline qint32 valueToLink() const { return itemData.value.toLink( parentModel, this ); } + //! Return the value of an item as a link if the item is not null and if it's applicable. + static inline qint32 valueToLink( const NifItem * item ) { return item ? item->valueToLink() : -1; } + + //! Return the item's value as a QColor, if applicable. + inline QColor valueToColor() const { return itemData.value.toColor( parentModel, this ); } + //! Return the value of an item as a QColor if the item is not null and if it's applicable. + static inline QColor valueToColor( const NifItem * item ) { return item ? item->valueToColor() : QColor(); } + + //! Return the item's value as a file version, if applicable. + inline quint32 valueToFileVersion() const { return itemData.value.toFileVersion( parentModel, this ); } + //! Return the value of an item as a file version if the item is not null and if it's applicable. + static inline quint32 valueToFileVersion( const NifItem * item ) { return item ? item->valueToFileVersion() : 0; } + + //! Return a string which represents the item's value. + inline QString valueToString() const { return itemData.value.toString(); } + //! Return a string which represents the value of an item if the the is the item is not null. + static inline QString valueToString( const NifItem * item ) { return item ? item->valueToString() : QString(); } + + //! Return the item's value as a QVariant. + inline QVariant valueToVariant() const { return itemData.value.toVariant(); } + //! Return the value of an item as a QVariant if the item is not null. + static inline QVariant valueToVariant( const NifItem * item ) { return item ? item->valueToVariant() : QVariant(); } //! Get the value of the item - template inline T get() const { return itemData.value.get(); } - - //! Get the child items of arrayRoot as an array if arrayRoot is not nullptr - template static inline QVector getArray( const NifItem * arrayRoot ) { return arrayRoot ? arrayRoot->getArray() : QVector(); } + template inline T get() const { return itemData.value.get( parentModel, this ); } + //! Get the value of an item if it's not nullptr + template static inline T get( const NifItem * item ) { return item ? item->get() : T(); } //! Get the child items as an array template QVector getArray() const @@ -711,33 +767,92 @@ class NifItem int nSize = childItems.count(); if ( nSize > 0 ) { array.reserve( nSize ); - for ( NifItem * child : childItems ) { - array.append( NifItem::get( child ) ); - } + for ( const NifItem * child : childItems ) + array.append( child->get() ); } return array; } + //! Get the child items of arrayRoot as an array if arrayRoot is not nullptr + template static inline QVector getArray( const NifItem * arrayRoot ) + { + return arrayRoot ? arrayRoot->getArray() : QVector(); + } + + //! Set the value of the item. + template inline bool set( const T & v ) { return itemData.value.set( v, parentModel, this ); } + //! Set the value of an item if it's not nullptr. + template static inline bool set( NifItem * item, const T & v ) { return item ? item->set(v) : false; } //! Set the child items from an array - template void setArray( const QVector & array ) + template bool setArray( const QVector & array ) { - int x = 0; - for ( NifItem * child : childItems ) { - child->itemData.value.set( array.value( x++ ) ); + int nSize = childItems.count(); + if ( nSize != array.count() ) { + reportError( + "setArray", + QString( "The input QVector's size (%1) does not match the array's size (%2)." ).arg( array.count() ).arg( nSize ) + ); + return false; + } + for ( int i = 0; i < nSize; i++ ) { + if ( !childItems.at(i)->set( array.at(i) ) ) + return false; } + + return true; } //! Set the child items from a single value - template void setArray( const T & val ) + template bool fillArray( const T & val ) { for ( NifItem * child : childItems ) { - child->itemData.value.set( val ); + if ( !child->set( val ) ) + return false; } + + return true; } + //! Set the item's value to a count. + inline bool valueFromCount( quint64 c ) { return itemData.value.setCount( c, parentModel, this ); } + //! Set the value of an item to a count if the item is not null. + static inline bool valueFromCount( NifItem * item, quint64 c ) { return item ? item->valueFromCount( c ) : false; } + + //! Set the item's value to a float. + inline bool valueFromFloat( float f ) { return itemData.value.setFloat( f, parentModel, this ); } + //! Set the value of an item to a float if the item is not null. + static inline bool valueFromFloat( NifItem * item, float f ) { return item ? item->valueFromFloat( f ) : false; } + + //! Set the item's value to a link (block number). + inline bool valueFromLink( qint32 link ) { return itemData.value.setLink( link, parentModel, this ); } + //! Set the value of an item to a link (block number) if the item is not null. + static inline bool valueFromLink( NifItem * item, qint32 link ) { return item ? item->valueFromLink( link ) : false; } + + //! Set the item's value to a file version. + inline bool valueFromFileVersion( quint32 v ) { return itemData.value.setFileVersion( v, parentModel, this ); } + //! Set the value of an item to a file version if the item is not null. + static inline bool valueFromFileVersion( NifItem * item, quint32 v ) { return item ? item->valueFromFileVersion( v ) : false; } + + //! Set the item's value from a string. + inline bool valueFromString( const QString & str ) { return itemData.value.setFromString( str, parentModel, this ); } + //! Set the value of an item from a string if the item is not null. + static inline bool valueFromString( NifItem * item, const QString & str ) { return item ? item->valueFromString( str ) : false; } + + //! Set the item's value from a QVariant. + inline bool valueFromVariant( const QVariant & v ) { return itemData.value.setFromVariant( v ); } + //! Set the value of an item from a QVariant if the item is not null. + static inline bool valueFromVariant( NifItem * item, const QVariant & v ) { return item ? item->valueFromVariant( v ) : false; } + + //! Change the type of value stored. + inline void valueChangeType( NifValue::Type t ) { itemData.value.changeType( t ); } + + void reportError( const QString & msg ) const; + void reportError( const QString & funcName, const QString & msg ) const; + private: //! The data held by the item NifData itemData; + BaseModel * parentModel = nullptr; //! The parent of this item NifItem * parentItem = nullptr; //! The child items @@ -747,15 +862,13 @@ class NifItem QVector linkAncestorRows; //! Rows which are links QVector linkRows; - //! If item is array with fixed compounds, the conditions are stored here for reuse - QVector arrConds; - //! Item's row index, -1 is invalid, otherwise 0+ + //! Item's row index, -1 is not cached, otherwise 0+ mutable int rowIdx = -1; - //! Item's condition status, -1 is invalid, otherwise 0/1 - char conditionStatus = -1; - //! Item's vercond status, -1 is invalid, otherwise 0/1 - char vercondStatus = -1; + //! Item's condition status, -1 is not cached, otherwise 0/1 + mutable char conditionStatus = -1; + //! Item's vercond status, -1 is not cached, otherwise 0/1 + mutable char vercondStatus = -1; }; #endif diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index 1e9e5a7f6..fd188a466 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -766,35 +766,35 @@ bool NifValue::setFromVariant( const QVariant & var ) operator=( var.value() ); return true; } else if ( var.type() == QVariant::String ) { - return set( var.toString() ); + return set( var.toString(), nullptr, nullptr ); } return false; } -bool NifValue::setFromString( const QString & s ) +bool NifValue::setFromString( const QString & s, const BaseModel * model, const NifItem * item ) { - bool ok; + bool ok = false; switch ( typ ) { case tBool: val.u64 = 0; - if ( s == "yes" || s == "true" ) { + if ( s == QLatin1String("yes") || s == QLatin1String("true") ) { val.u32 = 1; - return true; - } else if ( s == "no" || s == "false" ) { + ok = true; + } else if ( s == QLatin1String("no") || s == QLatin1String("false") ) { val.u32 = 0; - return true; - } else if ( s == "undefined" ) { + ok = true; + } else if ( s == QLatin1String("undefined") ) { val.u32 = 2; - return true; + ok = true; } - + break; case tByte: val.u64 = 0; val.u08 = s.toUInt( &ok, 0 ); - return ok; + break; case tWord: case tFlags: case tStringOffset: @@ -802,38 +802,38 @@ bool NifValue::setFromString( const QString & s ) case tShort: val.u64 = 0; val.u16 = s.toShort( &ok, 0 ); - return ok; + break; case tInt: val.i32 = s.toInt( &ok, 0 ); - return ok; + break; case tUInt: case tULittle32: val.u64 = 0; val.u32 = s.toUInt( &ok, 0 ); - return ok; + break; case tInt64: val.i64 = s.toLongLong( &ok, 0 ); - return ok; + break; case tUInt64: val.u64 = s.toULongLong( &ok, 0 ); - return ok; + break; case tStringIndex: val.u64 = 0; val.u32 = s.toUInt( &ok ); - return ok; + break; case tLink: case tUpLink: val.u64 = 0; val.i32 = s.toInt( &ok ); - return ok; + break; case tFloat: val.u64 = 0; val.f32 = s.toDouble( &ok ); - return ok; + break; case tHfloat: val.u64 = 0; val.f32 = s.toDouble( &ok ); - return ok; + break; case tString: case tSizedString: case tText: @@ -842,45 +842,44 @@ bool NifValue::setFromString( const QString & s ) case tLineString: case tChar8String: *static_cast( val.data ) = s; - return true; + ok = true; + break; case tColor3: static_cast( val.data )->fromQColor( QColor( s ) ); - return true; + ok = true; + break; case tColor4: case tByteColor4: static_cast( val.data )->fromQColor( QColor( s ) ); - return true; + ok = true; + break; case tFileVersion: val.u64 = 0; val.u32 = NifModel::version2number( s ); - return val.u32 != 0; + ok = (val.u32 != 0); + break; case tVector2: static_cast( val.data )->fromString( s ); - return true; + ok = true; + break; case tVector3: static_cast( val.data )->fromString( s ); - return true; + ok = true; + break; case tVector4: static_cast( val.data )->fromString( s ); - return true; + ok = true; + break; case tQuat: case tQuatXYZW: static_cast( val.data )->fromString( s ); - return true; - case tByteArray: - case tByteMatrix: - case tStringPalette: - case tMatrix: - case tMatrix4: - case tTriangle: - case tBlob: - case tNone: - return false; - default: - return false; + ok = true; + break; } - return false; + if ( !ok && model ) + reportConvertFromError( model, item, QString("string \"%1\"").arg(s) ); + return ok; } QString NifValue::toString() const @@ -1081,14 +1080,93 @@ QString NifValue::toString() const } } -QColor NifValue::toColor() const +void NifValue::reportModelError( const BaseModel * model, const NifItem * item, const QString & msg ) +{ + if ( item ) + model->reportError( item, msg ); + else + model->reportError( msg ); +} + +void NifValue::reportConvertToError( const BaseModel * model, const NifItem * item, const QString & toType ) const +{ + reportModelError( model, item, QString("Could not convert a value of type %1 to %2.").arg( getTypeDebugStr( type() ), toType ) ); +} + +void NifValue::reportConvertFromError( const BaseModel * model, const NifItem * item, const QString & fromType ) const { - if ( type() == tColor3 ) + reportModelError( model, item, QString("Could not assign %1 to a value of type %2.").arg( fromType, getTypeDebugStr( type() ) ) ); +} + +QString NifValue::getTypeDebugStr( NifValue::Type t ) +{ + const char * typeStr; + switch ( t ) { + case tBool: typeStr = "Bool"; break; + case tByte: typeStr = "Byte"; break; + case tWord: typeStr = "Word"; break; + case tFlags: typeStr = "Flags"; break; + case tStringOffset: typeStr = "StringOffset"; break; + case tStringIndex: typeStr = "StringIndex"; break; + case tBlockTypeIndex: typeStr = "BlockTypeIndex"; break; + case tInt: typeStr = "Int"; break; + case tShort: typeStr = "Short"; break; + case tULittle32: typeStr = "ULittle32"; break; + case tInt64: typeStr = "Int64"; break; + case tUInt64: typeStr = "UInt64"; break; + case tUInt: typeStr = "UInt"; break; + case tLink: typeStr = "Link"; break; + case tUpLink: typeStr = "UpLink"; break; + case tFloat: typeStr = "Float"; break; + case tSizedString: typeStr = "SizedString"; break; + case tText: typeStr = "Text"; break; + case tShortString: typeStr = "ShortString"; break; + case tHeaderString: typeStr = "HeaderString"; break; + case tLineString: typeStr = "LineString"; break; + case tChar8String: typeStr = "Char8String"; break; + case tColor3: typeStr = "Color3"; break; + case tColor4: typeStr = "Color4"; break; + case tVector3: typeStr = "Vector3"; break; + case tQuat: typeStr = "Quat"; break; + case tQuatXYZW: typeStr = "QuatXYZW"; break; + case tMatrix: typeStr = "Matrix"; break; + case tMatrix4: typeStr = "Matrix4"; break; + case tVector2: typeStr = "Vector2"; break; + case tVector4: typeStr = "Vector4"; break; + case tTriangle: typeStr = "Triangle"; break; + case tFileVersion: typeStr = "FileVersion"; break; + case tByteArray: typeStr = "ByteArray"; break; + case tStringPalette: typeStr = "StringPalette"; break; + case tString: typeStr = "String"; break; + case tFilePath: typeStr = "FilePath"; break; + case tByteMatrix: typeStr = "ByteMatrix"; break; + case tBlob: typeStr = "Blob"; break; + case tHfloat: typeStr = "Hfloat"; break; + case tHalfVector3: typeStr = "HalfVector3"; break; + case tByteVector3: typeStr = "ByteVector3"; break; + case tHalfVector2: typeStr = "HalfVector2"; break; + case tByteColor4: typeStr = "ByteColor4"; break; + case tBSVertexDesc: typeStr = "BSVertexDesc"; break; + case tNone: typeStr = "None"; break; + default: typeStr = "UNKNOWN"; break; + } + + return QString("%2 (%1)").arg( int(t) ).arg( typeStr ); +} + +QColor NifValue::toColor( const BaseModel * model, const NifItem * item ) const +{ + switch ( type() ) { + case tColor3: return static_cast( val.data )->toQColor(); - else if ( type() == tColor4 || type() == tByteColor4 ) + case tColor4: + case tByteColor4: return static_cast( val.data )->toQColor(); - - return QColor(); + default: + if ( model ) + reportConvertToError(model, item, "a color"); + return QColor(); + } } diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 45ede4a74..2ef72477e 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -58,6 +58,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! Increment when editing color types (Color3, Color4) #define COLOR_STEP 0.01 +class BaseModel; +class NifItem; /*! A generic class used for storing a value of any type. * @@ -284,15 +286,15 @@ class NifValue bool isByteMatrix() const { return typ == tByteMatrix; } //! Return the value of the data as a QColor, if applicable. - QColor toColor() const; + QColor toColor( const BaseModel * model, const NifItem * item ) const; //! Return the value of the data as a count. - quint64 toCount() const; + quint64 toCount( const BaseModel * model, const NifItem * item ) const; //! Return the value of the data as a float. - float toFloat() const; + float toFloat( const BaseModel * model, const NifItem * item ) const; //! Return the value of the data as a link, if applicable. - qint32 toLink() const; + qint32 toLink( const BaseModel * model, const NifItem * item ) const; //! Return the value of the data as a file version, if applicable. - quint32 toFileVersion() const; + quint32 toFileVersion( const BaseModel * model, const NifItem * item ) const; //! Return a string which represents the value of the data. QString toString() const; @@ -303,31 +305,31 @@ class NifValue * * @return True if applicable, false otherwise */ - bool setCount( quint64 ); + bool setCount( quint64 c, const BaseModel * model, const NifItem * item ); /*! Set this value to a float. * * @return True if applicable, false otherwise */ - bool setFloat( float ); + bool setFloat( float f, const BaseModel * model, const NifItem * item ); /*! Set this value to a link. * * @return True if applicable, false otherwise */ - bool setLink( int ); + bool setLink( qint32 link, const BaseModel * model, const NifItem * item ); /*! Set this value to a file version. * * @return True if applicable, false otherwise */ - bool setFileVersion( quint32 ); + bool setFileVersion( quint32 v, const BaseModel * model, const NifItem * item ); /*! Set this value from a string. * * @return True if applicable, false otherwise */ - bool setFromString( const QString & ); + bool setFromString( const QString & s, const BaseModel * model, const NifItem * item ); /*! Set this value from a QVariant. * @@ -335,12 +337,10 @@ class NifValue */ bool setFromVariant( const QVariant & ); - //! Check whether the data is of type T. - template bool ask( T * t = 0 ) const; //! Get the data in the form of something of type T. - template T get() const; + template T get( const BaseModel * model, const NifItem * item ) const; //! Set the data from an instance of type T. Return true if successful. - template bool set( const T & x ); + template bool set( const T & x, const BaseModel * model, const NifItem * item ); protected: //! The type of this data. @@ -367,14 +367,14 @@ class NifValue * If the type t is not equal to the actual type of the data, then return T(). Serves * as a helper function for get, intended for internal use only. */ - template T getType( Type t ) const; + template T getType( Type t, const BaseModel * model, const NifItem * item ) const; /*! Set the data from an object of type T. * * If the type t is not equal to the actual type of the data, then return false, else * return true. Helper function for set, intended for internal use only. */ - template bool setType( Type t, T v ); + template bool setType( Type t, T v, const BaseModel * model, const NifItem * item ); //! A dictionary yielding the Type from a type string. static QHash typeMap; @@ -397,6 +397,12 @@ class NifValue * This dictionary allows that type to be exposed, eg. for NifValue::typeDescription(). */ static QHash aliasMap; + + static void reportModelError( const BaseModel * model, const NifItem * item, const QString & msg ); + static QString getTypeDebugStr( NifValue::Type t ); +public: + void reportConvertToError( const BaseModel * model, const NifItem * item, const QString & toType ) const; + void reportConvertFromError( const BaseModel * model, const NifItem * item, const QString & fromType ) const; }; Q_DECLARE_METATYPE( NifValue ) @@ -408,331 +414,369 @@ Q_DECLARE_METATYPE( NifValue ) // documented above; should this really be inlined? // GCC only allows type punning via union (http://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Optimize-Options.html#index-fstrict_002daliasing-550) // This also works on GCC 3.4.5 -inline quint64 NifValue::toCount() const +inline quint64 NifValue::toCount( const BaseModel * model, const NifItem * item ) const { if ( isCount() || isFloat() ) return val.u64; + if ( model ) + reportConvertToError( model, item, "a number"); return 0; } -inline float NifValue::toFloat() const +inline float NifValue::toFloat( const BaseModel * model, const NifItem * item ) const { if ( isFloat() ) return val.f32; + if ( model ) + reportConvertToError(model, item, "a float"); return 0.0; } -inline qint32 NifValue::toLink() const +inline qint32 NifValue::toLink( const BaseModel * model, const NifItem * item ) const { if ( isLink() ) return val.i32; + if ( model ) + reportConvertToError(model, item, "a link"); return -1; } -inline quint32 NifValue::toFileVersion() const +inline quint32 NifValue::toFileVersion( const BaseModel * model, const NifItem * item ) const { if ( isFileVersion() ) return val.u32; + if ( model ) + reportConvertToError(model, item, "a file version"); return 0; } -inline bool NifValue::setCount( quint64 c ) +inline bool NifValue::setCount( quint64 c, const BaseModel * model, const NifItem * item ) { if ( isCount() ) { val.u64 = c; return true; } + if ( model ) + reportConvertFromError( model, item, "a number" ); return false; } -inline bool NifValue::setFloat( float f ) +inline bool NifValue::setFloat( float f, const BaseModel * model, const NifItem * item ) { if ( isFloat() ) { val.f32 = f; return true; } + if ( model ) + reportConvertFromError( model, item, "a float" ); return false; } -inline bool NifValue::setLink( int l ) +inline bool NifValue::setLink( qint32 lnk, const BaseModel * model, const NifItem * item ) { if ( isLink() ) { - val.i32 = l; return true; + val.i32 = lnk; return true; } + if ( model ) + reportConvertFromError( model, item, "a link" ); return false; } -inline bool NifValue::setFileVersion( quint32 v ) +inline bool NifValue::setFileVersion( quint32 v, const BaseModel * model, const NifItem * item ) { if ( isFileVersion() ) { val.u32 = v; return true; } + if ( model ) + reportConvertFromError( model, item, "a file version" ); return false; } // Templates -template inline T NifValue::getType( Type t ) const +template inline T NifValue::getType( Type t, const BaseModel * model, const NifItem * item ) const { if ( typ == t ) return *static_cast( val.data ); // WARNING: this throws an exception if the type of v is not the original type by which val.data was initialized; the programmer must make sure that T matches t. + if ( model ) + reportConvertToError( model, item, getTypeDebugStr( t ) ); return T(); } -template inline bool NifValue::setType( Type t, T v ) +template inline bool NifValue::setType( Type t, T v, const BaseModel * model, const NifItem * item ) { if ( typ == t ) { *static_cast( val.data ) = v; // WARNING: this throws an exception if the type of v is not the original type by which val.data was initialized; the programmer must make sure that T matches t. return true; } + if ( model ) + reportConvertFromError( model, item, getTypeDebugStr(t) ); return false; } -template <> inline bool NifValue::get() const +template <> inline bool NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toCount(); + return toCount( model, item ); } -template <> inline qint64 NifValue::get() const +template <> inline qint64 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toCount(); + return toCount( model, item ); } -template <> inline quint64 NifValue::get() const +template <> inline quint64 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toCount(); + return toCount( model, item ); } -template <> inline qint32 NifValue::get() const +template <> inline qint32 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toCount(); + return toCount( model, item ); } -template <> inline quint32 NifValue::get() const +template <> inline quint32 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toCount(); + return toCount( model, item ); } -template <> inline qint16 NifValue::get() const +template <> inline qint16 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toCount(); + return toCount( model, item ); } -template <> inline quint16 NifValue::get() const +template <> inline quint16 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toCount(); + return toCount( model, item ); } -template <> inline quint8 NifValue::get() const +template <> inline quint8 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toCount(); + return toCount( model, item ); } -template <> inline float NifValue::get() const +template <> inline float NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toFloat(); + return toFloat( model, item ); } -template <> inline QColor NifValue::get() const +template <> inline QColor NifValue::get( const BaseModel * model, const NifItem * item ) const { - return toColor(); + return toColor( model, item ); } -template <> inline QVariant NifValue::get() const +template <> inline QVariant NifValue::get( const BaseModel * /* model */, const NifItem * /* item */ ) const { return toVariant(); } - -template <> inline Matrix NifValue::get() const +template <> inline Matrix NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tMatrix ); + return getType( tMatrix, model, item ); } -template <> inline Matrix4 NifValue::get() const +template <> inline Matrix4 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tMatrix4 ); + return getType( tMatrix4, model, item ); } -template <> inline Vector4 NifValue::get() const +template <> inline Vector4 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tVector4 ); + return getType( tVector4, model, item ); } -template <> inline Vector3 NifValue::get() const +template <> inline Vector3 NifValue::get( const BaseModel * model, const NifItem * item ) const { if ( typ == tVector3 || typ == tHalfVector3 ) return *static_cast(val.data); + if ( model ) + reportConvertToError( model, item, "a Vector3" ); return Vector3(); } -template <> inline HalfVector3 NifValue::get() const +template <> inline HalfVector3 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tHalfVector3 ); + return getType( tHalfVector3, model, item ); } -template <> inline ByteVector3 NifValue::get() const +template <> inline ByteVector3 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tByteVector3 ); + return getType( tByteVector3, model, item ); } -template <> inline HalfVector2 NifValue::get() const +template <> inline HalfVector2 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tHalfVector2 ); + return getType( tHalfVector2, model, item ); } -template <> inline Vector2 NifValue::get() const +template <> inline Vector2 NifValue::get( const BaseModel * model, const NifItem * item ) const { if ( typ == tVector2 || typ == tHalfVector2 ) return *static_cast(val.data); + if ( model ) + reportConvertToError( model, item, "a Vector2" ); return Vector2(); } -template <> inline Color3 NifValue::get() const +template <> inline Color3 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tColor3 ); + return getType( tColor3, model, item ); } -template <> inline ByteColor4 NifValue::get() const +template <> inline ByteColor4 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tByteColor4 ); + return getType( tByteColor4, model, item ); } -template <> inline Color4 NifValue::get() const +template <> inline Color4 NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tColor4 ); + return getType( tColor4, model, item ); } -template <> inline Triangle NifValue::get() const +template <> inline Triangle NifValue::get( const BaseModel * model, const NifItem * item ) const { - return getType( tTriangle ); + return getType( tTriangle, model, item ); } -template <> inline QString NifValue::get() const +template <> inline QString NifValue::get( const BaseModel * model, const NifItem * item ) const { if ( isString() ) return *static_cast( val.data ); + if ( model ) + reportConvertToError( model, item, "a string" ); return QString(); } -template <> inline QByteArray NifValue::get() const +template <> inline QByteArray NifValue::get( const BaseModel * model, const NifItem * item ) const { if ( isByteArray() ) return *static_cast( val.data ); + if ( model ) + reportConvertToError( model, item, "a byte array" ); return QByteArray(); } -template <> inline QByteArray * NifValue::get() const +template <> inline QByteArray * NifValue::get( const BaseModel * model, const NifItem * item ) const { if ( isByteArray() ) return static_cast( val.data ); + if ( model ) + reportConvertToError( model, item, "a byte array" ); return nullptr; } -template <> inline Quat NifValue::get() const +template <> inline Quat NifValue::get( const BaseModel * model, const NifItem * item ) const { if ( isQuat() ) return *static_cast( val.data ); + if ( model ) + reportConvertToError( model, item, "Quat" ); return Quat(); } -template <> inline ByteMatrix * NifValue::get() const +template <> inline ByteMatrix * NifValue::get( const BaseModel * model, const NifItem * item ) const { if ( isByteMatrix() ) return static_cast( val.data ); + if ( model ) + reportConvertToError( model, item, "ByteMatrix" ); return nullptr; } -template <> inline BSVertexDesc NifValue::get() const +template <> inline BSVertexDesc NifValue::get( const BaseModel * model, const NifItem * item ) const { - return BSVertexDesc(toCount()); + if ( isCount() ) + return BSVertexDesc( val.u64 ); + + if ( model ) + reportConvertToError( model, item, "BSVertexDesc" ); + return BSVertexDesc( 0 ); } //! Set the data from a boolean. Return true if successful. -template <> inline bool NifValue::set( const bool & b ) +template <> inline bool NifValue::set( const bool & b, const BaseModel * model, const NifItem * item ) { - return setCount( b ); + return setCount( b, model, item ); } //! Set the data from an integer. Return true if successful. -template <> inline bool NifValue::set( const int & i ) +template <> inline bool NifValue::set( const int & i, const BaseModel * model, const NifItem * item ) { - return setCount( i ); + return setCount( i, model, item); } //! Set the data from an unsigned integer. Return true if successful. -template <> inline bool NifValue::set( const quint32 & i ) +template <> inline bool NifValue::set( const quint32 & i, const BaseModel * model, const NifItem * item ) { - return setCount( i ); + return setCount( i, model, item ); } //! Set the data from a short. Return true if successful. -template <> inline bool NifValue::set( const qint16 & i ) +template <> inline bool NifValue::set( const qint16 & i, const BaseModel * model, const NifItem * item ) { - return setCount( i ); + return setCount( i, model, item ); } //! Set the data from an unsigned short. Return true if successful. -template <> inline bool NifValue::set( const quint16 & i ) +template <> inline bool NifValue::set( const quint16 & i, const BaseModel * model, const NifItem * item ) { - return setCount( i ); + return setCount( i, model, item ); } //! Set the data from an unsigned byte. Return true if successful. -template <> inline bool NifValue::set( const quint8 & i ) +template <> inline bool NifValue::set( const quint8 & i, const BaseModel * model, const NifItem * item ) { - return setCount( i ); + return setCount( i, model, item ); } //! Set the data from a float. Return true if successful. -template <> inline bool NifValue::set( const float & f ) +template <> inline bool NifValue::set( const float & f, const BaseModel * model, const NifItem * item ) { - return setFloat( f ); + return setFloat( f, model, item ); } //! Set the data from a Matrix. Return true if successful. -template <> inline bool NifValue::set( const Matrix & x ) +template <> inline bool NifValue::set( const Matrix & x, const BaseModel * model, const NifItem * item ) { - return setType( tMatrix, x ); + return setType( tMatrix, x, model, item ); } //! Set the data from a Matrix4. Return true if successful. -template <> inline bool NifValue::set( const Matrix4 & x ) +template <> inline bool NifValue::set( const Matrix4 & x, const BaseModel * model, const NifItem * item ) { - return setType( tMatrix4, x ); + return setType( tMatrix4, x, model, item ); } //! Set the data from a Vector4. Return true if successful. -template <> inline bool NifValue::set( const Vector4 & x ) +template <> inline bool NifValue::set( const Vector4 & x, const BaseModel * model, const NifItem * item ) { - return setType( tVector4, x ); + return setType( tVector4, x, model, item ); } //! Set the data from a Vector3. Return true if successful. -template <> inline bool NifValue::set( const Vector3 & x ) +template <> inline bool NifValue::set( const Vector3 & x, const BaseModel * model, const NifItem * item ) { - return setType( tVector3, x ); + return setType( tVector3, x, model, item ); } //! Set the data from a HalfVector3. Return true if successful. -template <> inline bool NifValue::set( const HalfVector3 & x ) +template <> inline bool NifValue::set( const HalfVector3 & x, const BaseModel * model, const NifItem * item ) { - return setType( tHalfVector3, x ); + return setType( tHalfVector3, x, model, item ); } //! Set the data from a ByteVector3. Return true if successful. -template <> inline bool NifValue::set( const ByteVector3 & x ) +template <> inline bool NifValue::set( const ByteVector3 & x, const BaseModel * model, const NifItem * item ) { - return setType( tByteVector3, x ); + return setType( tByteVector3, x, model, item ); } //! Set the data from a HalfVector2. Return true if successful. -template <> inline bool NifValue::set( const HalfVector2 & x ) +template <> inline bool NifValue::set( const HalfVector2 & x, const BaseModel * model, const NifItem * item ) { - return setType( tHalfVector2, x ); + return setType( tHalfVector2, x, model, item ); } //! Set the data from a Vector2. Return true if successful. -template <> inline bool NifValue::set( const Vector2 & x ) +template <> inline bool NifValue::set( const Vector2 & x, const BaseModel * model, const NifItem * item ) { - return setType( tVector2, x ); + return setType( tVector2, x, model, item ); } //! Set the data from a Color3. Return true if successful. -template <> inline bool NifValue::set( const Color3 & x ) +template <> inline bool NifValue::set( const Color3 & x, const BaseModel * model, const NifItem * item ) { - return setType( tColor3, x ); + return setType( tColor3, x, model, item ); } //! Set the data from a ByteColor4. Return true if successful. -template <> inline bool NifValue::set( const ByteColor4 & x ) +template <> inline bool NifValue::set( const ByteColor4 & x, const BaseModel * model, const NifItem * item ) { - return setType( tByteColor4, x ); + return setType( tByteColor4, x, model, item ); } //! Set the data from a Color4. Return true if successful. -template <> inline bool NifValue::set( const Color4 & x ) +template <> inline bool NifValue::set( const Color4 & x, const BaseModel * model, const NifItem * item ) { - return setType( tColor4, x ); + return setType( tColor4, x, model, item ); } //! Set the data from a Triangle. Return true if successful. -template <> inline bool NifValue::set( const Triangle & x ) +template <> inline bool NifValue::set( const Triangle & x, const BaseModel * model, const NifItem * item ) { - return setType( tTriangle, x ); + return setType( tTriangle, x, model, item ); } //! Set the data from a string. Return true if successful. -template <> inline bool NifValue::set( const QString & x ) +template <> inline bool NifValue::set( const QString & x, const BaseModel * model, const NifItem * item ) { if ( isString() ) { if ( !val.data ) { @@ -743,129 +787,38 @@ template <> inline bool NifValue::set( const QString & x ) return true; } + if ( model ) + reportConvertFromError( model, item, "a QString" ); return false; } //! Set the data from a byte array. Return true if successful. -template <> inline bool NifValue::set( const QByteArray & x ) +template <> inline bool NifValue::set( const QByteArray & x, const BaseModel * model, const NifItem * item ) { if ( isByteArray() ) { *static_cast( val.data ) = x; return true; } + if ( model ) + reportConvertFromError( model, item, "a QByteArray" ); return false; } //! Set the data from a quaternion. Return true if successful. -template <> inline bool NifValue::set( const Quat & x ) +template <> inline bool NifValue::set( const Quat & x, const BaseModel * model, const NifItem * item ) { if ( isQuat() ) { *static_cast( val.data ) = x; return true; } + if ( model ) + reportConvertFromError( model, item, "a Quat" ); return false; } //! Set the data from a BSVertexDesc. Return true if successful. -template <> inline bool NifValue::set( const BSVertexDesc & x ) -{ - return setCount(x.Value()); -} - - -//! Check whether the data is a boolean. -template <> inline bool NifValue::ask( bool * ) const -{ - return isCount(); -} -//! Check whether the data is an integer. -template <> inline bool NifValue::ask( int * ) const -{ - return isCount(); -} -//! Check whether the data is a short. -template <> inline bool NifValue::ask( short * ) const -{ - return isCount(); -} -//! Check whether the data is a float. -template <> inline bool NifValue::ask( float * ) const -{ - return isFloat(); -} -//! Check whether the data is a Matrix. -template <> inline bool NifValue::ask( Matrix * ) const -{ - return type() == tMatrix; -} -//! Check whether the data is a Matrix4. -template <> inline bool NifValue::ask( Matrix4 * ) const -{ - return type() == tMatrix4; -} -//! Check whether the data is a quaternion. -template <> inline bool NifValue::ask( Quat * ) const -{ - return isQuat(); -} -//! Check whether the data is a Vector4. -template <> inline bool NifValue::ask( Vector4 * ) const -{ - return type() == tVector4; -} -//! Check whether the data is a Vector3. -template <> inline bool NifValue::ask( Vector3 * ) const -{ - return type() == tVector3; -} -//! Check whether the data is a HalfVector3. -template <> inline bool NifValue::ask( HalfVector3 * ) const -{ - return type() == tHalfVector3; -} -//! Check whether the data is a ByteVector3. -template <> inline bool NifValue::ask( ByteVector3 * ) const -{ - return type() == tByteVector3; -} -//! Check whether the data is a Vector2. -template <> inline bool NifValue::ask( HalfVector2 * ) const -{ - return type() == tHalfVector2; -} -//! Check whether the data is a Vector2. -template <> inline bool NifValue::ask( Vector2 * ) const -{ - return type() == tVector2; -} -//! Check whether the data is a Color3. -template <> inline bool NifValue::ask( Color3 * ) const -{ - return type() == tColor3; -} -//! Check whether the data is a ByteColor4. -template <> inline bool NifValue::ask( ByteColor4 * ) const -{ - return type() == tByteColor4; -} -//! Check whether the data is a Color4. -template <> inline bool NifValue::ask( Color4 * ) const -{ - return type() == tColor4; -} -//! Check whether the data is a Triangle. -template <> inline bool NifValue::ask( Triangle * ) const -{ - return type() == tTriangle; -} -//! Check whether the data is a string. -template <> inline bool NifValue::ask( QString * ) const -{ - return isString(); -} -//! Check whether the data is a byte array. -template <> inline bool NifValue::ask( QByteArray * ) const +template <> inline bool NifValue::set( const BSVertexDesc & x, const BaseModel * model, const NifItem * item ) { - return isByteArray(); + return setCount(x.Value(), model, item ); } #endif diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index f5cfe7001..7a44f273c 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -22,7 +22,7 @@ void BSShape::updateData( const NifModel * nif ) { auto vertexFlags = nif->get(iBlock, "Vertex Desc"); - isDynamic = nif->inherits(iBlock, "BSDynamicTriShape"); + isDynamic = nif->blockInherits(iBlock, "BSDynamicTriShape"); hasVertexColors = vertexFlags.HasFlag(VertexAttribute::VA_COLOR); @@ -42,11 +42,11 @@ void BSShape::updateData( const NifModel * nif ) skinDataName = "NiSkinData"; } - iSkin = nif->getBlock( nif->getLink( nif->getIndex( iBlock, "Skin" ) ), skinInstName ); + iSkin = nif->getBlockIndex( nif->getLink( nif->getIndex( iBlock, "Skin" ) ), skinInstName ); if ( iSkin.isValid() ) { - iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), skinDataName ); + iSkinData = nif->getBlockIndex( nif->getLink( iSkin, "Data" ), skinDataName ); if ( nif->getBSVersion() == 100 ) - iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); + iSkinPart = nif->getBlockIndex( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); } } @@ -87,12 +87,12 @@ void BSShape::updateData( const NifModel * nif ) bitX = dynv[3]; } else { verts << nif->get( idx, "Vertex" ); - bitX = nif->getValue( nif->getIndex( idx, "Bitangent X" ) ).toFloat(); + bitX = nif->get( idx, "Bitangent X" ); } // Bitangent Y/Z - auto bitYi = nif->getValue( nif->getIndex(idx, "Bitangent Y") ).toCount(); - auto bitZi = nif->getValue( nif->getIndex(idx, "Bitangent Z") ).toCount(); + auto bitYi = nif->get( idx, "Bitangent Y" ); + auto bitZi = nif->get( idx, "Bitangent Z" ); auto bitY = (double(bitYi) / 255.0) * 2.0 - 1.0; auto bitZ = (double(bitZi) / 255.0) * 2.0 - 1.0; @@ -435,7 +435,7 @@ void BSShape::drawSelection() const return; // Set current block name and detect if extra data - auto blockName = nif->getBlockName( blk ); + auto blockName = nif->itemName( blk ); if ( blockName.startsWith( "BSPackedCombined" ) ) extraData = true; @@ -449,7 +449,7 @@ void BSShape::drawSelection() const // Name of this index's parent auto p = idx.parent().data( NifSkopeDisplayRole ).toString(); // Parent index - auto pBlock = nif->getBlock( nif->getParent( blk ) ); + auto pBlock = nif->getBlockIndex( nif->getParent( blk ) ); auto push = [this] ( const Transform & t ) { if ( transformRigid ) { @@ -761,7 +761,7 @@ void BSShape::drawSelection() const // Draw all bones' bounding spheres if ( n == "NiSkinData" || n == "BSSkin::BoneData" ) { // Get shape block - if ( nif->getBlock( nif->getParent( nif->getParent( blk ) ) ) == iBlock ) { + if ( nif->getBlockIndex( nif->getParent( nif->getParent( blk ) ) ) == iBlock ) { auto iBones = nif->getIndex( blk, "Bone List" ); int ct = nif->rowCount( iBones ); diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp index 39fc8a0f7..7d01d0f44 100644 --- a/src/gl/controllers.cpp +++ b/src/gl/controllers.cpp @@ -53,7 +53,7 @@ bool ControllerManager::update( const NifModel * nif, const QModelIndex & index Scene * scene = target->scene; QVector lSequences = nif->getLinkArray( index, "Controller Sequences" ); for ( const auto l : lSequences ) { - QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); + QModelIndex iSeq = nif->getBlockIndex( l, "NiControllerSequence" ); if ( iSeq.isValid() ) { QString name = nif->get( iSeq, "Name" ); @@ -63,7 +63,7 @@ bool ControllerManager::update( const NifModel * nif, const QModelIndex & index QMap tags = scene->animTags[name]; - QModelIndex iKeys = nif->getBlock( nif->getLink( iSeq, "Text Keys" ), "NiTextKeyExtraData" ); + QModelIndex iKeys = nif->getBlockIndex( nif->getLink( iSeq, "Text Keys" ), "NiTextKeyExtraData" ); QModelIndex iTags = nif->getIndex( iKeys, "Text Keys" ); for ( int r = 0; r < nif->rowCount( iTags ); r++ ) { @@ -96,7 +96,7 @@ void ControllerManager::setSequence( const QString & seqname ) QVector lSequences = nif->getLinkArray( iBlock, "Controller Sequences" ); for ( const auto l : lSequences ) { - QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); + QModelIndex iSeq = nif->getBlockIndex( l, "NiControllerSequence" ); if ( iSeq.isValid() && nif->get( iSeq, "Name" ) == seqname ) { start = nif->get( iSeq, "Start Time" ); @@ -109,9 +109,9 @@ void ControllerManager::setSequence( const QString & seqname ) for ( int r = 0; r < nif->rowCount( iCtrlBlcks ); r++ ) { QModelIndex iCB = iCtrlBlcks.child( r, 0 ); - QModelIndex iInterp = nif->getBlock( nif->getLink( iCB, "Interpolator" ), "NiInterpolator" ); + QModelIndex iInterp = nif->getBlockIndex( nif->getLink( iCB, "Interpolator" ), "NiInterpolator" ); - QModelIndex iController = nif->getBlock( nif->getLink( iCB, "Controller" ), "NiTimeController" ); + QModelIndex iController = nif->getBlockIndex( nif->getLink( iCB, "Controller" ), "NiTimeController" ); QString nodename = nif->get( iCB, "Node Name" ); @@ -137,7 +137,7 @@ void ControllerManager::setSequence( const QString & seqname ) ctrltype = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); if ( ctrltype.isEmpty() && iController.isValid() ) - ctrltype = nif->getBlockName( iController ); + ctrltype = nif->itemName( iController ); } QString var1 = nif->get( iCB, "Controller ID" ); @@ -310,7 +310,7 @@ bool MultiTargetTransformController::update( const NifModel * nif, const QModelI QVector lTargets = nif->getLinkArray( index, "Extra Targets" ); for ( const auto l : lTargets ) { - Node * node = scene->getNode( nif, nif->getBlock( l ) ); + Node * node = scene->getNode( nif, nif->getBlockIndex( l ) ); if ( node ) { extraTargets.append( TransformTarget( node, 0 ) ); @@ -464,9 +464,9 @@ bool MorphController::update( const NifModel * nif, const QModelIndex & index ) // this is ugly... if ( iInterpolators.isValid() ) { - key->iFrames = nif->getIndex( nif->getBlock( nif->getLink( nif->getBlock( nif->getLink( iInterpolators.child( r, 0 ) ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); + key->iFrames = nif->getIndex( nif->getBlockIndex( nif->getLink( nif->getBlockIndex( nif->getLink( iInterpolators.child( r, 0 ) ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); } else if ( iInterpolatorWeights.isValid() ) { - key->iFrames = nif->getIndex( nif->getBlock( nif->getLink( nif->getBlock( nif->getLink( iInterpolatorWeights.child( r, 0 ), "Interpolator" ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); + key->iFrames = nif->getIndex( nif->getBlockIndex( nif->getLink( nif->getBlockIndex( nif->getLink( iInterpolatorWeights.child( r, 0 ), "Interpolator" ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); } else { key->iFrames = iKey; } @@ -563,7 +563,7 @@ bool ParticleController::update( const NifModel * nif, const QModelIndex & index return false; if ( Controller::update( nif, index ) || (index.isValid() && iExtras.contains( index )) ) { - emitNode = target->scene->getNode( nif, nif->getBlock( nif->getLink( iBlock, "Emitter" ) ) ); + emitNode = target->scene->getNode( nif, nif->getBlockIndex( nif->getLink( iBlock, "Emitter" ) ) ); emitStart = nif->get( iBlock, "Emit Start Time" ); emitStop = nif->get( iBlock, "Emit Stop Time" ); emitRate = nif->get( iBlock, "Birth Rate" ); @@ -619,7 +619,7 @@ bool ParticleController::update( const NifModel * nif, const QModelIndex & index iExtras.clear(); grav.clear(); iColorKeys = QModelIndex(); - QModelIndex iExtra = nif->getBlock( nif->getLink( iBlock, "Particle Modifier" ) ); + QModelIndex iExtra = nif->getBlockIndex( nif->getLink( iBlock, "Particle Modifier" ) ); while ( iExtra.isValid() ) { iExtras.append( iExtra ); @@ -630,7 +630,7 @@ bool ParticleController::update( const NifModel * nif, const QModelIndex & index grow = nif->get( iExtra, "Grow" ); fade = nif->get( iExtra, "Fade" ); } else if ( name == "NiParticleColorModifier" ) { - iColorKeys = nif->getIndex( nif->getBlock( nif->getLink( iExtra, "Color Data" ), "NiColorData" ), "Data" ); + iColorKeys = nif->getIndex( nif->getBlockIndex( nif->getLink( iExtra, "Color Data" ), "NiColorData" ), "Data" ); } else if ( name == "NiGravity" ) { Gravity g; g.force = nif->get( iExtra, "Force" ); @@ -640,7 +640,7 @@ bool ParticleController::update( const NifModel * nif, const QModelIndex & index grav.append( g ); } - iExtra = nif->getBlock( nif->getLink( iExtra, "Next Modifier" ) ); + iExtra = nif->getBlockIndex( nif->getLink( iExtra, "Next Modifier" ) ); } return true; @@ -889,9 +889,9 @@ void TexFlipController::updateTime( float time ) // TexturingProperty if ( target ) { - target->textures[flipSlot & 7].iSource = nif->getBlock( nif->getLink( iSources.child( (int)r, 0 ) ), "NiSourceTexture" ); + target->textures[flipSlot & 7].iSource = nif->getBlockIndex( nif->getLink( iSources.child( (int)r, 0 ) ), "NiSourceTexture" ); } else if ( oldTarget ) { - oldTarget->iImage = nif->getBlock( nif->getLink( iSources.child( (int)r, 0 ) ), "NiImage" ); + oldTarget->iImage = nif->getBlockIndex( nif->getLink( iSources.child( (int)r, 0 ) ), "NiImage" ); } } diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 28933570e..48558d2f0 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -121,9 +121,9 @@ void IControllable::updateImpl(const NifModel * nif, const QModelIndex & index) name = nif->get( iBlock, "Name" ); // sync the list of attached controllers QList rem( controllers ); - QModelIndex iCtrl = nif->getBlock( nif->getLink( iBlock, "Controller" ) ); + QModelIndex iCtrl = nif->getBlockIndex( nif->getLink( iBlock, "Controller" ) ); - while ( iCtrl.isValid() && nif->inherits( iCtrl, "NiTimeController" ) ) { + while ( iCtrl.isValid() && nif->blockInherits( iCtrl, "NiTimeController" ) ) { bool add = true; for ( Controller * ctrl : controllers ) { @@ -136,7 +136,7 @@ void IControllable::updateImpl(const NifModel * nif, const QModelIndex & index) if ( add ) setController( nif, iCtrl ); - iCtrl = nif->getBlock( nif->getLink( iCtrl, "Next Controller" ) ); + iCtrl = nif->getBlockIndex( nif->getLink( iCtrl, "Next Controller" ) ); } for ( Controller * ctrl : rem ) { @@ -212,7 +212,7 @@ void Controller::setInterpolator( const QModelIndex & index ) auto nif = NifModel::fromIndex( index ); if ( nif ) - iData = nif->getBlock( nif->getLink( iInterpolator, "Data" ) ); + iData = nif->getBlockIndex( nif->getLink( iInterpolator, "Data" ) ); } bool Controller::update( const NifModel * nif, const QModelIndex & index ) @@ -231,12 +231,12 @@ bool Controller::update( const NifModel * nif, const QModelIndex & index ) // TODO: Bit 5 (32) - Generally only set when sequences are present. // TODO: Bit 6 (64) - Always seems to be set on Skyrim NIFs, unknown function. - QModelIndex idx = nif->getBlock( nif->getLink( iBlock, "Interpolator" ) ); + QModelIndex idx = nif->getBlockIndex( nif->getLink( iBlock, "Interpolator" ) ); if ( idx.isValid() ) { setInterpolator( idx ); } else { - idx = nif->getBlock( nif->getLink( iBlock, "Data" ) ); + idx = nif->getBlockIndex( nif->getLink( iBlock, "Data" ) ); if ( idx.isValid() ) iData = idx; @@ -244,7 +244,7 @@ bool Controller::update( const NifModel * nif, const QModelIndex & index ) } if ( index == iInterpolator && iInterpolator.isValid() ) - iData = nif->getBlock( nif->getLink( iInterpolator, "Data" ) ); + iData = nif->getBlockIndex( nif->getLink( iInterpolator, "Data" ) ); return ( index.isValid() && (index == iBlock || index == iInterpolator || index == iData) ); } @@ -720,7 +720,7 @@ TransformInterpolator::TransformInterpolator( Controller * owner ) bool TransformInterpolator::update( const NifModel * nif, const QModelIndex & index ) { if ( Interpolator::update( nif, index ) ) { - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ), "NiKeyframeData" ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( index, "Data" ), "NiKeyframeData" ); iTranslations = nif->getIndex( iData, "Translations" ); iRotations = nif->getIndex( iData, "Rotations" ); @@ -755,8 +755,8 @@ bool BSplineTransformInterpolator::update( const NifModel * nif, const QModelInd start = nif->get( index, "Start Time" ); stop = nif->get( index, "Stop Time" ); - iSpline = nif->getBlock( nif->getLink( index, "Spline Data" ) ); - iBasis = nif->getBlock( nif->getLink( index, "Basis Data" ) ); + iSpline = nif->getBlockIndex( nif->getLink( index, "Spline Data" ) ); + iBasis = nif->getBlockIndex( nif->getLink( index, "Basis Data" ) ); if ( iSpline.isValid() ) iControl = nif->getIndex( iSpline, "Compact Control Points" ); diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 919e78b5c..dd1c9324d 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -70,7 +70,7 @@ void Mesh::updateData( const NifModel * nif ) { resetSkinning(); resetVertexData(); - if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) + if ( nif->checkVersion( 0x14050000, 0 ) && nif->blockInherits( iBlock, "NiMesh" ) ) updateData_NiMesh( nif ); else updateData_NiTriShape( nif ); @@ -80,12 +80,12 @@ void Mesh::updateData( const NifModel * nif ) if ( iSkin.isValid() ) { isSkinned = true; - iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), "NiSkinData" ); + iSkinData = nif->getBlockIndex( nif->getLink( iSkin, "Data" ), "NiSkinData" ); - iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); + iSkinPart = nif->getBlockIndex( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); if ( !iSkinPart.isValid() && iSkinData.isValid() ) { // nif versions < 10.2.0.0 have skin partition linked in the skin data block - iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); + iSkinPart = nif->getBlockIndex( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); } skeletonRoot = nif->getLink( iSkin, "Skeleton Root" ); @@ -146,7 +146,7 @@ void Mesh::updateData_NiMesh( const NifModel * nif ) auto iStreamEntry = iData.child( i, 0 ); auto stream = nif->getLink( iStreamEntry, "Stream" ); - auto iDataStream = nif->getBlock( stream ); + auto iDataStream = nif->getBlockIndex( stream ); auto usage = NiMesh::DataStreamUsage( nif->get( iDataStream, "Usage" ) ); auto access = nif->get( iDataStream, "Access" ); @@ -216,7 +216,7 @@ void Mesh::updateData_NiMesh( const NifModel * nif ) // Get the datastream quint32 stream = nif->getLink( iStreamEntry, "Stream" ); - auto iDataStream = nif->getBlock( stream ); + auto iDataStream = nif->getBlockIndex( stream ); auto usage = NiMesh::DataStreamUsage(nif->get( iDataStream, "Usage" )); // Only process USAGE_VERTEX and USAGE_VERTEX_INDEX @@ -349,19 +349,19 @@ void Mesh::updateData_NiMesh( const NifModel * nif ) switch ( compType ) { case NiMesh::E_POSITION: case NiMesh::E_POSITION_BP: - verts[j + off] = tempValue.get(); + verts[j + off] = tempValue.get( nif, nullptr ); break; case NiMesh::E_NORMAL: case NiMesh::E_NORMAL_BP: - norms[j + off] = tempValue.get(); + norms[j + off] = tempValue.get( nif, nullptr ); break; case NiMesh::E_TANGENT: case NiMesh::E_TANGENT_BP: - tangents[j + off] = tempValue.get(); + tangents[j + off] = tempValue.get( nif, nullptr ); break; case NiMesh::E_BINORMAL: case NiMesh::E_BINORMAL_BP: - bitangents[j + off] = tempValue.get(); + bitangents[j + off] = tempValue.get( nif, nullptr ); break; default: break; @@ -373,7 +373,7 @@ void Mesh::updateData_NiMesh( const NifModel * nif ) // TODO: The total index value across all submeshes // is likely allowed to exceed USHRT_MAX. // For now limit the index. - quint32 ind = tempValue.get() + off; + quint32 ind = tempValue.get( nif, nullptr ) + off; if ( ind > 0xFFFF ) qDebug() << QString( "[%1] %2" ).arg( stream ).arg( ind ); @@ -392,7 +392,7 @@ void Mesh::updateData_NiMesh( const NifModel * nif ) if ( compType == NiMesh::E_TEXCOORD ) { quint32 coordSet = compSemanticIndexMaps[i].value( k ).second; Q_ASSERT( coords.size() > coordSet ); - coords[coordSet][j + off] = tempValue.get(); + coords[coordSet][j + off] = tempValue.get( nif, nullptr ); } break; case NiMesh::F_UINT8_4: @@ -401,13 +401,13 @@ void Mesh::updateData_NiMesh( const NifModel * nif ) case NiMesh::F_NORMUINT8_4: Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); if ( compType == NiMesh::E_COLOR ) - colors[j + off] = tempValue.get(); + colors[j + off] = tempValue.get( nif, nullptr ); break; case NiMesh::F_NORMUINT8_4_BGRA: Q_ASSERT( usage == NiMesh::USAGE_VERTEX ); if ( compType == NiMesh::E_COLOR ) { // Swizzle BGRA -> RGBA - auto c = tempValue.get().data(); + auto c = tempValue.get( nif, nullptr ).data(); colors[j + off] = {c[2], c[1], c[0], c[3]}; } break; @@ -472,11 +472,11 @@ void Mesh::updateData_NiTriShape( const NifModel * nif ) { // Find iData and iSkin blocks among the children for ( auto childLink : nif->getChildLinks( id() ) ) { - QModelIndex iChild = nif->getBlock( childLink ); + QModelIndex iChild = nif->getBlockIndex( childLink ); if ( !iChild.isValid() ) continue; - if ( nif->inherits( iChild, "NiTriShapeData" ) || nif->inherits( iChild, "NiTriStripsData" ) ) { + if ( nif->blockInherits( iChild, "NiTriShapeData" ) || nif->blockInherits( iChild, "NiTriStripsData" ) ) { if ( !iData.isValid() ) { iData = iChild; } else if ( iData != iChild ) { @@ -484,7 +484,7 @@ void Mesh::updateData_NiTriShape( const NifModel * nif ) tr( "Block %1 has multiple data blocks" ).arg( id() ) ); } - } else if ( nif->inherits( iChild, "NiSkinInstance" ) ) { + } else if ( nif->blockInherits( iChild, "NiSkinInstance" ) ) { if ( !iSkin.isValid() ) { iSkin = iChild; } else if ( iSkin != iChild ) { @@ -519,7 +519,7 @@ void Mesh::updateData_NiTriShape( const NifModel * nif ) if ( iExtraData.isValid() ) { int nExtra = nif->rowCount( iExtraData ); for ( int e = 0; e < nExtra; e++ ) { - QModelIndex iExtra = nif->getBlock( nif->getLink( iExtraData.child( e, 0 ) ), "NiBinaryExtraData" ); + QModelIndex iExtra = nif->getBlockIndex( nif->getLink( iExtraData.child( e, 0 ) ), "NiBinaryExtraData" ); if ( nif->get( iExtra, "Name" ) == "Tangent space (binormal & tangent vectors)" ) { iTangentData = iExtra; QByteArray data = nif->get( iExtra, "Binary Data" ); diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 95ac7e4d9..b585fbb53 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -294,7 +294,7 @@ void Node::updateImpl( const NifModel * nif, const QModelIndex & index ) // Properties properties.clear(); for ( auto l : nif->getLinkArray(iBlock, "Properties") ) - properties.add( scene->getProperty(nif, nif->getBlock(l)) ); + properties.add( scene->getProperty(nif, nif->getBlockIndex(l)) ); properties.add( scene->getProperty(nif, iBlock, "Shader Property", "BSShaderProperty") ); properties.add( scene->getProperty(nif, iBlock, "Alpha Property", "NiAlphaProperty") ); @@ -310,7 +310,7 @@ void Node::updateImpl( const NifModel * nif, const QModelIndex & index ) qint32 link = nif->getLink( iChildren.child( c, 0 ) ); if ( lChildren.contains( link ) ) { - QModelIndex iChild = nif->getBlock( link ); + QModelIndex iChild = nif->getBlockIndex( link ); Node * node = scene->getNode( nif, iChild ); if ( node ) node->makeParent( this ); @@ -473,9 +473,9 @@ void Node::transform() // (need this later in the drawing stage for the constraints) auto nif = NifModel::fromValidIndex( iBlock ); if ( nif && nif->getBSVersion() > 0 ) { - QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); + QModelIndex iObject = nif->getBlockIndex( nif->getLink( iBlock, "Collision Object" ) ); if ( iObject.isValid() ) { - QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); + QModelIndex iBody = nif->getBlockIndex( nif->getLink( iObject, "Body" ) ); if ( iBody.isValid() ) { Transform t; @@ -570,7 +570,7 @@ void Node::drawSelection() const return; bool extraData = false; - auto currentBlock = nif->getBlockName( scene->currentBlock ); + auto currentBlock = nif->itemName( scene->currentBlock ); if ( currentBlock == "BSConnectPoint::Parents" ) extraData = nif->getBlockNumber( iBlock ) == 0; // Root Node only @@ -768,7 +768,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackrowCount( iShapes ); r++ ) { if ( !Node::SELECTING ) { - if ( scene->currentBlock == nif->getBlock( nif->getLink( iShapes.child( r, 0 ) ) ) ) { + if ( scene->currentBlock == nif->getBlockIndex( nif->getLink( iShapes.child( r, 0 ) ) ) ) { // fix: add selected visual to havok meshes glHighlightColor(); glLineWidth( 2.5 ); @@ -781,7 +781,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlock( nif->getLink( iShapes.child( r, 0 ) ) ), stack, scene, origin_color3fv ); + drawHvkShape( nif, nif->getBlockIndex( nif->getLink( iShapes.child( r, 0 ) ) ), stack, scene, origin_color3fv ); } } } else if ( name == "bhkTransformShape" || name == "bhkConvexTransformShape" ) { @@ -793,7 +793,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlock( nif->getLink( iShape, "Shape" ) ), stack, scene, origin_color3fv ); + drawHvkShape( nif, nif->getBlockIndex( nif->getLink( iShape, "Shape" ) ), stack, scene, origin_color3fv ); glPopMatrix(); } else if ( name == "bhkSphereShape" ) { if ( Node::SELECTING ) { @@ -867,7 +867,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackcurrentBlock == nif->getBlock( nif->getLink( iShape, "Shape" ) ) ) { + if ( scene->currentBlock == nif->getBlockIndex( nif->getLink( iShape, "Shape" ) ) ) { // fix: add selected visual to havok meshes glHighlightColor(); glLineWidth( 1.5f ); // taken from "DrawTriangleSelection" @@ -877,14 +877,14 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlock( nif->getLink( iShape, "Shape" ) ), stack, scene, origin_color3fv ); + drawHvkShape( nif, nif->getBlockIndex( nif->getLink( iShape, "Shape" ) ), stack, scene, origin_color3fv ); } else if ( name == "bhkPackedNiTriStripsShape" || name == "hkPackedNiTriStripsData" ) { if ( Node::SELECTING ) { int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iShape ) ); glColor4ubv( (GLubyte *)&s_nodeId ); } - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( iShape, "Data" ) ); if ( iData.isValid() ) { QVector verts = nif->getArray( iData, "Vertices" ); @@ -923,11 +923,11 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackrowCount( iTris ); t++ ) // DrawTriangleIndex( verts, nif->get( iTris.child( t, 0 ), "Triangle" ), t ); - } else if ( nif->isCompound( nif->getBlockType( scene->currentIndex ) ) ) { + } else if ( nif->isCompound( nif->itemType( scene->currentIndex ) ) ) { Triangle tri = nif->get( iTris.child( i, 0 ), "Triangle" ); DrawTriangleSelection( verts, tri ); //DrawTriangleIndex( verts, tri, i ); - } else if ( nif->getBlockName( scene->currentIndex ) == "Normal" ) { + } else if ( nif->itemName( scene->currentIndex ) == "Normal" ) { Triangle tri = nif->get( scene->currentIndex.parent(), "Triangle" ); Vector3 triCentre = ( verts.value( tri.v1() ) + verts.value( tri.v2() ) + verts.value( tri.v3() ) ) / 3.0; glLineWidth( 1.5f ); @@ -1079,7 +1079,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c glColor4ubv( (GLubyte *)&s_nodeId ); glLineWidth( 5 ); // make hitting a line a litlle bit more easy } else { - if ( scene->currentBlock == nif->getBlock( iConstraint ) ) { + if ( scene->currentBlock == nif->getBlockIndex( iConstraint ) ) { // fix: add selected visual to havok meshes glHighlightColor(); color_a.fromQColor( highlightColor ); @@ -1399,10 +1399,10 @@ void Node::drawHavok() return; // Draw BSMultiBound - auto iBSMultiBound = nif->getBlock( nif->getLink( iBlock, "Multi Bound" ), "BSMultiBound" ); + auto iBSMultiBound = nif->getBlockIndex( nif->getLink( iBlock, "Multi Bound" ), "BSMultiBound" ); if ( iBSMultiBound.isValid() ) { - auto iBSMultiBoundData = nif->getBlock( nif->getLink( iBSMultiBound, "Data" ), "BSMultiBoundData" ); + auto iBSMultiBoundData = nif->getBlockIndex( nif->getLink( iBSMultiBound, "Data" ), "BSMultiBoundData" ); if ( iBSMultiBoundData.isValid() ) { Vector3 a, b; @@ -1455,7 +1455,7 @@ void Node::drawHavok() if ( iExtraDataList.isValid() ) { for ( int d = 0; d < nif->rowCount( iExtraDataList ); d++ ) { - QModelIndex iBound = nif->getBlock( nif->getLink( iExtraDataList.child( d, 0 ) ), "BSBound" ); + QModelIndex iBound = nif->getBlockIndex( nif->getLink( iExtraDataList.child( d, 0 ) ), "BSBound" ); if ( !iBound.isValid() ) continue; @@ -1483,11 +1483,11 @@ void Node::drawHavok() } } - QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); + QModelIndex iObject = nif->getBlockIndex( nif->getLink( iBlock, "Collision Object" ) ); if ( !iObject.isValid() ) return; - QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); + QModelIndex iBody = nif->getBlockIndex( nif->getLink( iObject, "Body" ) ); glPushMatrix(); glLoadMatrix( scene->view ); @@ -1527,7 +1527,7 @@ void Node::drawHavok() glColor3fv( colors[ color_index ] ); if ( !Node::SELECTING ) { - if ( scene->currentBlock == nif->getBlock( nif->getLink( iBody, "Shape" ) ) ) { + if ( scene->currentBlock == nif->getBlockIndex( 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 @@ -1542,7 +1542,7 @@ void Node::drawHavok() if ( Node::SELECTING ) glLineWidth( 5 ); // make selection click a little more easy - drawHvkShape( nif, nif->getBlock( nif->getLink( iBody, "Shape" ) ), shapeStack, scene, colors[ color_index ] ); + drawHvkShape( nif, nif->getBlockIndex( nif->getLink( iBody, "Shape" ) ), shapeStack, scene, colors[ color_index ] ); if ( Node::SELECTING && scene->hasOption(Scene::ShowAxes) ) { int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iBody ) ); @@ -1557,9 +1557,9 @@ void Node::drawHavok() glPopMatrix(); for ( const auto l : nif->getLinkArray( iBody, "Constraints" ) ) { - QModelIndex iConstraint = nif->getBlock( l ); + QModelIndex iConstraint = nif->getBlockIndex( l ); - if ( nif->inherits( iConstraint, "bhkConstraint" ) ) + if ( nif->blockInherits( iConstraint, "bhkConstraint" ) ) drawHvkConstraint( nif, iConstraint, scene ); } } @@ -1788,7 +1788,7 @@ void Node::drawFurn() for ( int p = 0; p < nif->rowCount( iExtraDataList ); p++ ) { // DONE: never seen Furn in nifs, so there may be a need of a fix here later - saw one, fixed a bug - QModelIndex iFurnMark = nif->getBlock( nif->getLink( iExtraDataList.child( p, 0 ) ), "BSFurnitureMarker" ); + QModelIndex iFurnMark = nif->getBlockIndex( nif->getLink( iExtraDataList.child( p, 0 ) ), "BSFurnitureMarker" ); if ( !iFurnMark.isValid() ) continue; @@ -1822,7 +1822,7 @@ void Node::drawShapes( NodeList * secondPass, bool presort ) // BSOrderedNode support // Only set if true (|=) so that it propagates to all children - presort |= nif->getBlock( iBlock, "BSOrderedNode" ).isValid(); + presort |= nif->getBlockIndex( iBlock, "BSOrderedNode" ).isValid(); presorted = presort; if ( presorted ) @@ -1873,7 +1873,7 @@ BoundSphere Node::bounds() const boundsphere |= BoundSphere( trans, rad.length() ); } - if ( nif->getBlockType( iBlock ) == "NiMesh" ) + if ( nif->itemType( iBlock ) == "NiMesh" ) boundsphere |= BoundSphere( nif, iBlock ); // BSBound collision bounding box @@ -1881,7 +1881,7 @@ BoundSphere Node::bounds() const if ( iExtraDataList.isValid() ) { for ( int d = 0; d < nif->rowCount( iExtraDataList ); d++ ) { - QModelIndex iBound = nif->getBlock( nif->getLink( iExtraDataList.child( d, 0 ) ), "BSBound" ); + QModelIndex iBound = nif->getBlockIndex( nif->getLink( iExtraDataList.child( d, 0 ) ), "BSBound" ); if ( !iBound.isValid() ) continue; @@ -1913,7 +1913,7 @@ void LODNode::updateImpl( const NifModel * nif, const QModelIndex & index ) if ( ( index == iBlock ) || ( iData.isValid() && index == iData ) ) { ranges.clear(); - iData = nif->getBlock( nif->getLink( iBlock, "LOD Level Data" ), "NiRangeLODData" ); + iData = nif->getBlockIndex( nif->getLink( iBlock, "LOD Level Data" ), "NiRangeLODData" ); QModelIndex iLevels; if ( iData.isValid() ) { diff --git a/src/gl/glparticles.cpp b/src/gl/glparticles.cpp index 0c0199f3c..b7a21cbbb 100644 --- a/src/gl/glparticles.cpp +++ b/src/gl/glparticles.cpp @@ -58,12 +58,12 @@ void Particles::updateImpl( const NifModel * nif, const QModelIndex & index ) if ( index == iBlock ) { for (const auto link : nif->getChildLinks(id())) { - QModelIndex iChild = nif->getBlock(link); + QModelIndex iChild = nif->getBlockIndex(link); if (!iChild.isValid()) continue; - if (nif->inherits(iChild, "NiParticlesData")) { + if (nif->blockInherits(iChild, "NiParticlesData")) { iData = iChild; updateData = true; } diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 30cad74dd..305686232 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -321,7 +321,7 @@ void TexturingProperty::updateImpl( const NifModel * nif, const QModelIndex & in QModelIndex iTex = nif->getIndex( iBlock, texnames[t] ); if ( iTex.isValid() ) { - textures[t].iSource = nif->getBlock( nif->getLink( iTex, "Source" ), "NiSourceTexture" ); + textures[t].iSource = nif->getBlockIndex( nif->getLink( iTex, "Source" ), "NiSourceTexture" ); textures[t].coordset = nif->get( iTex, "UV Set" ); int filterMode = 0, clampMode = 0; @@ -544,7 +544,7 @@ void TextureProperty::updateImpl( const NifModel * nif, const QModelIndex & inde Property::updateImpl( nif, index ); if ( index == iBlock ) { - iImage = nif->getBlock( nif->getLink( iBlock, "Image" ), "NiImage" ); + iImage = nif->getBlockIndex( nif->getLink( iBlock, "Image" ), "NiImage" ); } } @@ -848,7 +848,7 @@ void BSShaderLightingProperty::updateImpl( const NifModel * nif, const QModelInd Property::updateImpl( nif, index ); if ( index == iBlock ) { - iTextureSet = nif->getBlock( nif->getLink( iBlock, "Texture Set" ), "BSShaderTextureSet" ); + iTextureSet = nif->getBlockIndex( nif->getLink( iBlock, "Texture Set" ), "BSShaderTextureSet" ); iWetMaterial = nif->getIndex( iBlock, "Root Material" ); } } diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 2aeafd440..fa8c64864 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -132,7 +132,7 @@ void Scene::update( const NifModel * nif, const QModelIndex & index ) return; if ( index.isValid() ) { - QModelIndex block = nif->getBlock( index ); + QModelIndex block = nif->getBlockIndex( index ); if ( !block.isValid() ) return; @@ -153,7 +153,7 @@ void Scene::update( const NifModel * nif, const QModelIndex & index ) roots.clear(); for ( const auto link : nif->getRootLinks() ) { - QModelIndex iBlock = nif->getBlock( link ); + QModelIndex iBlock = nif->getBlockIndex( link ); if ( iBlock.isValid() ) { Node * node = getNode( nif, iBlock ); if ( node ) { @@ -235,27 +235,27 @@ Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) return node; auto nodeName = nif->itemName(iNode); - if ( nif->inherits( iNode, "NiNode" ) ) { + if ( nif->blockInherits( iNode, "NiNode" ) ) { if ( nodeName == "NiLODNode" ) node = new LODNode( this, iNode ); else if ( nodeName == "NiBillboardNode" ) node = new BillboardNode( this, iNode ); else node = new Node( this, iNode ); - } else if ( nodeName == "NiTriShape" || nodeName == "NiTriStrips" || nif->inherits( iNode, "NiTriBasedGeom" ) ) { + } else if ( nodeName == "NiTriShape" || nodeName == "NiTriStrips" || nif->blockInherits( iNode, "NiTriBasedGeom" ) ) { node = new Mesh( this, iNode ); shapes += static_cast(node); } else if ( nif->checkVersion( 0x14050000, 0 ) && nodeName == "NiMesh" ) { node = new Mesh( this, iNode ); } - //else if ( nif->inherits( iNode, "AParticleNode" ) || nif->inherits( iNode, "AParticleSystem" ) ) - else if ( nif->inherits( iNode, "NiParticles" ) ) { + //else if ( nif->blockInherits( iNode, "AParticleNode" ) || nif->blockInherits( iNode, "AParticleSystem" ) ) + else if ( nif->blockInherits( iNode, "NiParticles" ) ) { // ... where did AParticleSystem go? node = new Particles( this, iNode ); - } else if ( nif->inherits( iNode, "BSTriShape" ) ) { + } else if ( nif->blockInherits( iNode, "BSTriShape" ) ) { node = new BSShape( this, iNode ); shapes += static_cast(node); - } else if ( nif->inherits( iNode, "NiAVObject" ) ) { + } else if ( nif->blockInherits( iNode, "NiAVObject" ) ) { if ( nodeName == "BSTreeNode" ) node = new Node( this, iNode ); } @@ -282,8 +282,8 @@ Property * Scene::getProperty( const NifModel * nif, const QModelIndex & iProper Property * Scene::getProperty( const NifModel * nif, const QModelIndex & iParentBlock, const QString & itemName, const QString & mustInherit ) { - QModelIndex iPropertyBlock = nif->getBlock( nif->getLink(iParentBlock, itemName) ); - if ( iPropertyBlock.isValid() && nif->inherits(iPropertyBlock, mustInherit) ) + QModelIndex iPropertyBlock = nif->getBlockIndex( nif->getLink(iParentBlock, itemName) ); + if ( iPropertyBlock.isValid() && nif->blockInherits(iPropertyBlock, mustInherit) ) return getProperty( nif, iPropertyBlock ); return nullptr; } diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index 86e61f6b6..8445e2ed8 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -413,7 +413,7 @@ int TexCache::bind( const QModelIndex & iSource, Game::GameMode game ) auto nif = NifModel::fromValidIndex(iSource); if ( nif ) { if ( nif->get( iSource, "Use External" ) == 0 ) { - QModelIndex iData = nif->getBlock( nif->getLink( iSource, "Pixel Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( iSource, "Pixel Data" ) ); if ( iData.isValid() ) { Tex * tx = embedTextures.value( iData ); @@ -481,7 +481,7 @@ QString TexCache::info( const QModelIndex & iSource ) auto nif = NifModel::fromValidIndex(iSource); if ( nif ) { if ( nif->get( iSource, "Use External" ) == 0 ) { - QModelIndex iData = nif->getBlock( nif->getLink( iSource, "Pixel Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( iSource, "Pixel Data" ) ); if ( iData.isValid() ) { Tex * tx = embedTextures.value( iData ); diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index 1f04dea7e..57c91c046 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -759,7 +759,7 @@ bool texLoad( const QModelIndex & iData, QString & texformat, GLenum & target, G { texformat += " (PAL8)"; // Read the NiPalette entries; change this if we change NiPalette in nif.xml - QModelIndex iPalette = nif->getBlock( nif->getLink( iData, "Palette" ) ); + QModelIndex iPalette = nif->getBlockIndex( nif->getLink( iData, "Palette" ) ); if ( iPalette.isValid() ) { QVector map; @@ -833,7 +833,7 @@ GLuint texLoadNIF( QIODevice & f, QString & texformat, GLenum & target, GLuint & QPersistentModelIndex iRoot; for ( const auto l : pix.getRootLinks() ) { - QModelIndex iData = pix.getBlock( l, "NiPixelFormat" ); + QModelIndex iData = pix.getBlockIndex( l, "NiPixelFormat" ); if ( !iData.isValid() || iData == QModelIndex() ) throw QString( "this is not a normal .nif file; there should be only pixel data as root blocks" ); @@ -1598,7 +1598,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) // possibly update this to ATextureRenderData... QPersistentModelIndex iPixData; - iPixData = pix.getBlock( 0, "NiPixelData" ); + iPixData = pix.getBlockIndex( 0, "NiPixelData" ); if ( !iPixData.isValid() ) throw QString( "Texture .nifs should only have NiPixelData blocks" ); @@ -1669,7 +1669,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) QModelIndex srcMipMaps = pix.getIndex( iPixData, "Mipmaps" ); QModelIndex destMipMaps = nif->getIndex( iData, "Mipmaps" ); - nif->updateArray( destMipMaps ); + nif->updateArraySize( destMipMaps ); for ( int i = 0; i < pix.rowCount( srcMipMaps ); i++ ) { nif->set( destMipMaps.child( i, 0 ), "Width", pix.get( srcMipMaps.child( i, 0 ), "Width" ) ); @@ -1683,7 +1683,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) QModelIndex destPixelData = nif->getIndex( iData, "Pixel Data" ); for ( int i = 0; i < pix.rowCount( srcPixelData ); i++ ) { - nif->updateArray( destPixelData.child( i, 0 ) ); + nif->updateArraySize( destPixelData.child( i, 0 ) ); nif->set( destPixelData.child( i, 0 ), "Pixel Data", pix.get( srcPixelData.child( i, 0 ), "Pixel Data" ) ); } @@ -1737,7 +1737,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( iData, "Num Mipmaps", mipmaps ); nif->set( iData, "Bytes Per Pixel", 4 ); QModelIndex destMipMaps = nif->getIndex( iData, "Mipmaps" ); - nif->updateArray( destMipMaps ); + nif->updateArraySize( destMipMaps ); QByteArray pixelData; @@ -1773,7 +1773,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( iData, "Num Pixels", mipmapOffset ); QModelIndex iPixelData = nif->getIndex( iData, "Pixel Data" ); - nif->updateArray( iPixelData ); + nif->updateArraySize( iPixelData ); nif->set( iPixelData, "Pixel Data", pixelData ); @@ -1916,7 +1916,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) //qDebug() << "Mipmap count: " << ddsHeader.dwMipMapCount; nif->set( iData, "Num Mipmaps", ddsHeader.dwMipMapCount ); QModelIndex destMipMaps = nif->getIndex( iData, "Mipmaps" ); - nif->updateArray( destMipMaps ); + nif->updateArraySize( destMipMaps ); nif->set( iData, "Bytes Per Pixel", ddsHeader.ddspf.dwRGBBitCount / 8 ); @@ -1950,7 +1950,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) // finally, copy the data... QModelIndex iPixelData = nif->getIndex( iData, "Pixel Data" ); - nif->updateArray( iPixelData ); + nif->updateArraySize( iPixelData ); f.seek( 4 + ddsHeader.dwSize ); //qDebug() << "Reading from " << f.pos(); diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 91e5ea86f..290ca7212 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -348,7 +348,7 @@ Transform bhkBodyTrans( const NifModel * nif, const QModelIndex & index ) qint32 l = nif->getBlockNumber( index ); while ( (l = nif->getParent( l )) >= 0 ) { - QModelIndex iAV = nif->getBlock( l, "NiAVObject" ); + QModelIndex iAV = nif->getBlockIndex( l, "NiAVObject" ); if ( iAV.isValid() ) t = Transform( nif, iAV ) * t; @@ -950,7 +950,7 @@ void drawNiTSS( const NifModel * nif, const QModelIndex & iShape, bool solid ) { QModelIndex iStrips = nif->getIndex( iShape, "Strips Data" ); for ( int r = 0; r < nif->rowCount( iStrips ); r++ ) { - QModelIndex iStripData = nif->getBlock( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ); + QModelIndex iStripData = nif->getBlockIndex( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ); if ( iStripData.isValid() ) { QVector verts = nif->getArray( iStripData, "Vertices" ); @@ -986,7 +986,7 @@ void drawNiTSS( const NifModel * nif, const QModelIndex & iShape, bool solid ) void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid ) { - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( iShape, "Data" ) ); if ( iData.isValid() ) { QModelIndex iBigVerts = nif->getIndex( iData, "Big Verts" ); QModelIndex iBigTris = nif->getIndex( iData, "Big Tris" ); diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index c6bc6304d..eaf2e684c 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -127,9 +127,9 @@ QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QVe auto blk = blkid.remove( "HEADER/" ); if ( blk.contains("/") ) { auto blks = blk.split( "/" ); - return nif->getIndex( nif->getIndex( nif->getHeader(), blks.at(0) ), blks.at(1) ); + return nif->getIndex( nif->getIndex( nif->getHeaderIndex(), blks.at(0) ), blks.at(1) ); } - return nif->getIndex( nif->getHeader(), blk ); + return nif->getIndex( nif->getHeaderIndex(), blk ); } int pos = blkid.indexOf( "/" ); @@ -140,7 +140,7 @@ QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QVe } for ( QModelIndex iBlock : iBlocks ) { - if ( nif->inherits( iBlock, blkid ) ) { + if ( nif->blockInherits( iBlock, blkid ) ) { if ( childid.isEmpty() ) return iBlock; @@ -160,18 +160,20 @@ bool Renderer::ConditionSingle::eval( const NifModel * nif, const QVectorgetValue( iLeft ); - - if ( val.isString() ) - return compare( val.toString(), right ) ^ invert; - else if ( val.isCount() ) - return compare( val.toCount(), right.toULongLong( nullptr, 0 ) ) ^ invert; - else if ( val.isFloat() ) - return compare( val.toFloat(), (float)right.toDouble() ) ^ invert; - else if ( val.isFileVersion() ) - return compare( val.toFileVersion(), right.toUInt( nullptr, 0 ) ) ^ invert; - else if ( val.type() == NifValue::tBSVertexDesc ) - return compare( (uint)val.get().GetFlags(), right.toUInt( nullptr, 0 ) ) ^ invert; + const NifItem * item = nif->getItem( iLeft ); + if ( !item ) + return false; + + if ( item->valueIsString() ) + return compare( item->valueToString(), right ) ^ invert; + else if ( item->valueIsCount() ) + return compare( item->valueToCount(), right.toULongLong( nullptr, 0 ) ) ^ invert; + else if ( item->valueIsFloat() ) + return compare( item->valueToFloat(), (float)right.toDouble() ) ^ invert; + else if ( item->valueIsFileVersion() ) + return compare( item->valueToFileVersion(), right.toUInt( nullptr, 0 ) ) ^ invert; + else if ( item->valueType() == NifValue::tBSVertexDesc ) + return compare( (uint) item->get().GetFlags(), right.toUInt( nullptr, 0 ) ) ^ invert; return false; } diff --git a/src/glview.cpp b/src/glview.cpp index 5526694ab..1f7bf63d9 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -848,7 +848,7 @@ int indexAt( /*GLuint *buffer,*/ NifModel * model, Scene * scene, QList 0 ) { - auto furnBlock = model->getBlock( model->index( 3, 0, model->getBlock( choose & 0x0ffff ) ), "BSFurnitureMarker" ); + auto furnBlock = model->getBlockIndex( model->index( 3, 0, model->getBlockIndex( choose & 0x0ffff ) ), "BSFurnitureMarker" ); if ( furnBlock.isValid() ) { furn = choose >> 16; @@ -912,7 +912,7 @@ QModelIndex GLView::indexAt( const QPoint & pos, int cycle ) chooseIndex = shape->vertexAt( vert ); } else if ( choose != -1 ) { // Block Index - chooseIndex = model->getBlock( choose ); + chooseIndex = model->getBlockIndex( choose ); if ( furn != -1 ) { // Furniture Row @ Block Index @@ -1139,7 +1139,7 @@ void GLView::setCurrentIndex( const QModelIndex & index ) if ( !( model && index.model() == model ) ) return; - scene->currentBlock = model->getBlock( index ); + scene->currentBlock = model->getBlockIndex( index ); scene->currentIndex = index.sibling( index.row(), 0 ); update(); @@ -1607,14 +1607,14 @@ void GLView::dragMoveEvent( QDragMoveEvent * e ) fnDragTexOrg = QString(); } - QModelIndex iObj = model->getBlock( indexAt( e->pos() ), "NiAVObject" ); + QModelIndex iObj = model->getBlockIndex( indexAt( e->pos() ), "NiAVObject" ); if ( iObj.isValid() ) { for ( const auto l : model->getChildLinks( model->getBlockNumber( iObj ) ) ) { - QModelIndex iTxt = model->getBlock( l, "NiTexturingProperty" ); + QModelIndex iTxt = model->getBlockIndex( l, "NiTexturingProperty" ); if ( iTxt.isValid() ) { - QModelIndex iSrc = model->getBlock( model->getLink( iTxt, "Base Texture/Source" ), "NiSourceTexture" ); + QModelIndex iSrc = model->getBlockIndex( model->getLink( iTxt, "Base Texture/Source" ), "NiSourceTexture" ); if ( iSrc.isValid() ) { iDragTarget = model->getIndex( iSrc, "File Name" ); @@ -1763,7 +1763,7 @@ void GLView::mouseReleaseEvent( QMouseEvent * event ) if ( !(mods & Qt::AltModifier) ) { QModelIndex idx = indexAt( event->pos(), cycleSelect ); - scene->currentBlock = model->getBlock( idx ); + scene->currentBlock = model->getBlockIndex( idx ); scene->currentIndex = idx.sibling( idx.row(), 0 ); if ( idx.isValid() ) { diff --git a/src/lib/importex/3ds.cpp b/src/lib/importex/3ds.cpp index 223dc4547..7ad330aff 100644 --- a/src/lib/importex/3ds.cpp +++ b/src/lib/importex/3ds.cpp @@ -105,7 +105,7 @@ static void addLink( NifModel * nif, const QModelIndex & iBlock, const QString & QModelIndex iSize = nif->getIndex( iBlock, QString( "Num %1" ).arg( name ) ); int numIndices = nif->get( iSize ); nif->set( iSize, numIndices + 1 ); - nif->updateArray( iArray ); + nif->updateArraySize( iArray ); nif->setLink( iArray.child( numIndices, 0 ), link ); } @@ -170,7 +170,7 @@ void import3ds( NifModel * nif, const QModelIndex & index ) // If no existing node is selected, create a group node. Otherwise use selected node QPersistentModelIndex iRoot, iNode, iShape, iMaterial, iData, iTexProp, iTexSource; - QModelIndex iBlock = nif->getBlock( index ); + QModelIndex iBlock = nif->getBlockIndex( index ); //Be sure the user hasn't clicked on a NiTriStrips object if ( iBlock.isValid() && nif->itemName( iBlock ) == "NiTriStrips" ) { @@ -186,7 +186,7 @@ void import3ds( NifModel * nif, const QModelIndex & index ) int par_num = nif->getParent( nif->getBlockNumber( index ) ); if ( par_num != -1 ) { - iNode = nif->getBlock( par_num ); + iNode = nif->getBlockIndex( par_num ); } //Find material, texture, and data objects @@ -194,7 +194,7 @@ void import3ds( NifModel * nif, const QModelIndex & index ) for ( const auto child : children ) { if ( child != -1 ) { - QModelIndex temp = nif->getBlock( child ); + QModelIndex temp = nif->getBlockIndex( child ); QString type = nif->itemName( temp ); if ( type == "NiMaterialProperty" ) { @@ -208,7 +208,7 @@ void import3ds( NifModel * nif, const QModelIndex & index ) QList chn = nif->getChildLinks( nif->getBlockNumber( iTexProp ) ); for ( const auto c : chn ) { - QModelIndex temp = nif->getBlock( c ); + QModelIndex temp = nif->getBlockIndex( c ); QString type = nif->itemName( temp ); if ( (type == "NiSourceTexture") || (type == "NiImage") ) { @@ -703,22 +703,22 @@ void import3ds( NifModel * nif, const QModelIndex & index ) nif->set( iData, "Num Vertices", mesh->vertices.count() ); nif->set( iData, "Has Vertices", 1 ); - nif->updateArray( iData, "Vertices" ); + nif->updateArraySize( iData, "Vertices" ); nif->setArray( iData, "Vertices", mesh->vertices ); nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); + nif->updateArraySize( iData, "Normals" ); nif->setArray( iData, "Normals", mesh->normals ); nif->set( iData, "Has UV", 1 ); nif->set( iData, "Data Flags", 1 ); QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - nif->updateArray( iTexCo ); - nif->updateArray( iTexCo.child( 0, 0 ) ); + nif->updateArraySize( iTexCo ); + nif->updateArraySize( iTexCo.child( 0, 0 ) ); nif->setArray( iTexCo.child( 0, 0 ), mesh->texcoords ); nif->set( iData, "Has Triangles", 1 ); nif->set( iData, "Num Triangles", triangles.count() ); nif->set( iData, "Num Triangle Points", triangles.count() * 3 ); - nif->updateArray( iData, "Triangles" ); + nif->updateArraySize( iData, "Triangles" ); nif->setArray( iData, "Triangles", triangles ); Vector3 center; diff --git a/src/lib/importex/col.cpp b/src/lib/importex/col.cpp index e4b69514b..6b2fe6464 100644 --- a/src/lib/importex/col.cpp +++ b/src/lib/importex/col.cpp @@ -518,13 +518,13 @@ QDomElement textureElement( const NifModel * nif, QDomElement effect, QModelInde Q_UNUSED( nif ); Q_UNUSED( idx ); QDomElement ret; qint32 texIdx = nif->getLink( childNode, "Source" ); - QModelIndex iTexture = nif->getBlock( texIdx, "NiSourceTexture" ); + QModelIndex iTexture = nif->getBlockIndex( texIdx, "NiSourceTexture" ); int uvSet = nif->get( childNode, "UV Set" ); if ( !iTexture.isValid() ) { // try to use NiTextureProperty attributes texIdx = nif->getLink( childNode, "Image" ); - iTexture = nif->getBlock( texIdx, "NiImage" ); + iTexture = nif->getBlockIndex( texIdx, "NiImage" ); uvSet = 0; // NiTextureProperty only have one texture } @@ -587,7 +587,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) bool haveColors = false; bool haveMaterial = false; int haveUV = 0; - QModelIndex iBlock = nif->getBlock( idx ); + QModelIndex iBlock = nif->getBlockIndex( idx ); QDomElement extra; QDomElement textureBaseTexture; QDomElement textureDarkTexture; @@ -603,9 +603,9 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) return; foreach ( qint32 link, nif->getChildLinks( idx ) ) { - QModelIndex iProp = nif->getBlock( link ); + QModelIndex iProp = nif->getBlockIndex( link ); - if ( nif->inherits( iProp, "NiTexturingProperty" ) ) { + if ( nif->blockInherits( iProp, "NiTexturingProperty" ) ) { if ( !effect.isElement() ) { effect = doc.createElement( "effect" ); effect.setAttribute( "id", QString( "nifid_%1-effect" ).arg( idx ) ); @@ -622,7 +622,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) textureGlowTexture = textureElement( nif, profile, nif->getIndex( iProp, "Glow Texture" ), idx ); // TODO: Shader Textures array and mapping (for DAoC check NiIntegerExtraData for order) - } else if ( nif->inherits( iProp, "NiTextureProperty" ) ) { + } else if ( nif->blockInherits( iProp, "NiTextureProperty" ) ) { if ( !effect.isElement() ) { effect = doc.createElement( "effect" ); effect.setAttribute( "id", QString( "nifid_%1-effect" ).arg( idx ) ); @@ -632,7 +632,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) profile = doc.createElement( "profile_COMMON" ); textureBaseTexture = textureElement( nif, profile, iProp, idx ); - } else if ( nif->inherits( iProp, "NiMaterialProperty" ) || nif->inherits( iProp, "BSLightingShaderProperty" ) ) { + } else if ( nif->blockInherits( iProp, "NiMaterialProperty" ) || nif->blockInherits( iProp, "BSLightingShaderProperty" ) ) { if ( !effect.isElement() ) { effect = doc.createElement( "effect" ); effect.setAttribute( "id", QString( "nifid_%1-effect" ).arg( idx ) ); @@ -644,7 +644,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) // BSLightingShaderProperty inherits textures .. so it's bit ugly hack // 0 = diffuse, 1 = normal qint32 subIdx = nif->getLink( iProp, "Texture Set" ); - QModelIndex iTextures = nif->getBlock( subIdx ); + QModelIndex iTextures = nif->getBlockIndex( subIdx ); if ( iTextures.isValid() ) { int tCount = nif->get( iTextures, "Num Textures" ); @@ -745,7 +745,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) if ( extra.isElement() ) profile.appendChild( extra ); - } else if ( nif->inherits( iProp, "NiTriBasedGeomData" ) ) { + } else if ( nif->blockInherits( iProp, "NiTriBasedGeomData" ) ) { QDomElement geometry = doc.createElement( "geometry" ); geometry.setAttribute( "id", QString( "nifid_%1-lib" ).arg( idx ) ); geometry.setAttribute( "name", QString( "%1-lib" ).arg( nif->get( iBlock, "Name" ).replace( QRegularExpression( "\\W" ), "_" ) ) ); @@ -912,7 +912,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) instanceMaterial.appendChild( bind_vertex_input ); } } else { - qDebug() << "NOT_USED_PROPERTY:" << nif->getBlockName( iProp ); + qDebug() << "NOT_USED_PROPERTY:" << nif->itemName( iProp ); } if ( effect.isElement() ) @@ -928,7 +928,7 @@ void attachNiShape ( const NifModel * nif, QDomElement parentNode, int idx ) */ void attachNiNode ( const NifModel * nif, QDomElement parentNode, int idx ) { - QModelIndex iBlock = nif->getBlock( idx ); + QModelIndex iBlock = nif->getBlockIndex( idx ); // export culling if ( culling && !cullRegExp.pattern().isEmpty() && nif->get( iBlock, "Name" ).contains( cullRegExp ) ) @@ -946,12 +946,12 @@ void attachNiNode ( const NifModel * nif, QDomElement parentNode, int idx ) // parent attach and new loop parentNode.appendChild( node ); foreach ( int l, nif->getChildLinks( idx ) ) { - QModelIndex iChild = nif->getBlock( l ); + QModelIndex iChild = nif->getBlockIndex( l ); if ( iChild.isValid() ) { - if ( nif->inherits( iChild, "NiNode" ) ) + if ( nif->blockInherits( iChild, "NiNode" ) ) attachNiNode( nif, node, l ); - else if ( nif->inherits( iChild, "NiTriBasedGeom" ) ) + else if ( nif->blockInherits( iChild, "NiTriBasedGeom" ) ) attachNiShape( nif, node, l ); else { // qDebug() << "NO_FUNC:" << type; @@ -1031,10 +1031,10 @@ void exportCol( const NifModel * nif, QFileInfo fileInfo ) while ( !roots.empty() ) { int idx = roots.takeFirst(); - QModelIndex iBlock = nif->getBlock( idx ); + QModelIndex iBlock = nif->getBlockIndex( idx ); // get more if NiNode - if ( nif->inherits( iBlock, "NiNode" ) ) + if ( nif->blockInherits( iBlock, "NiNode" ) ) attachNiNode( nif, lv, idx ); } diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index 9753360c2..a83205947 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -205,7 +205,7 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS return; foreach ( qint32 link, nif->getChildLinks( nif->getBlockNumber( iShape ) ) ) { - QModelIndex iProp = nif->getBlock( link ); + QModelIndex iProp = nif->getBlockIndex( link ); if ( nif->isNiBlock( iProp, "NiMaterialProperty" ) ) { mata = nif->get( iProp, "Ambient Color" ); @@ -215,16 +215,16 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS matg = nif->get( iProp, "Glossiness" ); //matn = nif->get( iProp, "Name" ); } else if ( nif->isNiBlock( iProp, "NiTexturingProperty" ) ) { - QModelIndex iBase = nif->getBlock( nif->getLink( nif->getIndex( iProp, "Base Texture" ), "Source" ), "NiSourceTexture" ); + QModelIndex iBase = nif->getBlockIndex( nif->getLink( nif->getIndex( iProp, "Base Texture" ), "Source" ), "NiSourceTexture" ); map_Kd = TexCache::find( nif->get( iBase, "File Name" ), nif->getFolder() ); - QModelIndex iDark = nif->getBlock( nif->getLink( nif->getIndex( iProp, "Decal Texture 1" ), "Source" ), "NiSourceTexture" ); + QModelIndex iDark = nif->getBlockIndex( nif->getLink( nif->getIndex( iProp, "Decal Texture 1" ), "Source" ), "NiSourceTexture" ); decal = TexCache::find( nif->get( iDark, "File Name" ), nif->getFolder() ); - QModelIndex iBump = nif->getBlock( nif->getLink( nif->getIndex( iProp, "Bump Map Texture" ), "Source" ), "NiSourceTexture" ); + QModelIndex iBump = nif->getBlockIndex( nif->getLink( nif->getIndex( iProp, "Bump Map Texture" ), "Source" ), "NiSourceTexture" ); bump = TexCache::find( nif->get( iBump, "File Name" ), nif->getFolder() ); } else if ( nif->isNiBlock( iProp, "NiTextureProperty" ) ) { - QModelIndex iSource = nif->getBlock( nif->getLink( iProp, "Image" ), "NiImage" ); + QModelIndex iSource = nif->getBlockIndex( nif->getLink( iProp, "Image" ), "NiImage" ); map_Kd = TexCache::find( nif->get( iSource, "File Name" ), nif->getFolder() ); } else if ( nif->isNiBlock( iProp, "NiSkinInstance" ) ) { QMessageBox::warning( @@ -239,7 +239,7 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS } else if ( nif->isNiBlock( iProp, "BSEffectShaderProperty" ) ) { map_Kd = TexCache::find( nif->get( iProp, "Source Texture" ), nif->getFolder() ); } else if ( nif->isNiBlock( iProp, { "BSShaderPPLightingProperty", "Lighting30ShaderProperty", "BSLightingShaderProperty" } ) ) { - QModelIndex iArray = nif->getIndex( nif->getBlock( nif->getLink( iProp, "Texture Set" ) ), "Textures" ); + QModelIndex iArray = nif->getIndex( nif->getBlockIndex( nif->getLink( iProp, "Texture Set" ) ), "Textures" ); map_Kd = TexCache::find( nif->get( iArray.child( 0, 0 ) ), nif->getFolder() ); map_Kn = TexCache::find( nif->get( iArray.child( 1, 0 ) ), nif->getFolder() ); @@ -285,7 +285,7 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS obj << "\r\n# " << name << "\r\n\r\ng " << name << "\r\n" << "usemtl " << matn << "\r\n\r\n"; if ( nif->getBSVersion() < 100 ) - writeData( nif, nif->getBlock( nif->getLink( iShape, "Data" ) ), obj, ofs, t ); + writeData( nif, nif->getBlockIndex( nif->getLink( iShape, "Data" ) ), obj, ofs, t ); else writeData( nif, iShape, obj, ofs, t ); } @@ -298,16 +298,16 @@ static void writeParent( const NifModel * nif, const QModelIndex & iNode, QTextS t = t * Transform( nif, iNode ); foreach ( int l, nif->getChildLinks( nif->getBlockNumber( iNode ) ) ) { - QModelIndex iChild = nif->getBlock( l ); + QModelIndex iChild = nif->getBlockIndex( l ); - if ( nif->inherits( iChild, "NiNode" ) ) + if ( nif->blockInherits( iChild, "NiNode" ) ) writeParent( nif, iChild, obj, mtl, ofs, t ); else if ( nif->isNiBlock( iChild, { "NiTriShape", "NiTriStrips" } ) ) writeShape( nif, iChild, obj, mtl, ofs, t * Transform( nif, iChild ) ); else if ( nif->isNiBlock( iChild, { "BSTriShape", "BSSubIndexTriShape", "BSMeshLODTriShape" } ) ) writeShape( nif, iChild, obj, mtl, ofs, t * Transform( nif, iChild ) ); - else if ( nif->inherits( iChild, "NiCollisionObject" ) ) { - QModelIndex iBody = nif->getBlock( nif->getLink( iChild, "Body" ) ); + else if ( nif->blockInherits( iChild, "NiCollisionObject" ) ) { + QModelIndex iBody = nif->getBlockIndex( nif->getLink( iChild, "Body" ) ); if ( iBody.isValid() ) { Transform bt; @@ -318,13 +318,13 @@ static void writeParent( const NifModel * nif, const QModelIndex & iNode, QTextS bt.translation = Vector3( nif->get( iBody, "Translation" ) * 7 ); } - QModelIndex iShape = nif->getBlock( nif->getLink( iBody, "Shape" ) ); + QModelIndex iShape = nif->getBlockIndex( nif->getLink( iBody, "Shape" ) ); if ( nif->isNiBlock( iShape, "bhkMoppBvTreeShape" ) ) { - iShape = nif->getBlock( nif->getLink( iShape, "Shape" ) ); + iShape = nif->getBlockIndex( nif->getLink( iShape, "Shape" ) ); if ( nif->isNiBlock( iShape, "bhkPackedNiTriStripsShape" ) ) { - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( iShape, "Data" ) ); if ( nif->isNiBlock( iData, "hkPackedNiTriStripsData" ) ) { bt = t * bt; @@ -366,7 +366,7 @@ static void writeParent( const NifModel * nif, const QModelIndex & iNode, QTextS QModelIndex iStrips = nif->getIndex( iShape, "Strips Data" ); for ( int r = 0; r < nif->rowCount( iStrips ); r++ ) - writeData( nif, nif->getBlock( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ), obj, ofs, t * bt ); + writeData( nif, nif->getBlockIndex( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ), obj, ofs, t * bt ); } } } @@ -380,14 +380,14 @@ void exportObj( const NifModel * nif, const QModelIndex & index ) //--Determine how the file will export, and be sure the user wants to continue--// QList roots; - QModelIndex iBlock = nif->getBlock( index ); + QModelIndex iBlock = nif->getBlockIndex( index ); QString question; if ( iBlock.isValid() ) { roots.append( nif->getBlockNumber( index ) ); - if ( nif->inherits( index, "NiNode" ) ) { + if ( nif->blockInherits( index, "NiNode" ) ) { question = tr( "NiNode selected. All children of selected node will be exported." ); } else if ( nif->itemName( index ) == "NiTriShape" || nif->itemName( index ) == "NiTriStrips" ) { question = nif->itemName( index ) + tr( " selected. Selected mesh will be exported." ); @@ -450,9 +450,9 @@ void exportObj( const NifModel * nif, const QModelIndex & index ) 1, 1, 1 }; foreach ( int l, roots ) { - QModelIndex iBlock = nif->getBlock( l ); + QModelIndex iBlock = nif->getBlockIndex( l ); - if ( nif->inherits( iBlock, "NiNode" ) ) + if ( nif->blockInherits( iBlock, "NiNode" ) ) writeParent( nif, iBlock, sobj, smtl, ofs, Transform() ); else if ( nif->isNiBlock( iBlock, { "NiTriShape", "NiTriStrips" } ) ) writeShape( nif, iBlock, sobj, smtl, ofs, Transform() ); @@ -558,7 +558,7 @@ static void addLink( NifModel * nif, const QModelIndex & iBlock, const QString & QModelIndex iSize = nif->getIndex( iBlock, QString( "Num %1" ).arg( name ) ); int numIndices = nif->get( iSize ); nif->set( iSize, numIndices + 1 ); - nif->updateArray( iArray ); + nif->updateArraySize( iArray ); nif->setLink( iArray.child( numIndices, 0 ), link ); } @@ -568,7 +568,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) // If no existing node is selected, create a group node. Otherwise use selected node QPersistentModelIndex iNode, iShape, iStripsShape, iMaterial, iData, iTexProp, iTexSource; - QModelIndex iBlock = nif->getBlock( index ); + QModelIndex iBlock = nif->getBlockIndex( index ); bool cBSShaderPPLightingProperty = false; //Be sure the user hasn't clicked on a NiTriStrips object @@ -577,17 +577,17 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) return; } - if ( iBlock.isValid() && nif->inherits( iBlock, "NiNode" ) ) { + if ( iBlock.isValid() && nif->blockInherits( iBlock, "NiNode" ) ) { iNode = iBlock; } else if ( iBlock.isValid() && (nif->itemName( iBlock ) == "NiTriShape" - || (collision && nif->inherits( iBlock, "BSTriShape" ))) ) { + || (collision && nif->blockInherits( iBlock, "BSTriShape" ))) ) { iShape = iBlock; //Find parent of NiTriShape int par_num = nif->getParent( nif->getBlockNumber( iBlock ) ); if ( par_num != -1 ) { - iNode = nif->getBlock( par_num ); + iNode = nif->getBlockIndex( par_num ); } //Find material, texture, and data objects @@ -595,7 +595,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) for ( const auto child : children ) { if ( child != -1 ) { - QModelIndex temp = nif->getBlock( child ); + QModelIndex temp = nif->getBlockIndex( child ); QString type = nif->itemName( temp ); if ( type == "BSShaderPPLightingProperty" ) { @@ -613,7 +613,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) QList chn = nif->getChildLinks( nif->getBlockNumber( iTexProp ) ); for ( const auto c : chn ) { - QModelIndex temp = nif->getBlock( c ); + QModelIndex temp = nif->getBlockIndex( c ); QString type = nif->itemName( temp ); if ( (type == "NiSourceTexture") || (type == "NiImage") ) { @@ -831,11 +831,11 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) nif->set( textureSet, "Num Textures", 9 ); auto iTextures = nif->getIndex( textureSet, "Textures" ); - nif->updateArray( iTextures ); + nif->updateArraySize( iTextures ); QVector texturePaths { mtl.map_Kd, mtl.map_Kn }; nif->setArray( iTextures, texturePaths ); - nif->updateArray( iTextures ); + nif->updateArraySize( iTextures ); } else if ( nif->getVersionNumber() >= 0x0303000D ) { //Newer versions use NiTexturingProperty and NiSourceTexture if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemType( iTexProp ) != "NiTexturingProperty" ) { @@ -931,10 +931,10 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) nif->set( iData, "Num Vertices", verts.count() ); nif->set( iData, "Has Vertices", 1 ); - nif->updateArray( iData, "Vertices" ); + nif->updateArraySize( iData, "Vertices" ); nif->setArray( iData, "Vertices", verts ); nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); + nif->updateArraySize( iData, "Normals" ); nif->setArray( iData, "Normals", norms ); nif->set( iData, "Has UV", 1 ); nif->set( iData, "Num UV Sets", 1 ); @@ -943,18 +943,18 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) if ( nif->getBSVersion() > 34 ) { nif->set( iData, "Has Vertex Colors", 1 ); - nif->updateArray( iData, "Vertex Colors" ); + nif->updateArraySize( iData, "Vertex Colors" ); } QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - nif->updateArray( iTexCo ); - nif->updateArray( iTexCo.child( 0, 0 ) ); + nif->updateArraySize( iTexCo ); + nif->updateArraySize( iTexCo.child( 0, 0 ) ); nif->setArray( iTexCo.child( 0, 0 ), texco ); nif->set( iData, "Has Triangles", 1 ); nif->set( iData, "Num Triangles", triangles.count() ); nif->set( iData, "Num Triangle Points", triangles.count() * 3 ); - nif->updateArray( iData, "Triangles" ); + nif->updateArraySize( iData, "Triangles" ); nif->setArray( iData, "Triangles", triangles ); // "find me a center": see nif.xml for details @@ -1035,10 +1035,10 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) nif->set( iData, "Num Vertices", verts.count() ); nif->set( iData, "Has Vertices", 1 ); - nif->updateArray( iData, "Vertices" ); + nif->updateArraySize( iData, "Vertices" ); nif->setArray( iData, "Vertices", verts ); nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); + nif->updateArraySize( iData, "Normals" ); nif->setArray( iData, "Normals", norms ); Vector3 center; @@ -1069,14 +1069,14 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) QModelIndex iPoints = nif->getIndex( iData, "Points" ); if ( iLengths.isValid() && iPoints.isValid() ) { - nif->updateArray( iLengths ); - nif->updateArray( iPoints ); + nif->updateArraySize( iLengths ); + nif->updateArraySize( iPoints ); int x = 0; int z = 0; for ( const QVector & strip : strips ) { nif->set( iLengths.child( x, 0 ), strip.count() ); QModelIndex iStrip = iPoints.child( x, 0 ); - nif->updateArray( iStrip ); + nif->updateArraySize( iStrip ); nif->setArray( iStrip, strip ); x++; z += strip.count() - 2; @@ -1088,14 +1088,14 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) iStripsShape = nif->insertNiBlock( "bhkNiTriStripsShape" ); // For some reason need to update all the fixed arrays... - nif->updateArray( iStripsShape, "Unused" ); + nif->updateArraySize( iStripsShape, "Unused" ); QPersistentModelIndex iBody = nif->insertNiBlock( "bhkRigidBody" ); nif->setLink( iBody, "Shape", nif->getBlockNumber( iStripsShape ) ); for( int i = 0; i < nif->rowCount( iBody ); i++ ) { auto iChild = iBody.child( i, 0 ); if ( nif->isArray( iChild ) ) - nif->updateArray( iChild ); + nif->updateArraySize( iChild ); } QPersistentModelIndex iObject = nif->insertNiBlock( "bhkCollisionObject" ); @@ -1110,7 +1110,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) if ( shapecount >= 1 ) { addLink( nif, iStripsShape, "Strips Data", nif->getBlockNumber( iData ) ); nif->set( iStripsShape, "Num Filters", shapecount ); - nif->updateArray( iStripsShape, "Filters" ); + nif->updateArraySize( iStripsShape, "Filters" ); } } diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 6cdec1be5..88e533200 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -50,7 +50,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. BaseModel::BaseModel( QObject * p ) : QAbstractItemModel( p ) { - root = new NifItem( 0 ); + root = new NifItem( this, nullptr ); + root->setIsConditionless( true ); parentWindow = qobject_cast(p); msgMode = MSG_TEST; } @@ -60,26 +61,11 @@ BaseModel::~BaseModel() delete root; } -QWidget * BaseModel::getWindow() -{ - return parentWindow; -} - -void BaseModel::setEmitChanges( bool e ) -{ - emitChanges = e; -} - void BaseModel::setMessageMode( MsgMode mode ) { msgMode = mode; } -BaseModel::MsgMode BaseModel::getMessageMode() const -{ - return msgMode; -} - void BaseModel::logMessage( const QString & message, const QString & details, QMessageBox::Icon lvl ) const { if ( msgMode == MSG_USER ) { @@ -99,6 +85,11 @@ void BaseModel::testMsg( const QString & m ) const messages.append( TestMessage() << m ); } +inline NifItem * indexToItem( const QModelIndex & index ) +{ + return static_cast( index.internalPointer() ); +} + void BaseModel::beginInsertRows( const QModelIndex & parent, int first, int last ) { setState( Inserting ); @@ -145,38 +136,39 @@ QList BaseModel::getMessages() const bool BaseModel::isArray( const QModelIndex & index ) const { - return !itemArr1( index ).isEmpty(); + // TODO (Gavrant): I don't like that isArray(const QModelIndex &) and isArray( const NifItem *) check for different conditions. + const NifItem * item = getItem( index ); + return item ? item->isArray() : false; } bool BaseModel::isArray( const NifItem * item ) const { if ( !item ) return false; - return item->isArray() || ( item->parent() && item->parent()->isMultiArray() ); + if ( item->isArray() ) + return true; + const NifItem * p = item->parent(); + if ( p && p->isMultiArray() ) + return true; + return false; } -int BaseModel::getArraySize( NifItem * array ) const +int BaseModel::evalArraySize( const NifItem * array ) const { // shortcut for speed - if ( !isArray( array ) ) + if ( !isArray( array ) ) { + if ( array ) + reportError( array, "evalArraySize", "The input item is not an array." ); return 0; + } + + if ( array == root ) { + reportError( array, "evalArraySize", "The input item is the root."); + return 0; + } - return evaluateInt( array, array->arr1expr() ); -} - -bool BaseModel::updateArray( const QModelIndex & array ) -{ - NifItem * item = static_cast( array.internalPointer() ); - - if ( !( array.isValid() && item && array.model() == this ) ) - return false; - - return updateArrayItem( item ); -} - -bool BaseModel::updateArray( const QModelIndex & parent, const QString & name ) -{ - return updateArray( getIndex( parent, name ) ); + BaseModelEval functor(this, array); + return array->arr1expr().evaluateUInt(functor); } /* @@ -185,143 +177,96 @@ bool BaseModel::updateArray( const QModelIndex & parent, const QString & name ) QString BaseModel::itemName( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return QString(); - - return item->name(); + const NifItem * item = getItem( index ); + if ( item ) + return item->name(); + return QString(); } QString BaseModel::itemType( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return QString(); - - return item->type(); + const NifItem * item = getItem( index ); + if ( item ) + return item->type(); + return QString(); } QString BaseModel::itemTmplt( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return QString(); - - return item->temp(); + const NifItem * item = getItem( index ); + if ( item ) + return item->temp(); + return QString(); } - NifValue BaseModel::getValue( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return NifValue(); - - return item->value(); + const NifItem * item = getItem( index ); + if ( item ) + return item->value(); + return NifValue(); } -// where is -// NifValue BaseModel::getValue( const QModelIndex & parent, const QString & name ) const -// ? - QString BaseModel::itemArg( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return QString(); - - return item->arg(); + const NifItem * item = getItem( index ); + if ( item ) + return item->arg(); + return QString(); } QString BaseModel::itemArr1( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return QString(); - - return item->arr1(); + const NifItem * item = getItem( index ); + if ( item ) + return item->arr1(); + return QString(); } QString BaseModel::itemArr2( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return QString(); - - return item->arr2(); + const NifItem * item = getItem( index ); + if ( item ) + return item->arr2(); + return QString(); } QString BaseModel::itemCond( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return QString(); - - return item->cond(); + const NifItem * item = getItem( index ); + if ( item ) + return item->cond(); + return QString(); } quint32 BaseModel::itemVer1( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return 0; - - return item->ver1(); + const NifItem * item = getItem( index ); + return item ? item->ver1() : 0; } quint32 BaseModel::itemVer2( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return 0; - - return item->ver2(); + const NifItem * item = getItem( index ); + return item ? item->ver2() : 0; } QString BaseModel::itemText( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return QString(); - - return item->text(); -} - - -bool BaseModel::setValue( const QModelIndex & index, const NifValue & val ) -{ - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return false; - - return setItemValue( item, val ); + const NifItem * item = getItem( index ); + if ( item ) + return item->text(); + return QString(); } -bool BaseModel::setValue( const QModelIndex & parent, const QString & name, const NifValue & val ) +bool BaseModel::setItemValue( NifItem * item, const NifValue & val ) { - NifItem * parentItem = static_cast( parent.internalPointer() ); - - if ( !( parent.isValid() && parentItem && parent.model() == this ) ) + if ( !item ) return false; - NifItem * item = getItem( parentItem, name ); - - if ( item ) - return setItemValue( item, val ); - - return false; + item->value() = val; + onItemValueChange( item ); + return true; } @@ -331,56 +276,35 @@ bool BaseModel::setValue( const QModelIndex & parent, const QString & name, cons QModelIndex BaseModel::index( int row, int column, const QModelIndex & parent ) const { - NifItem * parentItem; - - if ( !( parent.isValid() && parent.model() == this ) ) - parentItem = root; - else - parentItem = static_cast( parent.internalPointer() ); - - NifItem * childItem = ( parentItem ? parentItem->child( row ) : 0 ); - - if ( childItem ) - return createIndex( row, column, childItem ); - - return QModelIndex(); + const NifItem * parentItem = parent.isValid() ? getItem(parent) : root; + return itemToIndex( parentItem ? parentItem->child(row) : nullptr, column ); } QModelIndex BaseModel::parent( const QModelIndex & child ) const { - if ( !( child.isValid() && child.model() == this ) ) - return QModelIndex(); - - NifItem * childItem = static_cast( child.internalPointer() ); - + const NifItem * childItem = getItem( child ); if ( !childItem || childItem == root ) return QModelIndex(); - NifItem * parentItem = childItem->parent(); - - if ( !parentItem || parentItem == root ) + const NifItem * parentItem = childItem->parent(); + if ( !parentItem || parentItem == root ) // Yes, we ignore parentItem == root because otherwise "Block Details" shows the whole hiearchy return QModelIndex(); - return createIndex( parentItem->row(), 0, parentItem ); + return itemToIndex( parentItem ); } int BaseModel::rowCount( const QModelIndex & parent ) const { - NifItem * parentItem; - - if ( !( parent.isValid() && parent.model() == this ) ) - parentItem = root; - else - parentItem = static_cast( parent.internalPointer() ); - - return ( parentItem ? parentItem->childCount() : 0 ); + if ( !parent.isValid() ) + return root->childCount(); + const NifItem * parentItem = getItem(parent); + return parentItem ? parentItem->childCount() : 0; } QVariant BaseModel::data( const QModelIndex & index, int role ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) + const NifItem * item = getItem( index ); + if ( !item ) return QVariant(); int column = index.column(); @@ -397,7 +321,7 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const case TypeCol: return item->type(); case ValueCol: - return item->value().toString(); + return item->valueToString(); case ArgCol: return item->arg(); case Arr1Col: @@ -424,7 +348,7 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const case TypeCol: return item->type(); case ValueCol: - return item->value().toVariant(); + return item->valueToVariant(); case ArgCol: return item->arg(); case Arr1Col: @@ -448,11 +372,11 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const switch ( column ) { case ValueCol: { - switch ( item->value().type() ) { + switch ( item->valueType() ) { case NifValue::tWord: case NifValue::tShort: { - quint16 s = item->value().toCount(); + quint16 s = item->valueToCount(); return QString( "dec: %1
hex: 0x%2" ).arg( s ).arg( s, 4, 16, QChar( '0' ) ); } case NifValue::tBool: @@ -460,45 +384,45 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const case NifValue::tUInt: case NifValue::tULittle32: { - quint32 i = item->value().toCount(); + quint32 i = item->valueToCount(); return QString( "dec: %1
hex: 0x%2" ).arg( i ).arg( i, 8, 16, QChar( '0' ) ); } case NifValue::tFloat: case NifValue::tHfloat: { - float f = item->value().toFloat(); - quint32 i = item->value().toCount(); + float f = item->valueToFloat(); + quint32 i = item->valueToCount(); return QString( "float: %1
data: 0x%2" ).arg( f ).arg( i, 8, 16, QChar( '0' ) ); } case NifValue::tFlags: { - quint16 f = item->value().toCount(); + quint16 f = item->valueToCount(); return QString( "dec: %1
hex: 0x%2
bin: 0b%3" ).arg( f ).arg( f, 4, 16, QChar( '0' ) ).arg( f, 16, 2, QChar( '0' ) ); } case NifValue::tVector3: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tHalfVector3: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tByteVector3: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tMatrix: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tQuat: case NifValue::tQuatXYZW: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tColor3: { - Color3 c = item->value().get(); + Color3 c = item->get(); return QString( "R %1
G %2
B %3" ).arg( c[0] ).arg( c[1] ).arg( c[2] ); } case NifValue::tByteColor4: { - Color4 c = item->value().get(); + Color4 c = item->get(); return QString( "R %1
G %2
B %3
A %4" ).arg( c[0] ).arg( c[1] ).arg( c[2] ).arg( c[3] ); } case NifValue::tColor4: { - Color4 c = item->value().get(); + Color4 c = item->get(); return QString( "R %1
G %2
B %3
A %4" ).arg( c[0] ).arg( c[1] ).arg( c[2] ).arg( c[3] ); } default: @@ -513,8 +437,8 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const return QVariant(); case Qt::BackgroundColorRole: { - if ( column == ValueCol && item->value().isColor() ) { - return item->value().toColor(); + if ( column == ValueCol && item->valueIsColor() ) { + return item->valueToColor(); } } return QVariant(); @@ -525,9 +449,11 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const bool BaseModel::setData( const QModelIndex & index, const QVariant & value, int role ) { - NifItem * item = static_cast( index.internalPointer() ); + if ( role != Qt::EditRole ) + return false; - if ( !( index.isValid() && role == Qt::EditRole && index.model() == this && item ) ) + NifItem * item = getItem( index ); + if ( !item ) return false; switch ( index.column() ) { @@ -538,7 +464,7 @@ bool BaseModel::setData( const QModelIndex & index, const QVariant & value, int item->setType( value.toString() ); break; case BaseModel::ValueCol: - item->value().setFromVariant( value ); + item->valueFromVariant( value ); break; case BaseModel::ArgCol: item->setArg( value.toString() ); @@ -566,7 +492,7 @@ bool BaseModel::setData( const QModelIndex & index, const QVariant & value, int } if ( state == Default ) - emit dataChanged( index, index ); + onItemValueChange( item ); return true; } @@ -618,32 +544,17 @@ Qt::ItemFlags BaseModel::flags( const QModelIndex & index ) const Qt::ItemFlags flags; - auto item = static_cast(index.internalPointer()); - - bool condExpr; - if ( item ) - condExpr = item->condition(); - else - condExpr = evalCondition( index, true ); - - if ( condExpr ) + const NifItem * item = indexToItem( index ); + if ( evalCondition(item) ){ flags = (Qt::ItemIsEnabled | Qt::ItemIsSelectable); - - switch ( index.column() ) { - case TypeCol: - case NameCol: - return flags; - case ValueCol: - if ( condExpr ) - return flags | Qt::ItemIsEditable; - - return flags; - - default: - return flags; + if ( index.column() == ValueCol ) + flags |= Qt::ItemIsEditable; } + + return flags; } + /* * load and save */ @@ -689,146 +600,266 @@ void BaseModel::refreshFileInfo( const QString & f ) * searching */ -NifItem * BaseModel::getItem( NifItem * item, const QString & name ) const +const NifItem * BaseModel::getItem( const NifItem * parent, const QString & name, bool reportErrors ) const { - if ( !item || item == root ) + if ( !parent ) return nullptr; - int slash = name.indexOf( "\\" ); + int slash = name.indexOf( QLatin1String("\\") ); if ( slash > 0 ) { QString left = name.left( slash ); QString right = name.right( name.length() - slash - 1 ); - if ( left == ".." ) - return getItem( item->parent(), right ); - - return getItem( getItem( item, left ), right ); + const NifItem * pp = ( left == QLatin1String("..") ) ? parent->parent() : getItem( parent, left ); + return getItem( pp, right ); } - for ( int c = 0; c < item->childCount(); c++ ) { - NifItem * child = item->child( c ); + for ( auto item : parent->childIter() ) + if ( item->hasName(name) && evalCondition(item) ) + return item; - if ( child->hasName(name) && evalCondition( child ) ) - return child; + if ( reportErrors ) + reportError( parent, tr( "Could not find \"%1\" subitem." ).arg( name ) ); + return nullptr; +} + +const NifItem * BaseModel::getItem( const NifItem * parent, const QLatin1String & name, bool reportErrors ) const +{ + if ( !parent ) + return nullptr; + + int slash = name.indexOf( QLatin1String("\\") ); + if ( slash > 0 ) { + QLatin1String left = name.left( slash ); + QLatin1String right = name.right( name.size() - slash - 1 ); + + const NifItem * pp = ( left == QLatin1String("..") ) ? parent->parent() : getItem( parent, left ); + return getItem( pp, right ); } + for ( auto item : parent->childIter() ) + if ( item->hasName(name) && evalCondition(item) ) + return item; + + if ( reportErrors ) + reportError( parent, tr( "Could not find \"%1\" subitem." ).arg( name ) ); return nullptr; } -/* -* Uses implicit load order -*/ -NifItem * BaseModel::getItemX( NifItem * item, const QString & name ) const +const NifItem * BaseModel::getItem( const NifItem * parent, int childIndex, bool reportErrors ) const { - if ( !item || !item->parent() ) - return 0; + if ( !parent ) + return nullptr; - NifItem * parent = item->parent(); + const NifItem * item = parent->child( childIndex ); + if ( item ) { + if ( evalCondition(item) ) + return item; + else { + if ( reportErrors ) { + QString repr; + if ( parent->isArray() ) + repr = QString::number( childIndex ); + else + repr = QString( "%1 (\"%2\")" ).arg( childIndex ).arg( item->name() ); + reportError( parent, QString( "Subitem %1 did not pass condition check." ).arg( repr ) ); + } + } + } else { + if ( reportErrors ) + reportError( parent, QString( "Invalid child index %1." ).arg( childIndex ) ); + } - for ( int c = item->row() - 1; c >= 0; c-- ) { - NifItem * child = parent->child( c ); + return nullptr; +} - if ( child && child->hasName(name) && evalCondition( child ) ) - return child; +const NifItem * BaseModel::getItem( const QModelIndex & index ) const +{ + if ( !index.isValid() ) + return nullptr; + if ( index.model() != this ) { + auto othermodel = static_cast( index.model() ); + const NifItem * tempItem = indexToItem( index ); + QString tempItemRepr = ( othermodel && tempItem ) ? othermodel->itemRepr(tempItem) : QString("???"); + reportError( tr( "BaseMode::getItem got an index from another model (%1)" ).arg( tempItemRepr ) ); + return nullptr; } - return getItemX( parent, name ); + const NifItem * item = indexToItem( index ); + if ( !item ) + reportError( "BaseMode::getItem got a valid index with null internalPointer" ); + return item; } -NifItem * BaseModel::findItemX( NifItem * item, const QString & name ) const + +/* +* Uses implicit load order +*/ +const NifItem * BaseModel::getItemX( const NifItem * item, const QLatin1String & name ) const { while ( item ) { - NifItem * r = getItem( item, name ); - - if ( r ) - return r; + const NifItem * parent = item->parent(); + if ( !parent ) + return nullptr; + + for ( int c = item->row() - 1; c >= 0; c-- ) { + const NifItem * child = parent->child( c ); + if ( child && child->hasName(name) && evalCondition(child) ) + return child; + } - item = item->parent(); + item = parent; } return nullptr; } -QModelIndex BaseModel::getIndex( const QModelIndex & parent, const QString & name ) const +const NifItem * BaseModel::findItemX( const NifItem * parent, const QLatin1String & name ) const { - NifItem * parentItem = static_cast( parent.internalPointer() ); - - if ( !( parent.isValid() && parentItem && parent.model() == this ) ) - return QModelIndex(); - - NifItem * item = getItem( parentItem, name ); - - if ( item ) - return createIndex( item->row(), 0, item ); + while ( parent ) { + const NifItem * c = getItem( parent, name, false ); + if ( c ) + return c; + parent = parent->parent(); + } - return QModelIndex(); + return nullptr; } /* * conditions and version */ -int BaseModel::evaluateInt( NifItem * item, const NifExpr & expr ) const +bool BaseModel::evalVersion( const NifItem * item ) const { - if ( !item || item == root ) - return -1; + if ( !item ) + return false; - BaseModelEval functor( this, item ); - return expr.evaluateUInt( functor ); + if ( !item->isVersionConditionCached() ) { + if ( item->parent() && !evalVersion( item->parent() ) ) + item->setVersionCondition( false ); + else if ( item->isConditionless() ) + item->setVersionCondition( true ); + else + item->setVersionCondition( evalVersionImpl( item ) ); + } + + return item->versionCondition(); } -bool BaseModel::evalCondition( NifItem * item, bool chkParents ) const +bool BaseModel::evalCondition( const NifItem * item ) const { - if ( !evalVersion( item, chkParents ) ) { - // Version is global and cond is not so set false and abort - item->setCondition( false ); + if ( !item ) return false; - } - if ( item->isConditionValid() ) - return item->condition(); + if ( !evalVersion(item) ) + return false; - item->setCondition( item == root ); - if ( item->condition() ) - return true; + if ( !item->isConditionCached() ) { + if ( item->parent() && !evalCondition( item->parent() ) ) + item->setCondition( false ); + else if ( item->isConditionless() ) + item->setCondition( true ); + else + item->setCondition( evalConditionImpl( item ) ); + } + + return item->condition(); +} - if ( chkParents && item->parent() ) { - // Set false if parent is false and reject early - item->setCondition( evalCondition( item->parent(), true ) ); - if ( !item->condition() ) +bool BaseModel::evalConditionImpl( const NifItem * item ) const +{ + // If there is a cond, evaluate it + if ( !item->cond().isEmpty() ) { + BaseModelEval functor( this, item ); + if ( !item->condexpr().evaluateBool(functor) ) return false; } - // Early reject for cond - item->setCondition( item->cond().isEmpty() ); - if ( item->condition() ) - return true; + return true; +} - // If there is a cond, evaluate it - BaseModelEval functor( this, item ); - item->setCondition( item->condexpr().evaluateBool( functor ) ); +QString BaseModel::itemRepr( const NifItem * item ) const +{ + if ( !item ) + return QString("[NULL]"); + if ( item == root ) + return QString("[ROOT]"); + + QString result; + while( item ) { + const NifItem * parent = item->parent(); + if ( !parent ) { + result = "???" + result; // WTF... + break; + } else if ( parent == root ) { + result = topItemRepr( item ) + result; + break; + } else { + QString subres; + if ( isArray( parent ) ) + subres = QString(" [%1]").arg( item->row() ); + else + subres = "\\" + item->name(); + result = subres + result; + item = parent; + } + } - return item->condition(); + return result; +} + +QString BaseModel::topItemRepr( const NifItem * item ) const +{ + return QString("%2 [%1]").arg( item->row() ).arg( item->name() ); } -bool BaseModel::evalVersion( const QModelIndex & index, bool chkParents ) const +void BaseModel::reportError( const QString & err ) const { - NifItem * item = static_cast(index.internalPointer()); + Message::append( getWindow(), "Parsing warnings.", err ); +} - if ( index.isValid() && index.model() == this && item ) - return evalVersion( item, chkParents ); +void BaseModel::reportError( const NifItem * item, const QString & err ) const +{ + reportError( itemRepr( item ) + ": " + err ); +} - return false; +void BaseModel::reportError( const NifItem * item, const QString & funcName, const QString & err ) const +{ + reportError( QString("%1, %2: %3").arg( itemRepr( item ), funcName, err ) ); } -bool BaseModel::evalCondition( const QModelIndex & index, bool chkParents ) const +void BaseModel::onItemValueChange( NifItem * item ) { - NifItem * item = static_cast(index.internalPointer()); + if ( state != Processing ) { + QModelIndex idx = itemToIndex( item, ValueCol ); + emit dataChanged( idx, idx ); + } else { + changedWhileProcessing = true; + } +} + +void BaseModel::onArrayValuesChange( NifItem * arrayRootItem ) +{ + int x = arrayRootItem->childCount() - 1; + if ( x >= 0 ) { + emit dataChanged( + createIndex( 0, ValueCol, arrayRootItem->children().at(0) ), + createIndex( x, ValueCol, arrayRootItem->children().at(x) ) + ); + } +} - if ( index.isValid() && index.model() == this && item ) - return evalCondition( item, chkParents ); +const NifItem * BaseModel::getTopItem( const NifItem * item ) const +{ + while( item ) { + auto p = item->parent(); + if ( p == root ) + break; + item = p; + } - return false; + return item; } @@ -842,25 +873,25 @@ BaseModelEval::BaseModelEval( const BaseModel * model, const NifItem * item ) this->item = item; } -QVariant BaseModelEval::operator()(const QVariant & v) const +QVariant BaseModelEval::operator()( const QVariant & v ) const { if ( v.type() == QVariant::String ) { - QString left = v.toString(); - const NifItem * i = item; // Resolve "ARG" - bool argexpr = false; + QString left = v.toString(); + const NifItem * exprItem = item; + bool isArgExpr = false; while ( left == XMLARG ) { - if ( !i->parent() ) + exprItem = exprItem->parent(); + if ( !exprItem ) return false; - - i = i->parent(); - left = i->arg(); - argexpr = !i->argexpr().noop(); + left = exprItem->arg(); + isArgExpr = !exprItem->argexpr().noop(); } + // ARG is an expression - if ( argexpr ) - return i->argexpr().evaluateUInt64( BaseModelEval( model, i ) ); + if ( isArgExpr ) + return exprItem->argexpr().evaluateUInt64( BaseModelEval( model, exprItem) ); bool numeric; int val = left.toInt( &numeric, 10 ); @@ -868,28 +899,26 @@ QVariant BaseModelEval::operator()(const QVariant & v) const return QVariant( val ); // resolve reference to sibling - const NifItem * sibling = model->getItem( i->parent(), left ); - + const NifItem * sibling = model->getItem( exprItem->parent(), left ); if ( sibling ) { - if ( sibling->value().isCount() || sibling->value().isFloat() ) { - return QVariant( sibling->value().toCount() ); - } else if ( sibling->value().isFileVersion() ) { - return QVariant( sibling->value().toFileVersion() ); + if ( sibling->valueIsCount() || sibling->valueIsFloat() ) { + return QVariant( sibling->valueToCount() ); + } else if ( sibling->valueIsFileVersion() ) { + return QVariant( sibling->valueToFileVersion() ); // this is tricky to understand // we check whether the reference is an array - // if so, we get the current item's row number (i->row()) + // if so, we get the current item's row number (exprItem->row()) // and get the sibling's child at that row number // this is used for instance to describe array sizes of strips } else if ( sibling->childCount() > 0 ) { - const NifItem * i2 = sibling->child( i->row() ); + const NifItem * i2 = sibling->child( exprItem->row() ); - if ( i2 && i2->value().isCount() ) - return QVariant( i2->value().toCount() ); + if ( i2 && i2->valueIsCount() ) + return QVariant( i2->valueToCount() ); + } else if ( sibling->valueType() == NifValue::tBSVertexDesc ) { + return QVariant( sibling->get().GetFlags() << 4 ); } else { - if ( sibling->value().type() == NifValue::tBSVertexDesc ) - return QVariant( sibling->value().get().GetFlags() << 4 ); - - qDebug() << ("can't convert " + left + " to a count"); + model->reportError( item, QString("BaseModelEval could not convert %1 to a count" ).arg( model->itemRepr(sibling) ) ); } } @@ -897,13 +926,9 @@ QVariant BaseModelEval::operator()(const QVariant & v) const // is the condition string a type? if ( model->isAncestorOrNiBlock( left ) ) { // get the type of the current block - const NifItem * block = i; - - while ( block->parent() && block->parent()->parent() ) { - block = block->parent(); - } - - return QVariant( model->inherits( block->name(), left ) ); + auto itemBlock = model->getTopItem( exprItem ); + if ( itemBlock ) + return QVariant( model->inherits( itemBlock->name(), left ) ); } return QVariant( 0 ); @@ -922,3 +947,13 @@ unsigned DJB1Hash( const char * key, unsigned tableSize ) } return hash % tableSize; } + +QString addConditionParentPrefix( const QString & x ) +{ + for ( int c = 0; c < x.length(); c++ ) { + if ( !x[c].isNumber() ) + return QString( "..\\" ) + x; + } + + return x; +} diff --git a/src/model/basemodel.h b/src/model/basemodel.h index 1ccc6c1fd..f33f5a303 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -51,6 +51,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Used for Block Name hashing unsigned DJB1Hash( const char * key, unsigned tableSize = UINT_MAX ); +// Used to fill arrays +QString addConditionParentPrefix( const QString & x ); //! @file basemodel.h BaseModel, BaseModelEval @@ -79,7 +81,7 @@ class BaseModel : public QAbstractItemModel ~BaseModel(); //! Get parent window - QWidget * getWindow(); + QWidget * getWindow() const { return parentWindow; } //! Clear model data. virtual void clear() = 0; @@ -92,26 +94,6 @@ class BaseModel : public QAbstractItemModel //! Get version as a number virtual quint32 getVersionNumber() const = 0; - //! Get an item. - template T get( const QModelIndex & index ) const; - //! Get an item by name. - template T get( const QModelIndex & parent, const QString & name ) const; - //! Set an item. - template bool set( const QModelIndex & index, const T & d ); - //! Set an item by name. - template bool set( const QModelIndex & parent, const QString & name, const T & v ); - - //! Get a model index array as a QVector. - template QVector getArray( const QModelIndex & iArray ) const; - //! Get a model index array as a QVector by name. - template QVector getArray( const QModelIndex & iArray, const QString & name ) const; - //! Write a QVector to a model index array. - template void setArray( const QModelIndex & iArray, const QVector & array ); - //! Write a value to a model index array. - template void setArray( const QModelIndex & iArray, const T & val ); - //! Write a QVector to a model index array by name. - template void setArray( const QModelIndex & iArray, const QString & name, const QVector & array ); - //! Load from file. bool loadFromFile( const QString & filename ); //! Save to file. @@ -155,25 +137,13 @@ class BaseModel : public QAbstractItemModel */ bool isArray( const NifItem * item ) const; - /*! Update the size of an array (append or remove items). - * - * @param array The index of the array whose size to update. - * @return true if the update succeeded, false otherwise. - */ - bool updateArray( const QModelIndex & iArray ); - - //! Update the size of an array by name. - bool updateArray( const QModelIndex & parent, const QString & name ); - //! Get an item as a NifValue. NifValue getValue( const QModelIndex & index ) const; // Get an item as a NifValue by name. //NifValue getValue( const QModelIndex & parent, const QString & name ) const; //! Set an item from a NifValue. - bool setValue( const QModelIndex & index, const NifValue & v ); - //! Set an item from a NifValue by name. - bool setValue( const QModelIndex & parent, const QString & name, const NifValue & v ); + bool setIndexValue( const QModelIndex & index, const NifValue & v ); //! Get the item name. QString itemName( const QModelIndex & index ) const; @@ -196,13 +166,7 @@ class BaseModel : public QAbstractItemModel //! Get the item template string. QString itemTmplt( const QModelIndex & index ) const; - //! Find a branch by name. - QModelIndex getIndex( const QModelIndex & parent, const QString & name ) const; - //! Evaluate condition and version. - bool evalCondition( const QModelIndex & idx, bool chkParents = false ) const; - //! Evaluate version. - bool evalVersion( const QModelIndex & idx, bool chkParents = false ) const; //! Is name a NiBlock identifier ( or )? virtual bool isAncestorOrNiBlock( const QString & /*name*/ ) const { return false; } //! Returns true if name inherits ancestor. @@ -291,23 +255,273 @@ class BaseModel : public QAbstractItemModel // end QAbstractItemModel - // Terrible hack for atomic-like operations - // Setting this to false will suspend dataChanged in set - // Doing so is necessary for performance reasons with setting thousands of rows at once - // e.g. Update Tangent Space with BSTriShapes - void setEmitChanges( bool e ); - enum MsgMode { MSG_USER, MSG_TEST }; void setMessageMode( MsgMode mode ); - MsgMode getMessageMode() const; + MsgMode getMessageMode() const { return msgMode; } + // TODO(Gavrant): replace with reportError? void logMessage( const QString & message, const QString & details, QMessageBox::Icon lvl = QMessageBox::Warning ) const; + // TODO(Gavrant): replace with reportError? void logWarning( const QString & details ) const; +public: + //! Return string representation ("path") of an item within the model (e.g., "Block [0]\Vertex Data [3]\Vertex colors") as a QString. + // Mostly for messages and debug. + QString itemRepr( const NifItem * item ) const; +protected: + virtual QString topItemRepr( const NifItem * item ) const; +public: + void reportError( const QString & err ) const; + void reportError( const NifItem * item, const QString & err ) const; + void reportError( const NifItem * item, const QString & funcName, const QString & err ) const; + +public: + QModelIndex itemToIndex( const NifItem * item, int column = 0 ) const; + + //! Get the top-level parent (a child of the model's root item) of an item. + // Return null if the item is the root itself or not a child of the root. + const NifItem * getTopItem( const NifItem * item ) const; + //! Get the top-level parent (a child of the model's root item) of an item. + // Return null if the item is the root itself or not a child of the root. + NifItem * getTopItem( const NifItem * item ); + //! Get the top-level parent (a child of the model's root item) of a model index. + // Return null if the item is the root itself or not a child of the root. + const NifItem * getTopItem( const QModelIndex & index ) const; + //! Get the top-level parent (a child of the model's root item) of a model index. + // Return null if the item is the root itself or not a child of the root. + NifItem * getTopItem( const QModelIndex & index ); + + //! Get the model index of the top-level parent (a child of the model's root item) of an item. + // Return invalid index if the item is the root itself or not a child of the root. + QModelIndex getTopIndex( const NifItem * item ) const; + //! Get the model index of the top-level parent (a child of the model's root item) of a model index. + // Return invalid index if the index is the root itself or not a child of the root. + QModelIndex getTopIndex( const QModelIndex & index ) const; + + // Evaluating NifItem condition and model version +public: + //! Evaluate NifItem model version. + bool evalVersion( const NifItem * item ) const; +protected: + virtual bool evalVersionImpl( const NifItem * item ) const = 0; + +public: + //! Evaluate NifItem model version and condition. + bool evalCondition( const NifItem * item ) const; +protected: + virtual bool evalConditionImpl( const NifItem * item ) const; + + // NifItem getters +public: + //! Get a child NifItem from its parent and name. + const NifItem * getItem( const NifItem * parent, const QString & name, bool reportErrors = false ) const; + //! Get a child NifItem from its parent and name. + NifItem * getItem( const NifItem * parent, const QString & name, bool reportErrors = false ); + //! Get a child NifItem from its parent and name. + const NifItem * getItem( const NifItem * parent, const QLatin1String & name, bool reportErrors = false ) const; + //! Get a child NifItem from its parent and name. + NifItem * getItem( const NifItem * parent, const QLatin1String & name, bool reportErrors = false ); + //! Get a child NifItem from its parent and name. + const NifItem * getItem( const NifItem * parent, const char * name, bool reportErrors = false ) const; + //! Get a child NifItem from its parent and name. + NifItem * getItem( const NifItem * parent, const char * name, bool reportErrors = false ); + //! Get a child NifItem from its parent and numerical index. + const NifItem * getItem( const NifItem * parent, int childIndex, bool reportErrors = true ) const; + //! Get a child NifItem from its parent and numerical index. + NifItem * getItem( const NifItem * parent, int childIndex, bool reportErrors = true ); + //! Get a NifItem from its model index. + const NifItem * getItem( const QModelIndex & index ) const; + //! Get a NifItem from its model index. + NifItem * getItem( const QModelIndex & index ); + //! Get a child NifItem from its parent and name. + const NifItem * getItem( const QModelIndex & parent, const QString & name, bool reportErrors = false ) const; + //! Get a child NifItem from its parent and name. + NifItem * getItem( const QModelIndex & parent, const QString & name, bool reportErrors = false ); + //! Get a child NifItem from its parent and name. + const NifItem * getItem( const QModelIndex & parent, const QLatin1String & name, bool reportErrors = false ) const; + //! Get a child NifItem from its parent and name. + NifItem * getItem( const QModelIndex & parent, const QLatin1String & name, bool reportErrors = false ); + //! Get a child NifItem from its parent and name. + const NifItem * getItem( const QModelIndex & parent, const char * name, bool reportErrors = false ) const; + //! Get a child NifItem from its parent and name. + NifItem * getItem( const QModelIndex & parent, const char * name, bool reportErrors = false ); + //! Get a child NifItem from its parent and numerical index. + const NifItem * getItem( const QModelIndex & parent, int childIndex, bool reportErrors = true ) const; + //! Get a child NifItem from its parent and numerical index. + NifItem * getItem( const QModelIndex & parent, int childIndex, bool reportErrors = true ); + +protected: + //! Find a NifItem by name, going up from 'item' in the load order. + const NifItem * getItemX( const NifItem * item, const QLatin1String & name ) const; + //! Find a NifItem by name, going up from 'item' in the load order. + const NifItem * getItemX( const NifItem * item, const char * name ) const; + + //! Find a NifItem by name, first in parent, then in parent's parent, etc. + const NifItem * findItemX( const NifItem * parent, const QLatin1String & name ) const; + //! Find a NifItem by name, first in parent, then in parent's parent, etc. + const NifItem * findItemX( const NifItem * parent, const char * name ) const; + + // Item model index getters +public: + //! Get the model index of a child item. + QModelIndex getIndex( const NifItem * itemParent, const QString & itemName, int column = 0 ) const; + //! Get the model index of a child item. + QModelIndex getIndex( const NifItem * itemParent, const QLatin1String & itemName, int column = 0 ) const; + //! Get the model index of a child item. + QModelIndex getIndex( const NifItem * itemParent, const char * itemName, int column = 0 ) const; + //! Get the model index of a child item. + QModelIndex getIndex( const QModelIndex & itemParent, const QString & itemName, int column = 0 ) const; + //! Get the model index of a child item. + QModelIndex getIndex( const QModelIndex & itemParent, const QLatin1String & itemName, int column = 0 ) const; + //! Get the model index of a child item. + QModelIndex getIndex( const QModelIndex & itemParent, const char * itemName, int column = 0 ) const; + + // Item value getters +public: + //! Get the value of an item. + template T get( const NifItem * item ) const; + //! Get the value of a child item. + template T get( const NifItem * itemParent, int itemIndex ) const; + //! Get the value of a child item. + template T get( const NifItem * itemParent, const QString & itemName ) const; + //! Get the value of a child item. + template T get( const NifItem * itemParent, const QLatin1String & itemName ) const; + //! Get the value of a child item. + template T get( const NifItem * itemParent, const char * itemName ) const; + //! Get the value of a model index. + template T get( const QModelIndex & index ) const; + //! Get the value of a child item. + template T get( const QModelIndex & itemParent, int itemIndex ) const; + //! Get the value of a child item. + template T get( const QModelIndex & itemParent, const QString & itemName ) const; + //! Get the value of a child item. + template T get( const QModelIndex & itemParent, const QLatin1String & itemName ) const; + //! Get the value of a child item. + template T get( const QModelIndex & itemParent, const char * itemName ) const; + + // Item value setters +public: + //! Set the value of an item. + template bool set( NifItem * item, const T & val ); + //! Set the value of a child item. + template bool set( const NifItem * itemParent, int itemIndex, const T & val ); + //! Set the value of a child item. + template bool set( const NifItem * itemParent, const QString & itemName, const T & val ); + //! Set the value of a child item. + template bool set( const NifItem * itemParent, const QLatin1String & itemName, const T & val ); + //! Set the value of a child item. + template bool set( const NifItem * itemParent, const char * itemName, const T & val ); + //! Set the value of a model index. + template bool set( const QModelIndex & index, const T & val ); + //! Set the value of a child item. + template bool set( const QModelIndex & itemParent, int itemIndex, const T & val ); + //! Set the value of a child item. + template bool set( const QModelIndex & itemParent, const QString & itemName, const T & val ); + //! Set the value of a child item. + template bool set( const QModelIndex & itemParent, const QLatin1String & itemName, const T & val ); + //! Set the value of a child item. + template bool set( const QModelIndex & itemParent, const char * itemName, const T & val ); + + // Array size management +protected: + //! Get the size of an array + int evalArraySize( const NifItem * array ) const; + virtual bool updateArraySizeImpl( NifItem * array ) = 0; +public: + //! Update the size of an array from its conditions (append missing or remove excess items). + bool updateArraySize( NifItem * arrayRootItem ); + //! Update the size of a child array from its conditions (append missing or remove excess items). + bool updateArraySize( const NifItem * arrayParent, int arrayIndex ); + //! Update the size of a child array from its conditions (append missing or remove excess items). + bool updateArraySize( const NifItem * arrayParent, const QString & arrayName ); + //! Update the size of a child array from its conditions (append missing or remove excess items). + bool updateArraySize( const NifItem * arrayParent, const QLatin1String & arrayName ); + //! Update the size of a child array from its conditions (append missing or remove excess items). + bool updateArraySize( const NifItem * arrayParent, const char * arrayName ); + //! Update the size of a model index array from its conditions (append missing or remove excess items). + bool updateArraySize( const QModelIndex & iArray ); + //! Update the size of a child array from its conditions (append missing or remove excess items). + bool updateArraySize( const QModelIndex & arrayParent, int arrayIndex ); + //! Update the size of a child array from its conditions (append missing or remove excess items). + bool updateArraySize( const QModelIndex & arrayParent, const QString & arrayName ); + //! Update the size of a child array from its conditions (append missing or remove excess items). + bool updateArraySize( const QModelIndex & arrayParent, const QLatin1String & arrayName ); + //! Update the size of a child array from its conditions (append missing or remove excess items). + bool updateArraySize( const QModelIndex & arrayParent, const char * arrayName ); + + // Array getters +public: + //! Get an array as a QVector. + template QVector getArray( const NifItem * arrayRootItem ) const; + //! Get a child array as a QVector. + template QVector getArray( const NifItem * arrayParent, int arrayIndex ) const; + //! Get a child array as a QVector. + template QVector getArray( const NifItem * arrayParent, const QString & arrayName ) const; + //! Get a child array as a QVector. + template QVector getArray( const NifItem * arrayParent, const QLatin1String & arrayName ) const; + //! Get a child array as a QVector. + template QVector getArray( const NifItem * arrayParent, const char * arrayName ) const; + //! Get a model index array as a QVector. + template QVector getArray( const QModelIndex & iArray ) const; + //! Get a child array as a QVector. + template QVector getArray( const QModelIndex & arrayParent, int arrayIndex ) const; + //! Get a child array as a QVector. + template QVector getArray( const QModelIndex & arrayParent, const QString & arrayName ) const; + //! Get a child array as a QVector. + template QVector getArray( const QModelIndex & arrayParent, const QLatin1String & arrayName ) const; + //! Get a child array as a QVector. + template QVector getArray( const QModelIndex & arrayParent, const char * arrayName ) const; + + // Array setters +public: + //! Write a QVector to an array. + template void setArray( NifItem * arrayRootItem, const QVector & array ); + //! Write a QVector to a child array. + template void setArray( const NifItem * arrayParent, int arrayIndex, const QVector & array ); + //! Write a QVector to a child array. + template void setArray( const NifItem * arrayParent, const QString & arrayName, const QVector & array ); + //! Write a QVector to a child array. + template void setArray( const NifItem * arrayParent, const QLatin1String & arrayName, const QVector & array ); + //! Write a QVector to a child array. + template void setArray( const NifItem * arrayParent, const char * arrayName, const QVector & array ); + //! Write a QVector to a model index array. + template void setArray( const QModelIndex & iArray, const QVector & array ); + //! Write a QVector to a child array. + template void setArray( const QModelIndex & arrayParent, int arrayIndex, const QVector & array ); + //! Write a QVector to a child array. + template void setArray( const QModelIndex & arrayParent, const QString & arrayName, const QVector & array ); + //! Write a QVector to a child array. + template void setArray( const QModelIndex & arrayParent, const QLatin1String & arrayName, const QVector & array ); + //! Write a QVector to a child array. + template void setArray( const QModelIndex & arrayParent, const char * arrayName, const QVector & array ); + + // Array fillers +public: + //! Fill an array with a value. + template void fillArray( NifItem * arrayRootItem, const T & val ); + //! Fill a child array with a value. + template void fillArray( const NifItem * arrayParent, int arrayIndex, const T & val ); + //! Fill a child array with a value. + template void fillArray( const NifItem * arrayParent, const QString & arrayName, const T & val ); + //! Fill a child array with a value. + template void fillArray( const NifItem * arrayParent, const QLatin1String & arrayName, const T & val ); + //! Fill a child array with a value. + template void fillArray( const NifItem * arrayParent, const char * arrayName, const T & val ); + //! Fill a model index array with a value. + template void fillArray( const QModelIndex & iArray, const T & val ); + //! Fill a child array with a value. + template void fillArray( const QModelIndex & arrayParent, int arrayIndex, const T & val ); + //! Fill a child array with a value. + template void fillArray( const QModelIndex & arrayParent, const QString & arrayName, const T & val ); + //! Fill a child array with a value. + template void fillArray( const QModelIndex & arrayParent, const QLatin1String & arrayName, const T & val ); + //! Fill a child array with a value. + template void fillArray( const QModelIndex & arrayParent, const char * arrayName, const T & val ); + signals: //! Messaging signal void sigMessage( const TestMessage & msg ) const; @@ -315,57 +529,26 @@ class BaseModel : public QAbstractItemModel void sigProgress( int c, int m ) const; protected: - //! Get an item - virtual NifItem * getItem( NifItem * parent, const QString & name ) const; //! Set an item value - virtual bool setItemValue( NifItem * item, const NifValue & v ) = 0; - - //! Update an array item - virtual bool updateArrayItem( NifItem * array ) = 0; + bool setItemValue( NifItem * item, const NifValue & v ); //! Convert a version number to a string virtual QString ver2str( quint32 ) const = 0; //! Convert a version string to a number virtual quint32 str2ver( QString ) const = 0; - //! Evaluate version - virtual bool evalVersion( NifItem * item, bool chkParents = false ) const = 0; - //! Set the header string virtual bool setHeaderString( const QString &, uint ver = 0 ) = 0; - //! Get an item by name - template T get( NifItem * parent, const QString & name ) const; - //! Get an item - template T get( NifItem * item ) const; - - //! Set an item by name - template bool set( NifItem * parent, const QString & name, const T & d ); - //! Set an item - template bool set( NifItem * item, const T & d ); - - //! Get the size of an array - int getArraySize( NifItem * array ) const; - //! Evaluate a string for an array - int evaluateInt( NifItem * item, const NifExpr & expr ) const; - - //! Get an item by name - NifItem * getItemX( NifItem * item, const QString & name ) const; - //! Find an item by name - NifItem * findItemX( NifItem * item, const QString & name ) const; - - //! Set an item value by name - bool setItemValue( NifItem * parent, const QString & name, const NifValue & v ); - - //! Evaluate conditions - bool evalCondition( NifItem * item, bool chkParents = false ) const; - void beginInsertRows( const QModelIndex & parent, int first, int last ); void endInsertRows(); void beginRemoveRows( const QModelIndex & parent, int first, int last ); void endRemoveRows(); + virtual void onItemValueChange( NifItem * item ); + void onArrayValuesChange( NifItem * arrayRootItem ); + //! NifSkope window the model belongs to QWidget * parentWindow; @@ -379,9 +562,6 @@ class BaseModel : public QAbstractItemModel //! The file info for the model QFileInfo fileinfo; - // Whether or not to emit dataChanged() in set - bool emitChanges = true; - //! A list of test messages mutable QList messages; //! Handle a test message @@ -414,141 +594,414 @@ class BaseModelEval }; -// Templates +// Inlines -template inline T BaseModel::get( NifItem * parent, const QString & name ) const +inline QModelIndex BaseModel::itemToIndex( const NifItem * item, int column ) const { - NifItem * item = getItem( parent, name ); + return item ? createIndex( item->row(), column, const_cast(item) ) : QModelIndex(); +} - if ( item ) - return item->value().get(); +inline NifItem * BaseModel::getTopItem( const NifItem * item ) +{ + return const_cast( const_cast(this)->getTopItem( item ) ); +} +inline const NifItem * BaseModel::getTopItem( const QModelIndex & index ) const +{ + return getTopItem( getItem(index) ); +} +inline NifItem * BaseModel::getTopItem( const QModelIndex & index ) +{ + return getTopItem( getItem(index) ); +} - return T(); +inline QModelIndex BaseModel::getTopIndex( const NifItem * item ) const +{ + return itemToIndex( getTopItem(item) ); +} +inline QModelIndex BaseModel::getTopIndex( const QModelIndex & index ) const +{ + return itemToIndex( getTopItem(index) ); } -template inline T BaseModel::get( const QModelIndex & parent, const QString & name ) const +inline bool BaseModel::setIndexValue( const QModelIndex & index, const NifValue & val ) { - NifItem * parentItem = static_cast( parent.internalPointer() ); + return setItemValue( getItem(index), val ); +} - if ( !( parent.isValid() && parentItem && parent.model() == this ) ) - return T(); - NifItem * item = getItem( parentItem, name ); +// Item getters - if ( item ) - return item->value().get(); +#define _BASEMODEL_NONCONST_GETITEM_1(arg) const_cast( const_cast(this)->getItem( arg ) ) +#define _BASEMODEL_NONCONST_GETITEM_3(arg1, arg2, arg3) const_cast( const_cast(this)->getItem( arg1, arg2, arg3 ) ) - return T(); +inline NifItem * BaseModel::getItem( const NifItem * parent, const QString & name, bool reportErrors ) +{ + return _BASEMODEL_NONCONST_GETITEM_3( parent, name, reportErrors ); } - -template inline bool BaseModel::set( NifItem * parent, const QString & name, const T & d ) +inline NifItem * BaseModel::getItem( const NifItem * parent, const QLatin1String & name, bool reportErrors ) { - NifItem * item = getItem( parent, name ); - - if ( item ) - return set( item, d ); - - return false; + return _BASEMODEL_NONCONST_GETITEM_3( parent, name, reportErrors ); +} +inline const NifItem * BaseModel::getItem( const NifItem * parent, const char * name, bool reportErrors ) const +{ + return getItem( parent, QLatin1String(name), reportErrors ); +} +inline NifItem * BaseModel::getItem( const NifItem * parent, const char * name, bool reportErrors ) +{ + return _BASEMODEL_NONCONST_GETITEM_3( parent, QLatin1String(name), reportErrors ); +} +inline NifItem * BaseModel::getItem( const NifItem * parent, int childIndex, bool reportErrors ) +{ + return _BASEMODEL_NONCONST_GETITEM_3( parent, childIndex, reportErrors ); +} +inline NifItem * BaseModel::getItem( const QModelIndex & index ) +{ + return _BASEMODEL_NONCONST_GETITEM_1( index ); +} +inline const NifItem * BaseModel::getItem( const QModelIndex & parent, const QString & name, bool reportErrors ) const +{ + return getItem( getItem(parent), name, reportErrors ); +} +inline NifItem * BaseModel::getItem( const QModelIndex & parent, const QString & name, bool reportErrors ) +{ + return _BASEMODEL_NONCONST_GETITEM_3( getItem(parent), name, reportErrors ); +} +inline const NifItem * BaseModel::getItem( const QModelIndex & parent, const QLatin1String & name, bool reportErrors ) const +{ + return getItem( getItem(parent), name, reportErrors ); +} +inline NifItem * BaseModel::getItem( const QModelIndex & parent, const QLatin1String & name, bool reportErrors ) +{ + return _BASEMODEL_NONCONST_GETITEM_3( getItem(parent), name, reportErrors ); +} +inline const NifItem * BaseModel::getItem( const QModelIndex & parent, const char * name, bool reportErrors ) const +{ + return getItem( getItem(parent), QLatin1String(name), reportErrors ); +} +inline NifItem * BaseModel::getItem( const QModelIndex & parent, const char * name, bool reportErrors ) +{ + return _BASEMODEL_NONCONST_GETITEM_3( getItem(parent), QLatin1String(name), reportErrors ); +} +inline const NifItem * BaseModel::getItem( const QModelIndex & parent, int childIndex, bool reportErrors ) const +{ + return getItem( getItem(parent), childIndex, reportErrors ); +} +inline NifItem * BaseModel::getItem( const QModelIndex & parent, int childIndex, bool reportErrors ) +{ + return _BASEMODEL_NONCONST_GETITEM_3( getItem(parent), childIndex, reportErrors ); } -template inline bool BaseModel::set( const QModelIndex & parent, const QString & name, const T & d ) +inline const NifItem * BaseModel::getItemX( const NifItem * item, const char * name ) const { - NifItem * parentItem = static_cast( parent.internalPointer() ); + return getItemX( item, QLatin1String(name) ); +} - if ( !( parent.isValid() && parentItem && parent.model() == this ) ) - return false; +inline const NifItem * BaseModel::findItemX( const NifItem * parent, const char * name ) const +{ + return findItemX( parent, QLatin1String(name) ); +} - NifItem * item = getItem( parentItem, name ); - if ( item ) - return set( item, d ); +// Item model index getters - return false; +inline QModelIndex BaseModel::getIndex( const NifItem * itemParent, const QString & itemName, int column ) const +{ + return itemToIndex( getItem(itemParent, itemName), column ); } - -template inline T BaseModel::get( NifItem * item ) const +inline QModelIndex BaseModel::getIndex( const NifItem * itemParent, const QLatin1String & itemName, int column ) const +{ + return itemToIndex( getItem(itemParent, itemName), column ); +} +inline QModelIndex BaseModel::getIndex( const NifItem * itemParent, const char * itemName, int column ) const { - return item->value().get(); + return itemToIndex( getItem(itemParent, QLatin1String(itemName)), column ); } +inline QModelIndex BaseModel::getIndex( const QModelIndex & itemParent, const QString & itemName, int column ) const +{ + return itemToIndex( getItem(itemParent, itemName), column ); +} +inline QModelIndex BaseModel::getIndex( const QModelIndex & itemParent, const QLatin1String & itemName, int column ) const +{ + return itemToIndex( getItem(itemParent, itemName), column ); +} +inline QModelIndex BaseModel::getIndex( const QModelIndex & itemParent, const char * itemName, int column ) const +{ + return itemToIndex( getItem(itemParent, QLatin1String(itemName)), column ); +} + +// Item value getters + +template inline T BaseModel::get( const NifItem * item ) const +{ + return NifItem::get( item ); +} +template inline T BaseModel::get( const NifItem * itemParent, int itemIndex ) const +{ + return NifItem::get( getItem(itemParent, itemIndex) ); +} +template inline T BaseModel::get( const NifItem * itemParent, const QString & itemName ) const +{ + return NifItem::get( getItem(itemParent, itemName) ); +} +template inline T BaseModel::get( const NifItem * itemParent, const QLatin1String & itemName ) const +{ + return NifItem::get( getItem(itemParent, itemName) ); +} +template inline T BaseModel::get( const NifItem * itemParent, const char * itemName ) const +{ + return NifItem::get( getItem(itemParent, QLatin1String(itemName)) ); +} template inline T BaseModel::get( const QModelIndex & index ) const { - NifItem * item = static_cast( index.internalPointer() ); + return NifItem::get( getItem(index) ); +} +template inline T BaseModel::get( const QModelIndex & itemParent, int itemIndex ) const +{ + return NifItem::get( getItem(itemParent, itemIndex) ); +} +template inline T BaseModel::get( const QModelIndex & itemParent, const QString & itemName ) const +{ + return NifItem::get( getItem(itemParent, itemName) ); +} +template inline T BaseModel::get( const QModelIndex & itemParent, const QLatin1String & itemName ) const +{ + return NifItem::get( getItem(itemParent, itemName) ); +} +template inline T BaseModel::get( const QModelIndex & itemParent, const char * itemName ) const +{ + return NifItem::get( getItem(itemParent, QLatin1String(itemName)) ); +} - if ( !( index.isValid() && item && index.model() == this ) ) - return T(); - return item->value().get(); -} +// Item value setters -template inline bool BaseModel::set( NifItem * item, const T & d ) +template inline bool BaseModel::set( NifItem * item, const T & val ) { - if ( item->value().set( d ) ) { - if ( state != Processing ) - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - else - changedWhileProcessing = true; - + if ( NifItem::set( item, val ) ) { + onItemValueChange( item ); return true; } return false; } - -template inline bool BaseModel::set( const QModelIndex & index, const T & d ) +template inline bool BaseModel::set( const NifItem * itemParent, int itemIndex, const T & val ) +{ + return set( getItem(itemParent, itemIndex, true), val ); +} +template inline bool BaseModel::set( const NifItem * itemParent, const QString & itemName, const T & val ) +{ + return set( getItem(itemParent, itemName, true), val ); +} +template inline bool BaseModel::set( const NifItem * itemParent, const QLatin1String & itemName, const T & val ) +{ + return set( getItem(itemParent, itemName, true), val ); +} +template inline bool BaseModel::set( const NifItem * itemParent, const char * itemName, const T & val ) +{ + return set( getItem(itemParent, QLatin1String(itemName), true), val ); +} +template inline bool BaseModel::set( const QModelIndex & index, const T & val ) +{ + return set( getItem(index), val ); +} +template inline bool BaseModel::set( const QModelIndex & itemParent, int itemIndex, const T & val ) +{ + return set( getItem(itemParent, itemIndex, true), val ); +} +template inline bool BaseModel::set( const QModelIndex & itemParent, const QString & itemName, const T & val ) +{ + return set( getItem(itemParent, itemName, true), val ); +} +template inline bool BaseModel::set( const QModelIndex & itemParent, const QLatin1String & itemName, const T & val ) +{ + return set( getItem(itemParent, itemName, true), val ); +} +template inline bool BaseModel::set( const QModelIndex & itemParent, const char * itemName, const T & val ) { - NifItem * item = static_cast( index.internalPointer() ); + return set( getItem(itemParent, QLatin1String(itemName), true), val ); +} - if ( !( index.isValid() && item && index.model() == this ) ) - return false; - return set( item, d ); -} +// Array size management -template inline QVector BaseModel::getArray( const QModelIndex & iArray ) const +inline bool BaseModel::updateArraySize( NifItem * arrayRootItem ) +{ + return updateArraySizeImpl( arrayRootItem ); +} +inline bool BaseModel::updateArraySize( const NifItem * arrayParent, int arrayIndex ) { - NifItem * item = static_cast( iArray.internalPointer() ); + return updateArraySizeImpl( getItem(arrayParent, arrayIndex) ); +} +inline bool BaseModel::updateArraySize( const NifItem * arrayParent, const QString & arrayName ) +{ + return updateArraySizeImpl( getItem(arrayParent, arrayName) ); +} +inline bool BaseModel::updateArraySize( const NifItem * arrayParent, const QLatin1String & arrayName ) +{ + return updateArraySizeImpl( getItem(arrayParent, arrayName) ); +} +inline bool BaseModel::updateArraySize( const NifItem * arrayParent, const char * arrayName ) +{ + return updateArraySizeImpl( getItem(arrayParent, QLatin1String(arrayName)) ); +} +inline bool BaseModel::updateArraySize( const QModelIndex & iArray ) +{ + return updateArraySizeImpl( getItem(iArray) ); +} +inline bool BaseModel::updateArraySize( const QModelIndex & arrayParent, int arrayIndex ) +{ + return updateArraySizeImpl( getItem(arrayParent, arrayIndex) ); +} +inline bool BaseModel::updateArraySize( const QModelIndex & arrayParent, const QString & arrayName ) +{ + return updateArraySizeImpl( getItem(arrayParent, arrayName) ); +} +inline bool BaseModel::updateArraySize( const QModelIndex & arrayParent, const QLatin1String & arrayName ) +{ + return updateArraySizeImpl( getItem(arrayParent, arrayName) ); +} +inline bool BaseModel::updateArraySize( const QModelIndex & arrayParent, const char * arrayName ) +{ + return updateArraySizeImpl( getItem(arrayParent, QLatin1String(arrayName)) ); +} - if ( isArray( iArray ) && item && iArray.model() == this ) - return item->getArray(); - return QVector(); -} +// Array getters -template inline QVector BaseModel::getArray( const QModelIndex & iParent, const QString & name ) const +template inline QVector BaseModel::getArray( const NifItem * arrayRootItem ) const { - return getArray( getIndex( iParent, name ) ); + return NifItem::getArray( arrayRootItem ); } - -template inline void BaseModel::setArray( const QModelIndex & iArray, const QVector & array ) +template inline QVector BaseModel::getArray( const NifItem * arrayParent, int arrayIndex ) const { - NifItem * item = static_cast( iArray.internalPointer() ); + return NifItem::getArray( getItem(arrayParent, arrayIndex) ); +} +template inline QVector BaseModel::getArray( const NifItem * arrayParent, const QString & arrayName ) const +{ + return NifItem::getArray( getItem(arrayParent, arrayName) ); +} +template inline QVector BaseModel::getArray( const NifItem * arrayParent, const QLatin1String & arrayName ) const +{ + return NifItem::getArray( getItem(arrayParent, arrayName) ); +} +template inline QVector BaseModel::getArray( const NifItem * arrayParent, const char * arrayName ) const +{ + return NifItem::getArray( getItem(arrayParent, QLatin1String(arrayName)) ); +} +template inline QVector BaseModel::getArray( const QModelIndex & iArray ) const +{ + return NifItem::getArray( getItem(iArray) ); +} +template inline QVector BaseModel::getArray( const QModelIndex & arrayParent, int arrayIndex ) const +{ + return NifItem::getArray( getItem(arrayParent, arrayIndex) ); +} +template inline QVector BaseModel::getArray( const QModelIndex & arrayParent, const QString & arrayName ) const +{ + return NifItem::getArray( getItem(arrayParent, arrayName)); +} +template inline QVector BaseModel::getArray( const QModelIndex & arrayParent, const QLatin1String & arrayName ) const +{ + return NifItem::getArray( getItem(arrayParent, arrayName) ); +} +template inline QVector BaseModel::getArray( const QModelIndex & arrayParent, const char * arrayName ) const +{ + return NifItem::getArray( getItem(arrayParent, QLatin1String(arrayName)) ); +} - if ( isArray( iArray ) && item && iArray.model() == this ) { - item->setArray( array ); - int x = item->childCount() - 1; - if ( x >= 0 ) - emit dataChanged( createIndex( 0, ValueCol, item->child( 0 ) ), createIndex( x, ValueCol, item->child( x ) ) ); +// Array setters + +template inline void BaseModel::setArray( NifItem * arrayRootItem, const QVector & array ) +{ + if ( arrayRootItem ) { + arrayRootItem->setArray( array ); + onArrayValuesChange( arrayRootItem ); } } - -template inline void BaseModel::setArray( const QModelIndex & iArray, const T & val ) +template inline void BaseModel::setArray( const NifItem * arrayParent, int arrayIndex, const QVector & array ) +{ + setArray( getItem(arrayParent, arrayIndex, true), array ); +} +template inline void BaseModel::setArray( const NifItem * arrayParent, const QString & arrayName, const QVector & array ) +{ + setArray( getItem(arrayParent, arrayName, true), array ); +} +template inline void BaseModel::setArray( const NifItem * arrayParent, const QLatin1String & arrayName, const QVector & array ) +{ + setArray( getItem(arrayParent, arrayName, true), array ); +} +template inline void BaseModel::setArray( const NifItem * arrayParent, const char * arrayName, const QVector & array ) +{ + setArray( getItem(arrayParent, QLatin1String(arrayName), true), array ); +} +template inline void BaseModel::setArray( const QModelIndex & iArray, const QVector & array ) { - NifItem * item = static_cast(iArray.internalPointer()); + setArray( getItem(iArray), array ); +} +template inline void BaseModel::setArray( const QModelIndex & arrayParent, int arrayIndex, const QVector & array ) +{ + setArray( getItem(arrayParent, arrayIndex, true), array ); +} +template inline void BaseModel::setArray( const QModelIndex & arrayParent, const QString & arrayName, const QVector & array ) +{ + setArray( getItem(arrayParent, arrayName, true), array ); +} +template inline void BaseModel::setArray( const QModelIndex & arrayParent, const QLatin1String & arrayName, const QVector & array ) +{ + setArray( getItem(arrayParent, arrayName, true), array ); +} +template inline void BaseModel::setArray( const QModelIndex & arrayParent, const char * arrayName, const QVector & array ) +{ + setArray( getItem(arrayParent, QLatin1String(arrayName), true), array ); +} - if ( isArray( iArray ) && item && iArray.model() == this ) { - item->setArray( val ); - int x = item->childCount() - 1; - if ( x >= 0 ) - emit dataChanged( createIndex( 0, ValueCol, item->child( 0 ) ), createIndex( x, ValueCol, item->child( x ) ) ); +// Array fillers + +template inline void BaseModel::fillArray( NifItem * arrayRootItem, const T & val ) +{ + if ( arrayRootItem ) { + arrayRootItem->fillArray( val ); + onArrayValuesChange( arrayRootItem ); } } - -template inline void BaseModel::setArray( const QModelIndex & iParent, const QString & name, const QVector & array ) +template inline void BaseModel::fillArray( const NifItem * arrayParent, int arrayIndex, const T & val ) +{ + fillArray( getItem(arrayParent, arrayIndex, true), val ); +} +template inline void BaseModel::fillArray( const NifItem * arrayParent, const QString & arrayName, const T & val ) +{ + fillArray( getItem(arrayParent, arrayName, true), val ); +} +template inline void BaseModel::fillArray( const NifItem * arrayParent, const QLatin1String & arrayName, const T & val ) +{ + fillArray( getItem(arrayParent, arrayName, true), val ); +} +template inline void BaseModel::fillArray( const NifItem * arrayParent, const char * arrayName, const T & val ) +{ + fillArray( getItem(arrayParent, QLatin1String(arrayName), true), val ); +} +template inline void BaseModel::fillArray( const QModelIndex & iArray, const T & val ) +{ + fillArray( getItem(iArray), val ); +} +template inline void BaseModel::fillArray( const QModelIndex & arrayParent, int arrayIndex, const T & val ) +{ + fillArray( getItem(arrayParent, arrayIndex, true), val ); +} +template inline void BaseModel::fillArray( const QModelIndex & arrayParent, const QString & arrayName, const T & val ) +{ + fillArray( getItem(arrayParent, arrayName, true), val ); +} +template inline void BaseModel::fillArray( const QModelIndex & arrayParent, const QLatin1String & arrayName, const T & val ) +{ + fillArray( getItem(arrayParent, arrayName, true), val ); +} +template inline void BaseModel::fillArray( const QModelIndex & arrayParent, const char * arrayName, const T & val ) { - setArray( getIndex( iParent, name ), array ); + fillArray( getItem(arrayParent, QLatin1String(arrayName), true), val ); } #endif diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index 09dc078d8..86241c1e0 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -85,22 +85,12 @@ quint32 KfmModel::version2number( const QString & s ) return v; } -bool KfmModel::evalVersion( NifItem * item, bool chkParents ) const +bool KfmModel::evalVersionImpl( const NifItem * item ) const { - if ( item->isVercondValid() ) - return item->versionCondition(); - - item->setVersionCondition( item == root ); - if ( item->versionCondition() ) - return true; - - if ( chkParents && item->parent() ) { - // Set false if parent is false and early reject - item->setVersionCondition( evalVersion( item->parent(), true ) ); - if ( !item->versionCondition() ) - return false; - } + return item->evalVersion(version); + // Gavrant: the commented out code below has been copied from the old evalVersion, and it does NOT make sense + /* // Early reject for ver1/ver2 item->setVersionCondition( item->evalVersion( version ) ); if ( !item->versionCondition() ) @@ -112,6 +102,7 @@ bool KfmModel::evalVersion( NifItem * item, bool chkParents ) const return true; return item->evalVersion( version ); + */ } void KfmModel::clear() @@ -125,9 +116,7 @@ void KfmModel::clear() rootData.setIsCompound( true ); rootData.setIsConditionless( true ); insertType( root, rootData ); - kfmroot = (root->childCount()) ? root->child( 0 ) : nullptr; - if ( kfmroot ) - kfmroot->setCondition( true ); + kfmroot = root->child( 0 ); version = 0x0200000b; endResetModel(); @@ -139,64 +128,51 @@ void KfmModel::clear() * array functions */ -static QString parentPrefix( const QString & x ) +bool KfmModel::updateArraySizeImpl( NifItem * array ) { - for ( int c = 0; c < x.length(); c++ ) { - if ( !x[c].isNumber() ) - return QString( "..\\" ) + x; + if ( !array->isArray() ) { + if ( array ) + reportError( array, "updateArraySize", "The input item is not an array." ); + return false; } + // Get new array size + int nNewSize = evalArraySize( array ); - return x; -} - -bool KfmModel::updateArrayItem( NifItem * array ) -{ - if ( !array->isArray() ) + if ( nNewSize > 1024 * 1024 * 8 ) { + reportError( array, "updateArraySize", tr( "Array size %1 is much too large." ).arg( nNewSize ) ); return false; - - int d1 = getArraySize( array ); - - if ( d1 > 1024 * 1024 * 8 ) { - auto m = tr( "array %1 much too large. %2 bytes requested" ).arg( array->name() ).arg( d1 ); - if ( msgMode == MSG_USER ) { - Message::append( nullptr, tr( "Could not update array item." ), m, QMessageBox::Critical ); - } else { - testMsg( m ); - } + } else if ( nNewSize < 0 ) { + reportError( array, "updateArraySize", tr( "Array size %1 is invalid." ).arg( nNewSize ) ); return false; } - int rows = array->childCount(); + int nOldSize = array->childCount(); - if ( d1 > rows ) { + if ( nNewSize > nOldSize ) { // Add missing items NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), - parentPrefix( array->arg() ), - parentPrefix( array->arr2() ) ); - + addConditionParentPrefix( array->arg() ), + addConditionParentPrefix( array->arr2() ) // arr1 in children is parent arr2 + ); + // Fill data flags data.setIsConditionless( true ); data.setIsCompound( array->isCompound() ); data.setIsArray( array->isMultiArray() ); - beginInsertRows( createIndex( array->row(), 0, array ), rows, d1 - 1 ); - - array->prepareInsert( d1 - rows ); - - for ( int c = rows; c < d1; c++ ) + beginInsertRows( itemToIndex(array), nOldSize, nNewSize - 1 ); + array->prepareInsert( nNewSize - nOldSize ); + for ( int c = nOldSize; c < nNewSize; c++ ) insertType( array, data ); - endInsertRows(); } - if ( d1 < rows ) { - beginRemoveRows( createIndex( array->row(), 0, array ), d1, rows - 1 ); - - array->removeChildren( d1, rows - d1 ); - + if ( nNewSize < nOldSize ) { // Remove excess items + beginRemoveRows( itemToIndex(array), nNewSize, nOldSize - 1 ); + array->removeChildren( nNewSize, nOldSize - nNewSize ); endRemoveRows(); } @@ -213,7 +189,7 @@ void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) NifItem * array = insertBranch( parent, data, at ); if ( evalCondition( array ) ) - updateArrayItem( array ); + updateArraySize( array ); } else if ( data.isCompound() ) { NifBlockPtr compound = compounds.value( data.type() ); @@ -223,7 +199,7 @@ void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) branch->prepareInsert( compound->types.count() ); if ( !data.arg().isEmpty() || !data.temp().isEmpty() ) { - QString arg = parentPrefix( data.arg() ); + QString arg = addConditionParentPrefix( data.arg() ); QString tmp = data.temp(); if ( tmp == XMLTMPL ) { @@ -262,17 +238,6 @@ void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) } -/* - * item value functions - */ - -bool KfmModel::setItemValue( NifItem * item, const NifValue & val ) -{ - item->value() = val; - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - return true; -} - /* * load and save */ @@ -331,17 +296,13 @@ bool KfmModel::load( NifItem * parent, NifIStream & stream ) if ( !parent ) return false; - for ( int row = 0; row < parent->childCount(); row++ ) { - NifItem * child = parent->child( row ); - - if ( !child->isConditionless() ) - child->invalidateCondition(); + for ( NifItem * child: parent->childIter() ) { + child->invalidateCondition(); if ( evalCondition( child ) ) { if ( child->isArray() ) { - if ( !updateArrayItem( child ) ) + if ( !updateArraySize( child ) ) return false; - if ( !load( child, stream ) ) return false; } else if ( child->childCount() > 0 ) { @@ -367,7 +328,7 @@ bool KfmModel::save( NifItem * parent, NifOStream & stream ) const if ( evalCondition( child ) ) { if ( !child->arr1().isEmpty() || !child->arr2().isEmpty() || child->childCount() > 0 ) { - if ( !child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) { + if ( !child->arr1().isEmpty() && child->childCount() != evalArraySize( child ) ) { Message::append( tr( "Warnings were generated while reading the blocks." ), tr( "%1 array size mismatch" ).arg( child->name() ) ); @@ -387,7 +348,5 @@ bool KfmModel::save( NifItem * parent, NifOStream & stream ) const NifItem * KfmModel::insertBranch( NifItem * parentItem, const NifData & data, int at ) { - NifItem * item = parentItem->insertChild( data, at ); - item->value().changeType( NifValue::tNone ); - return item; + return parentItem->insertChild( data, NifValue::tNone, at ); } diff --git a/src/model/kfmmodel.h b/src/model/kfmmodel.h index b111cbe3e..f8b4df6f6 100644 --- a/src/model/kfmmodel.h +++ b/src/model/kfmmodel.h @@ -89,16 +89,14 @@ class KfmModel final : public BaseModel void insertType( NifItem * parent, const NifData & data, int row = -1 ); NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); - bool updateArrayItem( NifItem * array ) override final; + bool updateArraySizeImpl( NifItem * array ) override final; bool load( NifItem * parent, NifIStream & stream ); bool save( NifItem * parent, NifOStream & stream ) const; - bool setItemValue( NifItem * item, const NifValue & v ) override final; - bool setHeaderString( const QString &, uint ver = 0 ) override final; - bool evalVersion( NifItem * item, bool chkParents = false ) const override final; + bool evalVersionImpl( const NifItem * item ) const override final; QString ver2str( quint32 v ) const override final { return version2string( v ); } quint32 str2ver( QString s ) const override final { return version2number( s ); } diff --git a/src/model/nifdelegate.cpp b/src/model/nifdelegate.cpp index 1126646f9..bc77d7785 100644 --- a/src/model/nifdelegate.cpp +++ b/src/model/nifdelegate.cpp @@ -119,7 +119,8 @@ class NifDelegate final : public QItemDelegate // Yes/No toggle for bool types if ( nv.type() == NifValue::tBool ) { - nv.set( !nv.get() ); + int oldv = nv.get( nullptr, nullptr ); + nv.set( oldv ? 0 : 1, nullptr, nullptr ); model->setData( index, nv.toVariant(), Qt::EditRole ); return true; } @@ -248,7 +249,7 @@ class NifDelegate final : public QItemDelegate cedit->clear(); QString t = index.sibling( index.row(), NifModel::TypeCol ).data( NifSkopeDisplayRole ).toString(); const NifValue::EnumOptions & eo = NifValue::enumOptionData( t ); - quint32 value = v.value().toCount(); + quint32 value = v.value().toCount( nullptr, nullptr ); QMapIterator > it( eo.o ); while ( it.hasNext() ) { @@ -315,7 +316,7 @@ class NifDelegate final : public QItemDelegate if ( v.canConvert() ) { NifValue nv = v.value(); - nv.setCount( x ); + nv.setCount( x, nullptr, nullptr ); v.setValue( nv ); // Value is unchanged, do not push to Undo Stack or call setData() diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 3096c4742..1114b9173 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -180,39 +180,6 @@ quint32 NifModel::version2number( const QString & s ) return ( i == 0xffffffff ? 0 : i ); } -bool NifModel::evalVersion( NifItem * item, bool chkParents ) const -{ - if ( item->isVercondValid() ) - return item->versionCondition(); - - item->setVersionCondition( item == root ); - if ( item->versionCondition() ) - return true; - - if ( chkParents && item->parent() ) { - // Set false if parent is false and early reject - item->setVersionCondition( evalVersion( item->parent(), true ) ); - if ( !item->versionCondition() ) - return false; - } - - // Early reject for ver1/ver2 - item->setVersionCondition( item->evalVersion( version ) ); - if ( !item->versionCondition() ) - return false; - - // Early reject for vercond - item->setVersionCondition( item->vercond().isEmpty() ); - if ( item->versionCondition() ) - return true; - - // If there is a vercond, evaluate it - NifModelEval functor( this, getHeaderItem() ); - item->setVersionCondition( item->verexpr().evaluateBool( functor ) ); - - return item->versionCondition(); -} - void NifModel::clear() { beginResetModel(); @@ -239,30 +206,19 @@ void NifModel::clear() } endResetModel(); - NifItem * headerItem = getHeaderItem(); - - NifItem * item = getItem( headerItem, "Version" ); - if ( item ) - item->value().setFileVersion( version ); - - QString header_string; + NifItem * header = getHeaderItem(); - if ( version <= 0x0A000100 ) { - header_string = "NetImmerse File Format, Version "; - } else { - header_string = "Gamebryo File Format, Version "; - } + NifItem::valueFromFileVersion( getItem( header, "Version" ), version ); + QString header_string( ( version <= 0x0A000100 ) ? "NetImmerse File Format, Version " : "Gamebryo File Format, Version " ); header_string += version2string( version ); + set( header, "Header String", header_string ); - set( headerItem, "Header String", header_string ); - - if ( version >= 0x14000005 ) { - set( headerItem, "User Version", cfg.userVersion ); - set( headerItem, "User Version 2", cfg.userVersion2 ); - } + set( getItem( header, "User Version", false ), cfg.userVersion ); + set( getItem( header, "BS Header\\BS Version", false ), cfg.userVersion2 ); + invalidateItemConditions( header ); - //set( headerItem, "Unknown Int 3", 11 ); + //set( header, "Unknown Int 3", 11 ); if ( version < 0x0303000D ) { QVector copyright( 3 ); @@ -270,57 +226,15 @@ void NifModel::clear() copyright[1] = "Copyright (c) 1996-2000"; copyright[2] = "All Rights Reserved"; - setArray( getHeader(), "Copyright", copyright ); + setArray( header, "Copyright", copyright ); } - cacheBSVersion( headerItem ); + cacheBSVersion( header ); lockUpdates = false; needUpdates = utNone; } -/* - * footer functions - */ - -NifItem * NifModel::getFooterItem() const -{ - return root->child( root->childCount() - 1 ); -} - -QModelIndex NifModel::getFooter() const -{ - NifItem * footer = getFooterItem(); - - if ( footer ) - return createIndex( footer->row(), 0, footer ); - - return QModelIndex(); -} - -void NifModel::updateFooter() -{ - if ( lockUpdates ) { - needUpdates = UpdateType( needUpdates | utFooter ); - return; - } - - NifItem * footer = getFooterItem(); - - if ( !footer ) - return; - - NifItem * roots = getItem( footer, "Roots" ); - - if ( !roots ) - return; - - set( footer, "Num Roots", rootLinks.count() ); - updateArrayItem( roots ); - - for ( int r = 0; r < roots->childCount(); r++ ) - roots->child( r )->value().setLink( rootLinks.value( r ) ); -} /* * header functions @@ -340,11 +254,6 @@ QString NifModel::extractRTTIArgs( const QString & RTTIName, NiMesh::DataStreamM return nameAndArgs[0]; } -QString NifModel::createRTTIName( const QModelIndex & iBlock ) const -{ - return createRTTIName( static_cast(iBlock.internalPointer()) ); -} - QString NifModel::createRTTIName( const NifItem * block ) const { if ( !block ) @@ -352,20 +261,14 @@ QString NifModel::createRTTIName( const NifItem * block ) const if ( block->hasName("NiDataStream") ) { return QString( "NiDataStream\x01%1\x01%2" ) - .arg( NifItem::get( block->child("Usage") ) ) - .arg( NifItem::get( block->child("Access") ) ); + .arg( NifItem::get( getItem( block, "Usage", true ) ) ) + .arg( NifItem::get( getItem( block, "Access", true ) ) ); } return block->name(); } -QModelIndex NifModel::getHeader() const -{ - QModelIndex header = index( 0, 0 ); - return header; -} - -NifItem * NifModel::getHeaderItem() const +const NifItem * NifModel::getHeaderItem() const { return root->child( 0 ); } @@ -382,282 +285,249 @@ void NifModel::updateHeader() NifItem * header = getHeaderItem(); set( header, "Num Blocks", getBlockCount() ); - NifItem * idxBlockTypes = getItem( header, "Block Types" ); - NifItem * idxBlockTypeIndices = getItem( header, "Block Type Index" ); - NifItem * idxBlockSize = getItem( header, "Block Size" ); + + NifItem * itemBlockTypes = getItem( header, "Block Types" ); + NifItem * itemBlockTypeIndices = getItem( header, "Block Type Index" ); + NifItem * itemBlockSizes = ( version >= 0x14020000 ) ? getItem( header, "Block Size" ) : nullptr; // 20.3.1.2 Custom Version - NifItem * idxBlockTypeHashes = nullptr; - if ( version == 0x14030102 ) - idxBlockTypeHashes = getItem( header, "Block Type Hashes" ); + NifItem * itemBlockTypeHashes = ( version == 0x14030102 ) ? getItem( header, "Block Type Hashes" ) : nullptr; // Update Block Types, Block Type Index, and Block Size - if ( (idxBlockTypes || idxBlockTypeHashes) && idxBlockTypeIndices ) { - QVector blocktypes; - QVector blocktypeindices; - QVector blocksizes; - - for ( int r = 1; r < root->childCount() - 1; r++ ) { - NifItem * block = root->child( r ); - QString blockName = createRTTIName( block ); - - int bTypeIdx = blocktypes.indexOf( blockName ); - if ( bTypeIdx < 0 ) { - blocktypes.append( blockName ); - bTypeIdx = blocktypes.count() - 1; - } - - blocktypeindices.append( bTypeIdx ); - - if ( version >= 0x14020000 && idxBlockSize ) { - updateArrays( block ); - blocksizes.append( blockSize( block ) ); - } - - } + if ( (itemBlockTypes || itemBlockTypeHashes) && itemBlockTypeIndices ) { + QVector blockTypes; + QVector blockTypeIndices; + QVector blockSizes; + + for ( int r = firstBlockRow(); r <= lastBlockRow(); r++ ) { + NifItem * itemBlock = root->child( r ); + if ( !itemBlock ) // Just in case... + continue; - set( header, "Num Block Types", blocktypes.count() ); + QString blockName = createRTTIName( itemBlock ); - if ( idxBlockTypes ) - updateArrayItem( idxBlockTypes ); - updateArrayItem( idxBlockTypeIndices ); + int iBlockType = blockTypes.indexOf( blockName ); + if ( iBlockType < 0 ) { + blockTypes.append( blockName ); + iBlockType = blockTypes.count() - 1; + } + blockTypeIndices.append( iBlockType ); - if ( version >= 0x14020000 && idxBlockSize ) { - updateArrayItem( idxBlockSize ); + if ( itemBlockSizes ) { + updateChildArraySizes( itemBlock ); + blockSizes.append( blockSize( itemBlock ) ); + } } + set( header, "Num Block Types", blockTypes.count() ); + setState( Processing ); - idxBlockTypeIndices->setArray( blocktypeindices ); - if ( idxBlockTypes ) - idxBlockTypes->setArray( blocktypes ); - if ( blocksizes.count() ) - idxBlockSize->setArray( blocksizes ); + updateArraySize(itemBlockTypeIndices); + itemBlockTypeIndices->setArray( blockTypeIndices ); + if ( itemBlockTypes ) { + updateArraySize(itemBlockTypes); + itemBlockTypes->setArray( blockTypes ); + } + if ( itemBlockSizes ) { + updateArraySize(itemBlockSizes); + itemBlockSizes->setArray( blockSizes ); + } // 20.3.1.2 Custom Version - if ( idxBlockTypeHashes ) { - QVector blocktypehashes; - for ( const auto & t : blocktypes ) - blocktypehashes << DJB1Hash( t.toStdString().c_str() ); + if ( itemBlockTypeHashes ) { + updateArraySize( itemBlockTypeHashes ); - updateArrayItem( idxBlockTypeHashes ); - idxBlockTypeHashes->setArray( blocktypehashes ); + QVector hashes; + hashes.reserve( blockTypes.count() ); + for ( const auto & t : blockTypes ) + hashes << DJB1Hash( t.toStdString().c_str() ); + setArray( itemBlockTypeHashes, hashes ); } restoreState(); // For 20.1 and above strings are saved in the header. Max String Length must be updated. if ( version >= 0x14010003 ) { - int maxlen = 0; - int nstrings = get( header, "Num Strings" ); - QModelIndex iArray = getIndex( getHeader(), "Strings" ); - - if ( nstrings > 0 && iArray.isValid() ) { - for ( int row = 0; row < nstrings; ++row ) { - int len = get( iArray.child( row, 0 ) ).length(); - - if ( len > maxlen ) - maxlen = len; + int nMaxLen = 0; + auto headerStrings = getItem( header, "Strings" ); + if ( headerStrings ) { + for ( auto c : headerStrings->childIter() ) { + int len = c->get().length(); + if ( len > nMaxLen ) + nMaxLen = len; } } - - set( header, "Max String Length", maxlen ); + set( header, "Max String Length", nMaxLen ); } } } + + /* - * search and find + * footer functions */ -NifItem * NifModel::getItem( NifItem * item, const QString & name ) const +const NifItem * NifModel::getFooterItem() const { - if ( !item || item == root ) - return nullptr; - - //if ( item->isArray() || item->parent()->isArray() ) { - int slash = name.indexOf( QLatin1String("\\") ); - if ( slash > 0 ) { - QString left = name.left( slash ); - QString right = name.right( name.length() - slash - 1 ); - - // Resolve ../ for arr1, arr2, arg passing - if ( left == ".." ) - return getItem( item->parent(), right ); - - return getItem( getItem( item, left ), right ); - } - //} + int nRootChildren = root->childCount(); + return ( nRootChildren >= 2 ) ? root->child( nRootChildren - 1 ) : nullptr; +} - for ( auto child : item->children() ) { - if ( child && child->hasName(name) && evalCondition( child ) ) - return child; +void NifModel::updateFooter() +{ + if ( lockUpdates ) { + needUpdates = UpdateType( needUpdates | utFooter ); + return; } - return nullptr; + NifItem * footer = getFooterItem(); + NifItem * itemRoots = getItem( footer, "Roots" ); + if ( itemRoots ) { + set( footer, "Num Roots", rootLinks.count() ); + updateArraySize( itemRoots ); + for ( int r = 0; r < itemRoots->childCount(); r++ ) + NifItem::valueFromLink( itemRoots->child( r ), rootLinks.value( r ) ); + } } /* * array functions */ -static QString parentPrefix( const QString & x ) +bool NifModel::updateArraySizeImpl( NifItem * array ) { - for ( int c = 0; c < x.length(); c++ ) { - if ( !x[c].isNumber() ) - return QString( "..\\" ) + x; + if ( !isArray( array ) ) { + if ( array ) + reportError( array, "updateArraySize", "The input item is not an array." ); + return false; } - return x; + // Binary array handling + if ( array->isBinary() ) { + return updateByteArraySize( array ); + } + + // Get new array size + int nNewSize = evalArraySize( array ); + + if ( nNewSize > 1024 * 1024 * 8 ) { + reportError( array, "updateArraySize", tr( "Array size %1 is much too large." ).arg( nNewSize ) ); + return false; + } else if ( nNewSize < 0 ) { + reportError( array, "updateArraySize", tr( "Array size %1 is invalid." ).arg( nNewSize ) ); + return false; + } + + int nOldSize = array->childCount(); + + if ( nNewSize > nOldSize ) { // Add missing items + NifData data( array->name(), + array->type(), + array->temp(), + NifValue( NifValue::type( array->type() ) ), + addConditionParentPrefix( array->arg() ), + addConditionParentPrefix( array->arr2() ) // arr1 in children is parent arr2 + ); + + // Fill data flags + data.setIsConditionless( true ); + data.setIsCompound( array->isCompound() ); + data.setIsArray( array->isMultiArray() ); + + beginInsertRows( itemToIndex(array), nOldSize, nNewSize - 1 ); + array->prepareInsert( nNewSize - nOldSize ); + for ( int c = nOldSize; c < nNewSize; c++ ) + insertType( array, data ); + endInsertRows(); + } + + if ( nNewSize < nOldSize ) { // Remove excess items + beginRemoveRows( itemToIndex(array), nNewSize, nOldSize - 1 ); + array->removeChildren( nNewSize, nOldSize - nNewSize ); + endRemoveRows(); + } + + if ( nNewSize != nOldSize + && state != Loading + && ( isCompound(array->type()) || NifValue::isLink(NifValue::type(array->type())) ) + && getTopItem( array ) != getFooterItem() + ) { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + + return true; } -bool NifModel::updateByteArrayItem( NifItem * array ) +bool NifModel::updateByteArraySize( NifItem * array ) { - // New row count - int rows = getArraySize( array ); - if ( rows == 0 ) + // TODO (Gavrant): I don't understand what's going on here, rewrite the function + + int nNewSize = evalArraySize( array ); + int nOldRows = array->childCount(); + if ( nNewSize == 0 ) return false; // Create byte array for holding blob data QByteArray bytes; - bytes.resize( rows ); + bytes.resize( nNewSize ); // Previous row count - int itemRows = array->childCount(); // Grab data from existing rows if appropriate and then purge - if ( itemRows > 1 ) { - for ( int i = 0; i < itemRows; i++ ) { + if ( nOldRows > 1 ) { + for ( int i = 0; i < nOldRows; i++ ) { if ( NifItem * child = array->child( 0 ) ) { bytes[i] = get( child ); } } - beginRemoveRows( createIndex( array->row(), 0, array ), 0, itemRows - 1 ); - array->removeChildren( 0, itemRows ); + beginRemoveRows( itemToIndex(array), 0, nOldRows - 1 ); + array->removeChildren( 0, nOldRows ); endRemoveRows(); - itemRows = 0; + nOldRows = 0; } // Create the dummy row for holding the byte array - if ( itemRows == 0 ) { - NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::tBlob ), parentPrefix( array->arg() ) ); + if ( nOldRows == 0 ) { + NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::tBlob ), addConditionParentPrefix( array->arg() ) ); data.setBinary( true ); - beginInsertRows( createIndex( array->row(), 0, array ), 0, 1 ); - + beginInsertRows( itemToIndex(array), 0, 1 ); array->prepareInsert( 1 ); insertType( array, data ); - endInsertRows(); } // Update the byte array if ( NifItem * child = array->child( 0 ) ) { - QByteArray * bm = (child->isBinary()) ? get( child ) : nullptr; + QByteArray * bm = child->isBinary() ? get( child ) : nullptr; if ( !bm ) { set( child, bytes ); } else if ( bm->size() == 0 ) { *bm = bytes; } else { - bm->resize( rows ); - } - } - - return true; -} - -bool NifModel::updateArrayItem( NifItem * array ) -{ - if ( !isArray( array ) ) - return false; - - // New row count - int rows = getArraySize( array ); - - // Binary array handling - if ( array->isBinary() ) { - if ( updateByteArrayItem( array ) ) - return true; - } - - // Error handling - if ( rows > 1024 * 1024 * 8 ) { - auto m = tr( "[%1] Array %2 much too large. %3 bytes requested" ).arg( getBlockNumber( array ) ) - .arg( array->name() ).arg( rows ); - logMessage(tr(readFail), m, QMessageBox::Critical); - - return false; - } else if ( rows < 0 ) { - auto m = tr( "[%1] Array %2 invalid" ).arg( getBlockNumber( array ) ).arg( array->name() ); - logMessage(tr(readFail), m, QMessageBox::Critical); - - return false; - } - - // Previous row count - int itemRows = array->childCount(); - - // Add item children - if ( rows > itemRows ) { - NifData data( array->name(), - array->type(), - array->temp(), - NifValue( NifValue::type( array->type() ) ), - parentPrefix( array->arg() ), - parentPrefix( array->arr2() ) // arr1 in children is parent arr2 - ); - - // Fill data flags - data.setIsConditionless( true ); - data.setIsCompound( array->isCompound() ); - data.setIsArray( array->isMultiArray() ); - - beginInsertRows( createIndex( array->row(), 0, array ), itemRows, rows - 1 ); - - array->prepareInsert( rows - itemRows ); - - for ( int c = itemRows; c < rows; c++ ) - insertType( array, data ); - - endInsertRows(); - } - - // Remove item children - if ( rows < itemRows ) { - beginRemoveRows( createIndex( array->row(), 0, array ), rows, itemRows - 1 ); - - array->removeChildren( rows, itemRows - rows ); - - endRemoveRows(); - } - - if ( (state != Loading) && (rows != itemRows) && (isCompound( array->type() ) || NifValue::isLink( NifValue::type( array->type() ) )) ) { - NifItem * parent = array; - - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - - if ( parent != getFooterItem() ) { - updateLinks(); - updateFooter(); - emit linksChanged(); + bm->resize( nNewSize ); } } return true; } -bool NifModel::updateArrays( NifItem * parent ) +bool NifModel::updateChildArraySizes( NifItem * parent ) { if ( !parent ) return false; - for ( auto child : parent->children() ) { + for ( auto child : parent->childIter() ) { if ( evalCondition( child ) ) { - if ( isArray( child ) ) { - if ( !updateArrayItem( child ) || !updateArrays( child ) ) + if ( isArray(child) ) { + if ( !updateArraySize(child) ) return false; - } else if ( child->childCount() > 0 ) { - if ( !updateArrays( child ) ) + } + if ( child->childCount() > 0 ) { + if ( !updateChildArraySizes(child) ) return false; } } @@ -688,9 +558,9 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at ) beginInsertRows( QModelIndex(), at, at ); - NifItem * branch = insertBranch( root, NifData( identifier, "NiBlock", block->text ), at ); - branch->setCondition( true ); - + NifData d = NifData( identifier, "NiBlock", block->text ); + d.setIsConditionless( true ); + NifItem * branch = insertBranch( root, d, at ); endInsertRows(); if ( !block->ancestor.isEmpty() ) @@ -780,10 +650,8 @@ void NifModel::updateStrings( NifModel * src, NifModel * tgt, NifItem * item ) if ( !item ) return; - NifValue::Type vt = item->value().type(); - - if ( vt == NifValue::tStringIndex || vt == NifValue::tSizedString || item->hasType("string") ) { - QString str = src->string( src->createIndex( 0, 0, item ) ); + if ( item->valueType() == NifValue::tStringIndex || item->valueType() == NifValue::tSizedString || item->hasType("string") ) { + QString str = src->resolveString( item ); tgt->assignString( tgt->createIndex( 0, 0, item ), str, false ); } @@ -897,129 +765,96 @@ void NifModel::mapLinks( const QMap & map ) updateFooter(); } -QString NifModel::getBlockName( const QModelIndex & idx ) const +int NifModel::getBlockNumber( const NifItem * item ) const { - const NifItem * block = static_cast( idx.internalPointer() ); - + const NifItem * block = getTopItem( item ); if ( block ) { - return block->name(); + int iRow = block->row(); + if ( iRow >= firstBlockRow() && iRow <= lastBlockRow() ) + return iRow - firstBlockRow(); } - return {}; + return -1; } -QString NifModel::getBlockType( const QModelIndex & idx ) const +const NifItem * NifModel::_getBlockItem( const NifItem * block, const QString & ancestor ) const { - const NifItem * block = static_cast( idx.internalPointer() ); + if ( block && inherits( block->name(), ancestor ) ) + return block; - if ( block ) { - return block->type(); - } - - return {}; + return nullptr; } -int NifModel::getBlockNumber( const QModelIndex & idx ) const +const NifItem * NifModel::_getBlockItem( const NifItem * block, const QLatin1String & ancestor ) const { - if ( !idx.isValid() || idx.model() != this ) - return -1; - - const NifItem * block = static_cast( idx.internalPointer() ); - - while ( block && block->parent() != root ) - block = block->parent(); - - if ( !block ) - return -1; - - int num = block->row() - 1; + if ( block && inherits( block->name(), ancestor ) ) + return block; - if ( num >= getBlockCount() ) - num = -1; - - return num; + return nullptr; } -QModelIndex NifModel::getBlock( const QModelIndex & idx, const QString & id ) const +const NifItem * NifModel::_getBlockItem( const NifItem * block, const std::initializer_list & ancestors ) const { - return getBlock( getBlockNumber( idx ), id ); + if ( block && inherits( block->name(), ancestors ) ) + return block; + + return nullptr; } -QModelIndex NifModel::getBlockOrHeader( const QModelIndex & idx ) const +const NifItem * NifModel::_getBlockItem( const NifItem * block, const QStringList & ancestors ) const { - QModelIndex block = idx; + if ( block && inherits( block->name(), ancestors ) ) + return block; - while ( block.isValid() && block.parent().isValid() ) - block = block.parent(); - - return block; + return nullptr; } -int NifModel::getBlockNumber( NifItem * block ) const +const NifItem * NifModel::getBlockItem( qint32 link ) const { - while ( block && block->parent() != root ) - block = block->parent(); - - if ( !block ) - return -1; + if ( link >= 0 && link < getBlockCount() ) + return root->child( link + firstBlockRow() ); - int num = block->row() - 1; - - if ( num >= getBlockCount() ) - num = -1; - - return num; + return nullptr; } -QModelIndex NifModel::getBlock( int x, const QString & name ) const +const NifItem * NifModel::getBlockItem( const NifItem * item ) const { - if ( x < 0 || x >= getBlockCount() ) - return QModelIndex(); - - x += 1; //the first block is the NiHeader - QModelIndex idx = index( x, 0 ); - - if ( inherits( idx, name ) ) - return idx; - - return QModelIndex(); + const NifItem * block = getTopItem( item ); + return isNiBlock(block) ? block : nullptr; } -bool NifModel::isNiBlock( const QModelIndex & index, const QString & name ) const +bool NifModel::isNiBlock( const NifItem * item ) const { - NifItem * item = static_cast( index.internalPointer() ); - - if ( index.isValid() && item && item->parent() == root && getBlockNumber( item ) >= 0 ) { - if ( name.isEmpty() ) + if ( item && item->parent() == root ) { + int iRow = item->row(); + if ( iRow >= firstBlockRow() && iRow <= lastBlockRow() ) return true; - - return item->hasName(name); } return false; } -bool NifModel::isNiBlock( const QModelIndex & index, const QStringList & names ) const +bool NifModel::isNiBlock( const NifItem * item, const std::initializer_list & testTypes ) const { - for ( const QString & name : names ) { - if ( isNiBlock( index, name ) ) - return true; + if ( isNiBlock(item) ) { + for ( auto name : testTypes ) { + if ( item->hasName(name) ) + return true; + } } - return false; } -NifItem * NifModel::getBlockItem( int x ) const +bool NifModel::isNiBlock( const NifItem * item, const QStringList & testTypes ) const { - if ( x < 0 || x >= getBlockCount() ) - return nullptr; - - return root->child( x + 1 ); -} + if ( isNiBlock( item ) ) { + for ( const QString & name : testTypes ) { + if ( item->hasName(name) ) + return true; + } + } -int NifModel::getBlockCount() const -{ - return rowCount() - 2; + return false; } @@ -1050,67 +885,79 @@ void NifModel::insertAncestor( NifItem * parent, const QString & identifier, int restoreState(); } -bool NifModel::inherits( const QString & name, const QString & aunty ) const +bool NifModel::inherits( const QString & blockName, const QString & ancestor ) const { - if ( name == aunty ) + if ( blockName == ancestor ) return true; - NifBlockPtr type = blocks.value( name ); + NifBlockPtr type = blocks.value( blockName ); + return type && inherits( type->ancestor, ancestor ); +} - if ( type && ( type->ancestor == aunty || inherits( type->ancestor, aunty ) ) ) +bool NifModel::inherits( const QString & blockName, const QLatin1String & ancestor ) const +{ + if ( blockName == ancestor ) return true; - return false; + NifBlockPtr type = blocks.value( blockName ); + return type && inherits( type->ancestor, ancestor ); } -bool NifModel::inherits( const QString & name, const QStringList & ancestors ) const +bool NifModel::inherits( const QString & blockName, const std::initializer_list & ancestors ) const { - for ( const auto & a : ancestors ) { - if ( inherits( name, a ) ) + for ( auto a : ancestors ) { + if ( inherits( blockName, a ) ) return true; } return false; } -bool NifModel::inherits( const QModelIndex & idx, const QString & aunty ) const +bool NifModel::inherits( const QString & blockName, const QStringList & ancestors ) const { - int x = getBlockNumber( idx ); + for ( const QString & a : ancestors ) { + if ( inherits( blockName, a ) ) + return true; + } - if ( x < 0 ) - return false; + return false; +} - return inherits( itemName( index( x + 1, 0 ) ), aunty ); +bool NifModel::blockInherits( const NifItem * item, const QString & ancestor ) const +{ + const NifItem * block = getTopItem( item ); + return isNiBlock(block) ? inherits( block->name(), ancestor ) : false; } -bool NifModel::inherits( const QModelIndex& index, const QStringList& ancestors ) const +bool NifModel::blockInherits( const NifItem * item, const QLatin1String & ancestor ) const { - for ( const auto & a : ancestors ) { - if ( inherits( index, a ) ) - return true; - } + const NifItem * block = getTopItem( item ); + return isNiBlock(block) ? inherits( block->name(), ancestor ) : false; +} - return false; +bool NifModel::blockInherits(const NifItem * item, const std::initializer_list & ancestors ) const +{ + const NifItem * block = getTopItem( item ); + return isNiBlock(block) ? inherits( block->name(), ancestors ) : false; +} + +bool NifModel::blockInherits(const NifItem * item, const QStringList & ancestors ) const +{ + const NifItem * block = getTopItem( item ); + return isNiBlock(block) ? inherits( block->name(), ancestors ) : false; } + /* * basic and compound type functions */ -void NifModel::insertType( const QModelIndex & parent, const NifData & data, int at ) -{ - NifItem * item = static_cast( parent.internalPointer() ); - - if ( parent.isValid() && item && item != root ) - insertType( item, data, at ); -} - void NifModel::insertType( NifItem * parent, const NifData & data, int at ) { setState( Inserting ); if ( data.isArray() ) { - NifItem * item = insertBranch( parent, data, at ); + insertBranch( parent, data, at ); } else if ( data.isCompound() ) { NifBlockPtr compound = compounds.value( data.type() ); if ( !compound ) @@ -1151,64 +998,36 @@ void NifModel::insertType( NifItem * parent, const NifData & data, int at ) insertType( parent, d, at ); } else { - NifItem * item = parent->insertChild( data, at ); - - // Kludge for string conversion. - // Ensure that the string type is correct for the nif version - if ( item->value().type() == NifValue::tString || item->value().type() == NifValue::tFilePath ) { - item->value().changeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); - } + if ( data.valueType() == NifValue::tString || data.valueType() == NifValue::tFilePath ) + // Kludge for string conversion. + // Ensure that the string type is correct for the nif version + parent->insertChild( data, version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex, at); + else + parent->insertChild( data, at ); } restoreState(); } -/* - * item value functions - */ - -bool NifModel::setItemValue( NifItem * item, const NifValue & val ) -{ - item->value() = val; - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - - if ( itemIsLink( item ) ) { - NifItem * parent = item; - - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - - if ( parent != getFooterItem() ) { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - } - - return true; -} - /* * QAbstractModel interface */ -QVariant NifModel::data( const QModelIndex & idx, int role ) const +QVariant NifModel::data( const QModelIndex & index, int role ) const { - QModelIndex index = buddy( idx ); - if ( index != idx ) - return data( index, role ); - - NifItem * item = static_cast( index.internalPointer() ); + QModelIndex _buddy = buddy( index ); + if ( _buddy != index ) + return data( _buddy, role ); - if ( !( index.isValid() && item && index.model() == this ) ) + const NifItem * item = getItem( index ); + if ( !item ) return QVariant(); int column = index.column(); - bool ndr = role == NifSkopeDisplayRole; - - if ( role == NifSkopeDisplayRole ) + bool ndr = ( role == NifSkopeDisplayRole ); + if ( ndr ) role = Qt::DisplayRole; switch ( role ) { @@ -1217,22 +1036,22 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const switch ( column ) { case NameCol: { - auto iname = item->name(); + const QString & iname = item->name(); if ( ndr ) return iname; - if ( itemType(index) == "NiBlock" ) - return QString::number(getBlockNumber(index)) + " " + iname; - else if ( isArray(item->parent()) ) { + if ( item->hasType("NiBlock") ) + return QString::number( getBlockNumber(item) ) + " " + iname; + else if ( isArray( item->parent() ) ) { auto arrayName = arrayPseudonyms.value(iname); if ( arrayName.isEmpty() ) { if ( item->hasName("UV Sets") ) - arrayName = QString( (item->value().type() == NifValue::tVector2) ? "UV" : "UV Set" ); + arrayName = QString( item->valueIsVector2() ? "UV" : "UV Set" ); else arrayName = iname; } - return arrayName + " " + QString::number(item->row()); + return arrayName + " " + QString::number( item->row() ); } return " " + iname; @@ -1241,12 +1060,11 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const case TypeCol: { if ( !item->temp().isEmpty() ) { - NifItem * i = item; + const NifItem * tempItem = item; + while ( tempItem && tempItem->temp() == XMLTMPL ) + tempItem = tempItem->parent(); - while ( i && i->temp() == XMLTMPL ) - i = i->parent(); - - return QString( "%1<%2>" ).arg( item->type(), i ? i->temp() : QString() ); + return QString( "%1<%2>" ).arg( item->type(), tempItem ? tempItem->temp() : QString() ); } return item->type(); @@ -1254,93 +1072,92 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const break; case ValueCol: { - const NifValue & value = item->value(); - - if ( value.type() == NifValue::tString || value.type() == NifValue::tFilePath ) { - return QString( this->string( index ) ).replace( "\n", " " ).replace( "\r", " " ); - } - else if ( value.type() == NifValue::tStringOffset ) - { - int ofs = value.get(); - if ( ofs < 0 || ofs == 0x0000FFFF ) - return QString( "" ); - - NifItem * palette = getItemX( item, "String Palette" ); - int link = ( palette ? palette->value().toLink() : -1 ); - - if ( ( palette = getBlockItem( link ) ) && ( palette = getItem( palette, "Palette" ) ) ) { - QByteArray bytes = palette->value().get(); - - if ( !(ofs < bytes.count()) ) - return tr( "" ); - - return QString( &bytes.data()[ofs] ); - } - - return tr( "" ); - } - else if ( value.type() == NifValue::tStringIndex ) - { - int idx = value.get(); - if ( idx == -1 ) + auto vt = item->valueType(); + + if ( vt == NifValue::tString || vt == NifValue::tFilePath ) { + return QString( resolveString( item ) ).replace( "\n", " " ).replace( "\r", " " ); + + } else if ( vt == NifValue::tStringOffset ) { + int offset = item->get(); + if ( offset < 0 || offset == 0x0000FFFF ) + return tr( "" ); + + auto itemPalette = getItemX( item, "String Palette" ); + if ( !itemPalette ) + return tr( "" ); + auto paletteLink = getLink( itemPalette ); + if ( paletteLink < 0 ) + return tr( "" ); + itemPalette = getBlockItem( paletteLink ); + if ( !itemPalette ) + return tr( "" ); + itemPalette = getItem( itemPalette, "Palette", false ); + if ( !itemPalette ) + return tr( "" ); + + QByteArray *pBytes = itemPalette->get(); + if ( !pBytes || offset >= pBytes->count() ) + return tr( "" ); + + return QString( &pBytes->data()[offset] ); + + } else if ( vt == NifValue::tStringIndex ) { + int iStrIndex = item->get(); + if ( iStrIndex == -1 ) return QString(); - - NifItem * header = getHeaderItem(); - QModelIndex stringIndex = createIndex( header->row(), 0, header ); - QString string = get( this->index( idx, 0, getIndex( stringIndex, "Strings" ) ) ); - - if ( idx < 0 ) - return tr( "%1 - " ).arg( idx ); - - return QString( "%2 [%1]" ).arg( idx ).arg( string ); - } - else if ( value.type() == NifValue::tBlockTypeIndex ) - { - - int idx = value.get(); - int offset = idx & 0x7FFF; - NifItem * blocktypes = getItemX( item, "Block Types" ); - NifItem * blocktyp = ( blocktypes ? blocktypes->child( offset ) : 0 ); - - if ( !blocktyp ) - return tr( "%1 - " ).arg( idx ); - - return QString( "%2 [%1]" ).arg( idx ).arg( blocktyp->value().get() ); - } - else if ( value.isLink() ) - { - int lnk = value.toLink(); - - if ( lnk >= 0 ) { - QModelIndex block = getBlock( lnk ); - - if ( !block.isValid() ) - return tr( "%1 " ).arg( lnk ); - - QModelIndex block_name = getIndex( block, "Name" ); - - if ( block_name.isValid() && !get( block_name ).isEmpty() ) - return QString( "%1 (%2)" ).arg( lnk ).arg( get( block_name ) ); - - return QString( "%1 [%2]" ).arg( lnk ).arg( itemName( block ) ); - } - - return tr( "None" ); - } - else if ( value.isCount() ) - { - QString optId = NifValue::enumOptionName( item->type(), value.toCount() ); - + if ( iStrIndex < 0 ) + return tr( "%1 - " ).arg( iStrIndex ); + + auto headerStrings = getItem( getHeaderItem(), "Strings" ); + if ( !headerStrings ) + return tr( "%1 -
" ).arg( iStrIndex ); + if ( iStrIndex >= headerStrings->childCount() ) + return tr( "%1 - " ).arg( iStrIndex ); + auto itemString = getItem( headerStrings, iStrIndex ); + QString s = itemString ? itemString->get() : QString(""); + return QString( "%2 [%1]" ).arg( iStrIndex ).arg( s ); + + } else if ( vt == NifValue::tBlockTypeIndex ) { + int iBlock = item->get(); + auto itemBlockTypes = getItemX( item, "Block Types" ); + if ( !itemBlockTypes ) + return tr( "%1 - " ).arg( iBlock ); + + auto itemBlockEntry = itemBlockTypes->child( iBlock & 0x7FFF ); + if ( !itemBlockEntry ) + return tr( "%1 - " ).arg( iBlock ); + + return QString( "%2 [%1]" ).arg( iBlock ).arg( itemBlockEntry->get() ); + + } else if ( item->valueIsLink() ) { + int link = item->valueToLink(); + if ( link < 0 ) + return tr( "None" ); + if ( link >= getBlockCount() ) + return tr( "%1 " ).arg( link ); + + auto block = root->child( link + firstBlockRow() ); + if ( !block ) + return tr( "%1 " ).arg( link ); + + QString blockName = get( block, "Name" ); + if ( !blockName.isEmpty() ) + return tr( "%1 (%2)" ).arg( link ).arg( blockName ); + + return tr( "%1 [%2]" ).arg( link ).arg( block->name() ); + + } else if ( item->valueIsCount() ) { if ( item->hasType("BSVertexDesc") ) - return BSVertexDesc(value.toCount()).toString(); + return item->get().toString(); + QString optId = NifValue::enumOptionName( item->type(), item->valueToCount() ); if ( optId.isEmpty() ) - return value.toString(); + return item->valueToString(); - return QString( "%1" ).arg( optId ); + return optId; } - return value.toString().replace( "\n", " " ).replace( "\r", " " ); + return item->valueToString().replace( "\n", " " ).replace( "\r", " " ); } break; case ArgCol: @@ -1382,13 +1199,9 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const return item->type(); case ValueCol: { - const NifValue & value = item->value(); - - if ( value.type() == NifValue::tString || value.type() == NifValue::tFilePath ) { - return string( index ); - } - - return item->value().toVariant(); + if ( item->valueType() == NifValue::tString || item->valueType() == NifValue::tFilePath ) + return resolveString( item ); + return item->valueToVariant(); } case ArgCol: return item->arg(); @@ -1439,7 +1252,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const return NifValue::typeDescription( item->type() ); case ValueCol: { - switch ( item->value().type() ) { + switch ( item->valueType() ) { case NifValue::tByte: case NifValue::tWord: case NifValue::tShort: @@ -1449,19 +1262,19 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const case NifValue::tULittle32: { return tr( "dec: %1\nhex: 0x%2" ) - .arg( item->value().toString() ) - .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); + .arg( item->valueToString() ) + .arg( item->valueToCount(), 8, 16, QChar( '0' ) ); } case NifValue::tFloat: case NifValue::tHfloat: { return tr( "float: %1\nhex: 0x%2" ) - .arg( NumOrMinMax( item->value().toFloat(), 'g', 8 ) ) - .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); + .arg( NumOrMinMax( item->valueToFloat(), 'g', 8 ) ) + .arg( item->valueToCount(), 8, 16, QChar( '0' ) ); } case NifValue::tFlags: { - quint16 f = item->value().toCount(); + quint16 f = item->valueToCount(); return tr( "dec: %1\nhex: 0x%2\nbin: 0b%3" ) .arg( f ) .arg( f, 4, 16, QChar( '0' ) ) @@ -1469,26 +1282,26 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const } case NifValue::tStringIndex: return QString( "0x%1" ) - .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); + .arg( item->valueToCount(), 8, 16, QChar( '0' ) ); case NifValue::tStringOffset: return QString( "0x%1" ) - .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); + .arg( item->valueToCount(), 8, 16, QChar( '0' ) ); case NifValue::tVector3: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tHalfVector3: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tByteVector3: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tMatrix: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tMatrix4: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tQuat: case NifValue::tQuatXYZW: - return item->value().get().toHtml(); + return item->get().toHtml(); case NifValue::tColor3: { - Color3 c = item->value().get(); + Color3 c = item->get(); return QString( "R %1\nG %2\nB %3" ) .arg( c[0] ) .arg( c[1] ) @@ -1496,7 +1309,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const } case NifValue::tByteColor4: { - Color4 c = item->value().get(); + Color4 c = item->get(); return QString( "R %1\nG %2\nB %3\nA %4" ) .arg( c[0] ) .arg( c[1] ) @@ -1505,7 +1318,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const } case NifValue::tColor4: { - Color4 c = item->value().get(); + Color4 c = item->get(); return QString( "R %1\nG %2\nB %3\nA %4" ) .arg( c[0] ) .arg( c[1] ) @@ -1526,23 +1339,21 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const { // "notify" about an invalid index in "Triangles" // TODO: checkbox, "show invalid only" - if ( column == ValueCol && item->value().type() == NifValue::tTriangle ) { - NifItem * nv = findItemX( item, "Num Vertices" ); + if ( column == ValueCol && item->valueIsTriangle() ) { + const NifItem * nv = findItemX( item, "Num Vertices" ); if ( !nv ) { qDebug() << "Num Vertices is null"; return QVariant(); } - quint32 nvc = nv->value().toCount(); - Triangle t = item->value().get(); + quint32 nvc = nv->valueToCount(); + Triangle t = item->get(); if ( t[0] >= nvc || t[1] >= nvc || t[2] >= nvc ) return QColor::fromRgb( 240, 210, 210 ); - } - - if ( column == ValueCol && item->value().isColor() ) { - return item->value().toColor(); + } else if ( column == ValueCol && item->valueIsColor() ) { + return item->valueToColor(); } } return QVariant(); @@ -1550,7 +1361,6 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const { if ( column == ValueCol ) { SpellPtr spell = SpellBook::instant( this, index ); - if ( spell ) return spell->page() + "/" + spell->name(); } @@ -1563,15 +1373,17 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const bool NifModel::setData( const QModelIndex & index, const QVariant & value, int role ) { - NifItem * item = static_cast( index.internalPointer() ); + if ( role != Qt::EditRole ) + return false; - if ( !( index.isValid() && role == Qt::EditRole && index.model() == this && item ) ) + NifItem * item = getItem( index ); + if ( !item ) return false; // Set Buddy - QModelIndex idx = buddy( index ); - if ( index != idx ) - return setData( idx, value, role ); + QModelIndex _buddy = buddy( index ); + if ( index != _buddy ) + return setData( _buddy, value, role ); switch ( index.column() ) { case NifModel::NameCol: @@ -1586,19 +1398,11 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r break; case NifModel::ValueCol: { - NifValue & val = item->value(); - - if ( val.type() == NifValue::tString || val.type() == NifValue::tFilePath ) { - val.changeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); - assignString( index, value.toString(), true ); + if ( item->valueType() == NifValue::tString || item->valueType() == NifValue::tFilePath ) { + item->valueChangeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); + assignString( item, value.toString(), true ); } else { - item->value().setFromVariant( value ); - - if ( isLink( index ) && getBlockOrHeader( index ) != getFooter() ) { - updateLinks(); - updateFooter(); - emit linksChanged(); - } + item->valueFromVariant( value ); } } break; @@ -1630,26 +1434,26 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r if ( index.column() == ValueCol ) { if ( item->hasName("File Name") ) { NifItem * parent = item->parent(); - if ( parent && ( parent->hasName("Texture Source") || parent->hasName("NiImage") ) ) { parent = parent->parent(); - - if ( parent && parent->hasType("NiBlock") && parent->hasName("NiSourceTexture") ) - emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); + if ( parent && parent->hasType("NiBlock") && parent->hasName("NiSourceTexture") ) { + QModelIndex pidx = itemToIndex( parent, ValueCol ); + emit dataChanged( pidx, pidx ); + } } + } else if ( item->hasName("Name") ) { NifItem * parent = item->parent(); + if ( parent && parent->hasType("NiBlock") ) { + QModelIndex pidx = itemToIndex( parent, ValueCol ); + emit dataChanged( pidx, pidx ); + } - if ( parent && parent->hasType("NiBlock") ) - emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); } } if ( state == Default ) { - // Reassess conditions for reliant data only when modifying value - invalidateDependentConditions( item ); - // update original index - emit dataChanged( index, index ); + onItemValueChange( item ); } return true; @@ -1663,24 +1467,28 @@ void NifModel::reset() endResetModel(); } -bool NifModel::removeRows( int row, int count, const QModelIndex & parent ) +bool NifModel::removeRows( int iStart, int count, const QModelIndex & parent ) { - NifItem * item = static_cast( parent.internalPointer() ); - - if ( !( parent.isValid() && parent.model() == this && item ) ) + NifItem * item = getItem( parent ); + if ( !item ) return false; - if ( row >= 0 && ( (row + count) <= item->childCount() ) ) { - bool link = false; + int iEnd = iStart + count; + if ( iStart >= 0 && iEnd <= item->childCount() && iEnd > iStart ) { + bool hasLinks = false; - for ( int r = row; r < row + count; r++ ) - link |= itemIsLink( item->child( r ) ); + for ( int r = iStart; r < iEnd; r++ ) { + if ( isLink( item->child(r) ) ) { + hasLinks = true; + break; + } + } - beginRemoveRows( parent, row, row + count ); - item->removeChildren( row, count ); + beginRemoveRows( parent, iStart, iEnd - 1 ); + item->removeChildren( iStart, count ); endRemoveRows(); - if ( link ) { + if ( hasLinks ) { updateLinks(); updateFooter(); emit linksChanged(); @@ -1694,12 +1502,12 @@ bool NifModel::removeRows( int row, int count, const QModelIndex & parent ) QModelIndex NifModel::buddy( const QModelIndex & index ) const { - NifItem * item = static_cast(index.internalPointer()); + const NifItem * item = getItem( index ); if ( !item ) return QModelIndex(); - QModelIndex buddy; if ( index.column() == ValueCol && item->parent() == root && item->hasType("NiBlock") ) { + QModelIndex buddy; if ( item->hasName("NiSourceTexture") || item->hasName("NiImage") ) { buddy = getIndex( index, "File Name" ); @@ -1717,6 +1525,8 @@ QModelIndex NifModel::buddy( const QModelIndex & index ) const } else if ( index.column() == ValueCol && item->parent() != root ) { if ( item->hasType("ControlledBlock") && item->hasName("Controlled Blocks") ) { + QModelIndex buddy; + if ( version >= 0x14010003 ) { buddy = getIndex( index, "Node Name" ); } else if ( version <= 0x14000005 ) { @@ -2088,9 +1898,8 @@ bool NifModel::save( QIODevice & device ) const bool NifModel::loadIndex( QIODevice & device, const QModelIndex & index ) { - NifItem * item = static_cast( index.internalPointer() ); - - if ( item && index.isValid() && index.model() == this ) { + NifItem * item = getItem( index ); + if ( item ) { NifIStream stream( this, &device ); bool ok = loadItem( item, stream ); updateLinks(); @@ -2105,9 +1914,8 @@ bool NifModel::loadIndex( QIODevice & device, const QModelIndex & index ) bool NifModel::loadAndMapLinks( QIODevice & device, const QModelIndex & index, const QMap & map ) { - NifItem * item = static_cast( index.internalPointer() ); - - if ( item && index.isValid() && index.model() == this ) { + NifItem * item = getItem( index ); + if ( item ) { NifIStream stream( this, &device ); bool ok = loadItem( item, stream ); mapLinks( item, map ); @@ -2167,7 +1975,7 @@ bool NifModel::earlyRejection( const QString & filepath, const QString & blockId if ( blockId.isEmpty() == true || v < 0x0A000100 ) { blk_match = true; } else { - const auto & types = nif.getArray( nif.getHeader(), "Block Types" ); + const auto & types = nif.getArray( nif.getHeaderItem(), "Block Types" ); for ( const QString& s : types ) { if ( inherits( s, blockId ) ) { blk_match = true; @@ -2181,43 +1989,42 @@ bool NifModel::earlyRejection( const QString & filepath, const QString & blockId bool NifModel::saveIndex( QIODevice & device, const QModelIndex & index ) const { - NifOStream stream( this, &device ); - NifItem * item = static_cast( index.internalPointer() ); - return ( item && index.isValid() && index.model() == this && saveItem( item, stream ) ); + const NifItem * item = getItem( index ); + if ( item ) { + NifOStream stream( this, &device ); + return saveItem( item, stream ); + } + return false; } int NifModel::fileOffset( const QModelIndex & index ) const { - NifSStream stream( this ); - NifItem * target = static_cast( index.internalPointer() ); - - if ( target && index.isValid() && index.model() == this ) { + const NifItem * target = getItem( index ); + if ( target ) { + NifSStream stream( this ); int ofs = 0; for ( int c = 0; c < root->childCount(); c++ ) { - if ( c > 0 && c <= getBlockCount() ) { + const NifItem * block = root->child( c ); + + if ( c >= firstBlockRow() && c <= lastBlockRow() ) { if ( version > 0x0a000000 ) { - if ( version < 0x0a020000 ) { + if ( version < 0x0a020000 ) ofs += 4; - } } else { if ( version < 0x0303000d ) { - if ( rootLinks.contains( c - 1 ) ) { - QString string = "Top Level Object"; - ofs += 4 + string.length(); - } + if ( rootLinks.contains( c - 1 ) ) + ofs += 4 + QLatin1String( "Top Level Object" ).size(); } - QString string = itemName( this->NifModel::index( c, 0 ) ); - ofs += 4 + string.length(); + ofs += 4 + ( block ? block->name().length() : 0); - if ( version < 0x0303000d ) { + if ( version < 0x0303000d ) ofs += 4; - } } } - if ( fileOffset( root->child( c ), target, stream, ofs ) ) + if ( fileOffset( block, target, stream, ofs ) ) return ofs; } } @@ -2225,28 +2032,19 @@ int NifModel::fileOffset( const QModelIndex & index ) const return -1; } -int NifModel::blockSize( const QModelIndex & index ) const -{ - NifSStream stream( this ); - return blockSize( static_cast( index.internalPointer() ), stream ); -} - -int NifModel::blockSize( NifItem * parent ) const +int NifModel::blockSize( const NifItem * item ) const { NifSStream stream( this ); - return blockSize( parent, stream ); + return blockSize( item, stream ); } -int NifModel::blockSize( NifItem * parent, NifSStream & stream ) const +int NifModel::blockSize( const NifItem * item, NifSStream & stream ) const { - int size = 0; - - if ( !parent ) + if ( !item ) return 0; - for ( int row = 0; row < parent->childCount(); row++ ) { - NifItem * child = parent->child( row ); - + int size = 0; + for ( auto child : item->childIter() ) { if ( child->isAbstract() ) { //qDebug() << "Not counting abstract item " << child->name(); continue; @@ -2254,11 +2052,15 @@ int NifModel::blockSize( NifItem * parent, NifSStream & stream ) const if ( evalCondition( child ) ) { if ( isArray( child ) || !child->arr2().isEmpty() || child->childCount() > 0 ) { - if ( isArray( child ) && child->childCount() != getArraySize( child ) ) { - if ( child->isBinary() ) { - // special byte - } else { - logWarning(tr("Block %1 %2 array size mismatch").arg(getBlockNumber(parent)).arg(child->name())); + if ( isArray( child ) && !child->isBinary() ) { + int nRealSize = child->childCount(); + int nCalcSize = evalArraySize( child ); + if ( nRealSize != nCalcSize ) { + reportError( + child, + "blockSize", + tr( "The array's size (%1) does not match its calculated size (%2)." ).arg( nRealSize ).arg( nCalcSize ) + ); } } @@ -2287,9 +2089,8 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) QString name; - for ( auto child : parent->children() ) { - if ( !child->isConditionless() ) - child->invalidateCondition(); + for ( auto child : parent->childIter() ) { + child->invalidateCondition(); if ( child->isAbstract() ) { //qDebug() << "Not loading abstract item " << child->name(); @@ -2298,7 +2099,9 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) if ( evalCondition( child ) ) { if ( isArray( child ) ) { - if ( !updateArrayItem( child ) || !loadItem( child, stream ) ) + if ( !updateArraySize( child ) ) + return false; + if ( !loadItem( child, stream ) ) return false; } else if ( child->childCount() > 0 ) { if ( !loadItem( child, stream ) ) @@ -2310,12 +2113,9 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) } if ( stopRead && child->hasName("Name") ) { - auto idx = child->value().get(); - if ( idx != -1 ) { - NifItem * header = getHeaderItem(); - QModelIndex stringIndex = createIndex( header->row(), 0, header ); - name = get( this->index( idx, 0, getIndex( stringIndex, "Strings" ) ) ); - } + auto iStr = child->get(); + if ( iStr >= 0 ) + name = get( getItem( getHeaderItem(), "Strings" ), iStr ); } if ( stopRead && child->hasName("Controller") && !name.isEmpty() ) @@ -2340,19 +2140,19 @@ bool NifModel::loadHeader( NifItem * header, NifIStream & stream ) bsVersion = 0; set( header, "User Version", 0 ); - set( getItem(header, "BS Header"), "BS Version", 0 ); - invalidateConditions(header, true); + set( header, "BS Header\\BS Version", 0 ); + invalidateItemConditions( header ); bool result = loadItem(header, stream); cacheBSVersion( header ); return result; } -bool NifModel::saveItem( NifItem * parent, NifOStream & stream ) const +bool NifModel::saveItem( const NifItem * parent, NifOStream & stream ) const { if ( !parent ) return false; - for ( auto child : parent->children() ) { + for ( auto child : parent->childIter() ) { if ( child->isAbstract() ) { qDebug() << "Not saving abstract item " << child->name(); continue; @@ -2360,7 +2160,7 @@ bool NifModel::saveItem( NifItem * parent, NifOStream & stream ) const if ( evalCondition( child ) ) { if ( isArray( child ) || !child->arr2().isEmpty() || child->childCount() > 0 ) { - if ( isArray( child ) && child->childCount() != getArraySize( child ) ) { + if ( isArray( child ) && child->childCount() != evalArraySize( child ) ) { if ( child->isBinary() ) { // special byte } else { @@ -2380,12 +2180,14 @@ bool NifModel::saveItem( NifItem * parent, NifOStream & stream ) const return true; } -bool NifModel::fileOffset( NifItem * parent, NifItem * target, NifSStream & stream, int & ofs ) const +bool NifModel::fileOffset( const NifItem * parent, const NifItem * target, NifSStream & stream, int & ofs ) const { + if ( !parent ) + return false; if ( parent == target ) return true; - for ( auto child : parent->children() ) { + for ( auto child : parent->childIter() ) { if ( child == target ) return true; @@ -2404,80 +2206,56 @@ bool NifModel::fileOffset( NifItem * parent, NifItem * target, NifSStream & stre NifItem * NifModel::insertBranch( NifItem * parentItem, const NifData & data, int at ) { - NifItem * item = parentItem->insertChild( data, at ); - item->value().changeType( NifValue::tNone ); - return item; + return parentItem->insertChild( data, NifValue::tNone, at ); } -bool NifModel::evalCondition( NifItem * item, bool chkParents ) const +const NifItem * NifModel::getConditionCacheItem( const NifItem * item ) const { - if ( !evalVersion( item, chkParents ) ) { - // Version is global and cond is not so set false and abort - item->setCondition( false ); - return false; - } - - if ( item->isConditionValid() ) - return item->condition(); - - // Early reject - if ( item->isConditionless() ) { - item->setCondition( true ); - return item->condition(); - } - - // Store row conditions for certain compound arrays - NifItem * parent = item->parent(); - if ( isFixedCompound( parent->type() ) ) { - NifItem * arrRoot = parent->parent(); - if ( isArray( arrRoot ) ) { - // This is a compound in the compound array - if ( parent->row() == 0 ) { - // This is the first compound in the compound array - if ( item->row() == 0 ) { - // First row of first compound, initialize condition cache - arrRoot->resetArrayConditions(); - } - // Cache condition on array root - arrRoot->updateArrayCondition( BaseModel::evalCondition( item ), item->row() ); - } - auto arrCond = arrRoot->arrayConditions(); - auto itemRow = item->row(); - if ( arrCond.count() > itemRow ) - item->setCondition( arrCond.at( itemRow ) ); - return item->condition(); + // For an array of BSVertexData/BSVertexDataSSE structures ("fixed compounds", see "Vertex Data" in BSTriShape) + // we use the first structure in the array as a cache of condition values for all the structure fields. + const NifItem * compoundStruct = item->parent(); + if ( compoundStruct ) { + const NifItem * compoundArray = compoundStruct->parent(); + if ( isArray(compoundArray) && compoundStruct->row() > 0 && isFixedCompound( compoundStruct->type() ) ) { + const NifItem * refStruct = compoundArray->child( 0 ); + if ( !refStruct ) // Just in case... + return nullptr; + return refStruct->child( item->row() ); } } - item->setCondition( BaseModel::evalCondition( item, chkParents ) ); - - return item->condition(); + return item; } -void NifModel::invalidateConditions( NifItem * item, bool refresh ) +bool NifModel::evalVersionImpl( const NifItem * item ) const { - for ( NifItem * c : item->children() ) { - c->invalidateCondition(); - c->invalidateVersionCondition(); - if ( refresh ) - c->setCondition( BaseModel::evalCondition( c ) ); + // Early reject for ver1/ver2 + if ( !item->evalVersion(version) ) + return false; - if ( isArray( c ) || c->childCount() > 0 ) { - invalidateConditions( c ); - } + // If there is a vercond, evaluate it + if ( !item->vercond().isEmpty() ) { + const NifItem * refItem = getConditionCacheItem( item ); + if ( refItem != item ) + return evalVersion( refItem ); + + NifModelEval functor( this, getHeaderItem() ); + if ( !item->verexpr().evaluateBool(functor) ) + return false; } - // Reset conditions cached on array root for fixed condition compounds - if ( isArray( item ) && isFixedCompound( item->type() ) ) { - item->resetArrayConditions(); - } + return true; } -void NifModel::invalidateConditions( const QModelIndex & index, bool refresh ) +bool NifModel::evalConditionImpl( const NifItem * item ) const { - auto item = static_cast(index.internalPointer()); - if ( item ) - invalidateConditions( item, refresh ); + if ( !item->cond().isEmpty() ) { + const NifItem * refItem = getConditionCacheItem( item ); + if ( refItem != item ) + return evalCondition( refItem ); + } + + return BaseModel::evalConditionImpl( item ); } void NifModel::invalidateDependentConditions( NifItem * item ) @@ -2486,31 +2264,38 @@ void NifModel::invalidateDependentConditions( NifItem * item ) return; NifItem * p = item->parent(); - if ( !p || p == root ) + if ( !p || p == root || isArray(p) ) return; - QString name = item->name(); - for ( int i = item->row(); i < p->childCount(); i++ ) { - auto c = p->children().at( i ); + const QString & name = item->name(); + for ( int i = item->row() + 1; i < p->childCount(); i++ ) { + auto c = p->child( i ); + if ( !c ) // Just in case... + continue; + // String check for Name in cond or arg // Note: May cause some false positives but this is OK - if ( c->cond().contains( name ) ) { + if ( c->cond().contains(name) + || c->arg().contains(name) + || ( c->childCount() > 0 && !isArray(c) ) // If it has children but is not an array, let's reset conditions just to be safe. + ) { c->invalidateCondition(); - c->setCondition( BaseModel::evalCondition( c ) ); } - - if ( (c->cond().contains( name ) || c->arg().contains( name )) && c->childCount() > 0 ) - invalidateConditions( c, true ); } } -void NifModel::invalidateDependentConditions( const QModelIndex & index ) +void NifModel::invalidateHeaderConditions() { - auto item = static_cast(index.internalPointer()); - if ( item ) - invalidateDependentConditions( item ); + invalidateItemConditions( getHeaderItem() ); } +void NifModel::invalidateItemConditions( NifItem * item ) +{ + if ( item ) { + item->invalidateVersionCondition(); + item->invalidateCondition(); + } +} /* * link functions @@ -2576,9 +2361,9 @@ void NifModel::updateLinks( int block, NifItem * parent ) continue; } - int i = c->value().toLink(); + int i = c->valueToLink(); if ( i >= 0 ) { - if ( c->value().type() == NifValue::tUpLink ) { + if ( c->valueType() == NifValue::tUpLink ) { if ( !parentLinks[block].contains( i ) ) parentLinks[block].append( i ); } else { @@ -2620,13 +2405,13 @@ void NifModel::adjustLinks( NifItem * parent, int block, int delta ) for ( auto child : parent->children() ) adjustLinks( child, block, delta ); } else { - int l = parent->value().toLink(); + int l = parent->valueToLink(); if ( l >= 0 && ( ( delta != 0 && l >= block ) || l == block ) ) { if ( delta == 0 ) - parent->value().setLink( -1 ); + parent->valueFromLink( -1 ); else - parent->value().setLink( l + delta ); + parent->valueFromLink( l + delta ); } } } @@ -2640,165 +2425,77 @@ void NifModel::mapLinks( NifItem * parent, const QMap & map ) for ( auto child : parent->children() ) mapLinks( child, map ); } else { - int l = parent->value().toLink(); + int l = parent->valueToLink(); if ( l >= 0 ) { if ( map.contains( l ) ) - parent->value().setLink( map[ l ] ); + parent->valueFromLink( map[ l ] ); } } } -qint32 NifModel::getLink( const QModelIndex & index ) const +bool NifModel::setLink( NifItem * item, qint32 link ) { - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return -1; - - return item->value().toLink(); -} - -qint32 NifModel::getLink( const QModelIndex & parent, const QString & name ) const -{ - NifItem * parentItem = static_cast( parent.internalPointer() ); - - if ( !( parent.isValid() && parentItem && parent.model() == this ) ) - return -1; - - NifItem * item = getItem( parentItem, name ); - - if ( item ) - return item->value().toLink(); + if ( NifItem::valueFromLink( item, link) ) { + onItemValueChange( item ); + return true; + } - return -1; + return false; } -QVector NifModel::getLinkArray( const QModelIndex & iArray ) const +QVector NifModel::getLinkArray( const NifItem * arrayRootItem ) const { QVector links; - NifItem * item = static_cast( iArray.internalPointer() ); - if ( isArray( iArray ) && item && iArray.model() == this ) { - for ( auto child : item->children() ) { - if ( itemIsLink( child ) ) { - links.append( child->value().toLink() ); - } else { - links.clear(); - break; - } + if ( isArray( arrayRootItem ) ) { + int nLinks = arrayRootItem->childCount(); + if ( nLinks > 0 ) { + links.reserve( nLinks ); + for ( auto child : arrayRootItem->childIter() ) + links.append( child->valueToLink() ); } + } else { + if ( arrayRootItem ) + reportError( arrayRootItem, "getLinkArray", "The item is not an array." ); } return links; } -QVector NifModel::getLinkArray( const QModelIndex & parent, const QString & name ) const +bool NifModel::setLinkArray( NifItem * arrayRootItem, const QVector & links ) { - return getLinkArray( getIndex( parent, name ) ); -} - -bool NifModel::setLink( const QModelIndex & parent, const QString & name, qint32 l ) -{ - NifItem * parentItem = static_cast( parent.internalPointer() ); - - if ( !( parent.isValid() && parentItem && parent.model() == this ) ) + if ( !isArray(arrayRootItem) ) { + if ( arrayRootItem ) + reportError( arrayRootItem, "setLinkArray", "The item is not an array." ); return false; - - NifItem * item = getItem( parentItem, name ); - - if ( item && item->value().setLink( l ) ) { - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - NifItem * parent = item; - - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - - if ( parent != getFooterItem() ) { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - - return true; } - return false; -} - -bool NifModel::setLink( const QModelIndex & index, qint32 l ) -{ - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) + int nLinks = arrayRootItem->childCount(); + if ( links.count() != nLinks ) { + reportError( + arrayRootItem, + "setLinkArray", + tr( "The input QVector's size (%1) does not match the array's size (%2)." ).arg( links.count() ).arg( nLinks ) + ); return false; - - if ( item && item->value().setLink( l ) ) { - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - NifItem * parent = item; - - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - - if ( parent != getFooterItem() ) { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - - return true; } + if ( nLinks == 0 ) + return true; - return false; -} - -bool NifModel::setLinkArray( const QModelIndex & iArray, const QVector & links ) -{ - NifItem * item = static_cast( iArray.internalPointer() ); - - if ( isArray( iArray ) && item && iArray.model() == this ) { - bool ret = true; - - for ( int c = 0; c < item->childCount() && c < links.count(); c++ ) { - ret &= item->child( c )->value().setLink( links[c] ); - } - - ret &= item->childCount() == links.count(); - int x = item->childCount() - 1; - - if ( x >= 0 ) - emit dataChanged( createIndex( 0, ValueCol, item->child( 0 ) ), createIndex( x, ValueCol, item->child( x ) ) ); - - NifItem * parent = item; + for ( int i = 0; i < nLinks; i++ ) + NifItem::valueFromLink( arrayRootItem->child( i ), links.at( i ) ); - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); + onArrayValuesChange( arrayRootItem ); - if ( parent != getFooterItem() ) { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - - return ret; + auto block = getTopItem( arrayRootItem ); + if ( block && block != getFooterItem() ) { + updateLinks(); + updateFooter(); + emit linksChanged(); } - return false; -} - -bool NifModel::setLinkArray( const QModelIndex & parent, const QString & name, const QVector & links ) -{ - return setLinkArray( getIndex( parent, name ), links ); -} - -bool NifModel::isLink( const QModelIndex & index, bool * isChildLink ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return false; - - return itemIsLink( item, isChildLink ); + return true; } int NifModel::getParent( int block ) const @@ -2820,154 +2517,150 @@ int NifModel::getParent( const QModelIndex & index ) const return getParent( getBlockNumber( index ) ); } -QString NifModel::string( const QModelIndex & index, bool extraInfo ) const +QString NifModel::resolveString( const NifItem * item ) const { - NifValue v = getValue( index ); + if ( !item ) + return QString(); - if ( v.type() == NifValue::tSizedString ) - return BaseModel::get( index ); + if ( item->valueIsString() ) + return item->get(); if ( getVersionNumber() >= 0x14010003 ) { - QModelIndex iIndex; - int idx = -1; - - if ( v.type() == NifValue::tStringIndex ) - idx = get( index ); - else if ( !v.isValid() ) - idx = get( getIndex( index, "Index" ) ); + int iStrIndex = -1; + auto vt = item->valueType(); + if ( vt == NifValue::tStringIndex ) + iStrIndex = item->get(); + else if ( vt == NifValue::tNone ) { + auto itemIndex = getItem( item, "Index" ); + if ( itemIndex ) + iStrIndex = itemIndex->get(); + else + reportError( item, "resolveString", "Could not find \"Index\" subitem." ); + } else { + reportError( item, "resolveString", tr( "Unsupported value type (%1)." ).arg( int(vt) ) ); + } - if ( idx < 0 ) + if ( iStrIndex < 0 ) return QString(); - NifItem * header = this->getHeaderItem(); - QModelIndex stringIndex = createIndex( header->row(), 0, header ); - - if ( !stringIndex.isValid() ) - return tr( "%1 - " ).arg( idx ); - - QString string = BaseModel::get( this->index( idx, 0, getIndex( stringIndex, "Strings" ) ) ); - - if ( extraInfo ) - string = QString( "%2 [%1]" ).arg( idx ).arg( string ); - - return string; + auto headerStrings = getItem( getHeaderItem(), "Strings" ); + if ( !headerStrings || iStrIndex >= headerStrings->childCount() ) + return QString(); + return BaseModel::get( headerStrings, iStrIndex ); } - if ( v.type() != NifValue::tNone ) - return BaseModel::get( index ); + if ( item->valueType() == NifValue::tNone ) { + auto itemString = getItem( item, "String" ); + if ( itemString ) + return itemString->get(); - QModelIndex iIndex = getIndex( index, "String" ); - if ( iIndex.isValid() ) - return BaseModel::get( iIndex ); + reportError( item, "resolveString", "Could not find \"String\" subitem."); + } else { + item->value().reportConvertToError( this, item, "a QString"); + } return QString(); } -QString NifModel::string( const QModelIndex & index, const QString & name, bool extraInfo ) const -{ - return string( getIndex( index, name ), extraInfo ); -} - -bool NifModel::assignString( const QModelIndex & index, const QString & string, bool replace ) -{ - NifItem * item = static_cast( index.internalPointer() ); - - if ( !( index.isValid() && item && index.model() == this ) ) - return false; - - return assignString( item, string, replace ); -} - bool NifModel::assignString( NifItem * item, const QString & string, bool replace ) { - NifValue & v = item->value(); + if ( !item ) + return false; if ( getVersionNumber() >= 0x14010003 ) { - NifItem * pItem; - int idx = -1; + NifItem * itemIndex = nullptr; + int iOldStrIndex = -1; - switch ( v.type() ) { + switch ( item->valueType() ) { case NifValue::tNone: - pItem = getItem( item, "Index" ); - if ( !pItem ) + itemIndex = getItem( item, "Index" ); + if ( !itemIndex ) { + reportError( item, "assignString", "Could not find \"Index\" subitem." ); return false; - - idx = get( pItem ); + } + iOldStrIndex = itemIndex->get(); break; case NifValue::tStringIndex: - idx = get( pItem = item ); + itemIndex = item; + iOldStrIndex = itemIndex->get(); break; case NifValue::tSizedString: if ( item->hasType("string") ) { - pItem = item; - idx = -1; + itemIndex = item; + iOldStrIndex = -1; break; } // fall through default: return BaseModel::set( item, string ); } - QModelIndex header = getHeader(); - int nstrings = get( header, "Num Strings" ); + NifItem * header = getHeaderItem(); + NifItem * headerStrings = getItem( header, "Strings", true ); + if ( !headerStrings ) + return false; + int nHeaderStrings = headerStrings->childCount(); if ( string.isEmpty() ) { - if ( replace && idx >= 0 && idx < nstrings ) { + if ( replace && iOldStrIndex >= 0 && iOldStrIndex < nHeaderStrings) { // TODO: Can we remove the string safely here? } - v.changeType( NifValue::tStringIndex ); - return set( item, 0xffffffff ); + itemIndex->valueChangeType( NifValue::tStringIndex ); + return set( itemIndex, 0xffffffff ); } - QModelIndex iArray = getIndex( header, "Strings" ); - // Simply replace the string - if ( replace && idx >= 0 && idx < nstrings ) { - return BaseModel::set( iArray.child( idx, 0 ), string ); + if ( replace && iOldStrIndex >= 0 && iOldStrIndex < nHeaderStrings ) { + return BaseModel::set( headerStrings, iOldStrIndex, string ); } - QVector stringVector = getArray( iArray ); - auto newidx = stringVector.indexOf( string ); - - // Already exists. Just update the Index - if ( newidx >= 0 && newidx < stringVector.size() ) { - v.changeType( NifValue::tStringIndex ); - return set( pItem, newidx ); + int iNewStrIndex = -1; + for ( int i = 0; i < nHeaderStrings; i++ ) { + const NifItem * c = headerStrings->child( i ); + if ( c && c->get() == string ) { + iNewStrIndex = i; + break; + } + } + if ( iNewStrIndex < 0 ) { + // Append string to end of the header string list. + iNewStrIndex = nHeaderStrings; + set( header, "Num Strings", nHeaderStrings + 1 ); + updateArraySize( headerStrings ); + BaseModel::set( headerStrings, nHeaderStrings, string ); } - // Append string to end of list - set( header, "Num Strings", nstrings + 1 ); - updateArray( header, "Strings" ); - BaseModel::set( iArray.child( nstrings, 0 ), string ); - - v.changeType( NifValue::tStringIndex ); - return set( pItem, nstrings ); + itemIndex->valueChangeType( NifValue::tStringIndex ); + return set( itemIndex, iNewStrIndex ); } // endif getVersionNumber() >= 0x14010003 // Handle the older simpler strings - if ( v.type() == NifValue::tNone ) { - return BaseModel::set( getItem( item, "String" ), string ); - } else if ( v.type() == NifValue::tStringIndex ) { + if ( item->valueType() == NifValue::tNone ) { + NifItem * itemString = getItem( item, "String" ); + if ( !itemString ) { + reportError( item, "assignString", "Could not find \"String\" subitem."); + return false; + } + return BaseModel::set( itemString, string ); + + } else if ( item->valueType() == NifValue::tStringIndex ) { NifValue v( NifValue::tString ); - v.set( string ); + v.set( string, this, item ); return setItemValue( item, v ); - //return BaseModel::set( index, string ); } return BaseModel::set( item, string ); } -bool NifModel::assignString( const QModelIndex & index, const QString & name, const QString & string, bool replace ) -{ - return assignString( getIndex( index, name ), string, replace ); -} - // convert a block from one type to another void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & index ) { - QString btype = getBlockName( index ); + NifItem * branch = getItem( index ); + if ( !branch ) + return; + const QString & btype = branch->name(); if ( btype == identifier ) return; @@ -2976,11 +2669,10 @@ void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & i return; } - NifItem * branch = static_cast( index.internalPointer() ); NifBlockPtr srcBlock = blocks.value( btype ); NifBlockPtr dstBlock = blocks.value( identifier ); - if ( srcBlock && dstBlock && branch ) { + if ( srcBlock && dstBlock ) { branch->setName( identifier ); if ( inherits( btype, identifier ) ) { @@ -3074,11 +2766,36 @@ void NifModel::updateModel( UpdateType value ) emit linksChanged(); } -void NifModel::cacheBSVersion( NifItem * headerItem ) +void NifModel::cacheBSVersion( const NifItem * headerItem ) { - bsVersion = get( getItem( headerItem, "BS Header" ), "BS Version" ); + bsVersion = get( headerItem, "BS Header\\BS Version" ); } +QString NifModel::topItemRepr( const NifItem * item ) const +{ + int iRow = item->row(); + if ( iRow >= firstBlockRow() && iRow <= lastBlockRow() ) + return QString("%2 [%1]").arg( iRow - 1 ).arg( item->name() ); + + return item->name(); +} + +void NifModel::onItemValueChange( NifItem * item ) +{ + invalidateDependentConditions( item ); + BaseModel::onItemValueChange( item ); + + if ( item->valueIsLink() ) { + auto block = getTopItem( item ); + if ( block && block != getFooterItem() ) { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + } +} + + /* * NifModelEval */ @@ -3093,14 +2810,13 @@ QVariant NifModelEval::operator()( const QVariant & v ) const { if ( v.type() == QVariant::String ) { QString left = v.toString(); - NifItem * i = const_cast(item); - i = model->getItem( i, left ); - - if ( i ) { - if ( i->value().isCount() ) - return QVariant( i->value().toCount() ); - else if ( i->value().isFileVersion() ) - return QVariant( i->value().toFileVersion() ); + const NifItem * itemLeft = model->getItem( item, left, true ); + + if ( itemLeft ) { + if ( itemLeft->valueIsCount() ) + return QVariant( itemLeft->valueToCount() ); + else if ( itemLeft->valueIsFileVersion() ) + return QVariant( itemLeft->valueToFileVersion() ); } return QVariant( 0 ); diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index e10a56d90..4b620fbfb 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -99,12 +99,6 @@ class NifModel final : public BaseModel QString getVersion() const override final { return version2string( version ); } quint32 getVersionNumber() const override final { return version; } - template T get( const QModelIndex & index ) const; - template bool set( const QModelIndex & index, const T & d ); - - template T get( const QModelIndex & parent, const QString & name ) const; - template bool set( const QModelIndex & parent, const QString & name, const T & v ); - // end BaseModel //! Load from QIODevice and index @@ -114,14 +108,10 @@ class NifModel final : public BaseModel //! Resets the model to its original state in any attached views. void reset(); - //! Evaluate the condition and version expressions for a NifItem - bool evalCondition( NifItem * item, bool chkParents = false ) const; - //! Invalidate the conditions of the item and its children recursively - void invalidateConditions( NifItem * item, bool refresh = true ); - void invalidateConditions( const QModelIndex & index, bool refresh = true ); //! Invalidate only the conditions of the items dependent on this item void invalidateDependentConditions( NifItem * item ); - void invalidateDependentConditions( const QModelIndex & index ); + //! Reset all cached conditions of the header + void invalidateHeaderConditions(); //! Loads a model and maps links bool loadAndMapLinks( QIODevice & device, const QModelIndex &, const QMap & map ); @@ -131,12 +121,10 @@ class NifModel final : public BaseModel //! Returns the the estimated file offset of the model index int fileOffset( const QModelIndex & ) const; - //! Returns the estimated file size of the model index - int blockSize( const QModelIndex & ) const; //! Returns the estimated file size of the item - int blockSize( NifItem * parent ) const; + int blockSize( const NifItem * item ) const; //! Returns the estimated file size of the stream - int blockSize( NifItem * parent, NifSStream & stream ) const; + int blockSize( const NifItem * item, NifSStream & stream ) const; /*! Checks if the specified file contains the specified block ID in its header and is of the specified version * @@ -148,8 +136,12 @@ class NifModel final : public BaseModel */ bool earlyRejection( const QString & filepath, const QString & blockId, quint32 version ); + const NifItem * getHeaderItem() const; + NifItem * getHeaderItem(); //! Returns the model index of the NiHeader - QModelIndex getHeader() const; + // TODO(Gavrant): try replace it with getHeaderItem + QModelIndex getHeaderIndex() const; + //! Updates the header infos ( num blocks etc. ) void updateHeader(); //! Extracts the 0x01 separated args from NiDataStream. NiDataStream is the only known block to use RTTI args. @@ -159,74 +151,15 @@ class NifModel final : public BaseModel //! Creates the 0x01 separated args for NiDataStream. NiDataStream is the only known block to use RTTI args. QString createRTTIName( const NifItem * block ) const; - //! Returns the model index of the NiFooter - QModelIndex getFooter() const; + const NifItem * getFooterItem() const; + NifItem * getFooterItem(); + //! Updates the footer info (num root links etc. ) void updateFooter(); //! Set delayed updating of model links bool holdUpdates( bool value ); - //! Insert or append ( row == -1 ) a new NiBlock - QModelIndex insertNiBlock( const QString & identifier, int row = -1 ); - //! Remove a block from the list - void removeNiBlock( int blocknum ); - //! Move a block in the list - void moveNiBlock( int src, int dst ); - //! Return the block name - QString getBlockName( const QModelIndex & ) const; - //! Return the block type - QString getBlockType( const QModelIndex & ) const; - //! Return the block number - int getBlockNumber( const QModelIndex & ) const; - - /*! Get the NiBlock at a given index - * - * @param idx The model index to get the parent of - * @param name Optional: the type to check for - */ - QModelIndex getBlock( const QModelIndex & idx, const QString & name = QString() ) const; - - /*! Get the NiBlock at a given index - * - * @param x The integer index of the block - * @param name Optional: the type to check for - */ - QModelIndex getBlock( int x, const QString & name = QString() ) const; - - //! Returns the parent block or header - QModelIndex getBlockOrHeader( const QModelIndex & ) const; - - //! Get the number of NiBlocks - int getBlockCount() const; - - /*! Check if a given index is a NiBlock - * - * @param index The index to check - * @param name Optional: the type to check for - */ - bool isNiBlock( const QModelIndex & index, const QString & name = QString() ) const; - - /*! Check if a given index is a NiBlock - * - * @param index The index to check - * @param names A list of names to check - */ - bool isNiBlock( const QModelIndex & index, const QStringList & names ) const; - - //! Returns a list with all known NiXXX ids () - static QStringList allNiBlocks(); - //! Determine if a value is a NiBlock identifier (). - static bool isNiBlock( const QString & name ); - //! Reorders the blocks according to a list of new block numbers - void reorderBlocks( const QVector & order ); - //! Moves all niblocks from this nif to another nif, returns a map which maps old block numbers to new block numbers - QMap moveAllNiBlocks( NifModel * targetnif, bool update = true ); - //! Convert a block from one type to another - void convertNiBlock( const QString & identifier, const QModelIndex & index ); - - void insertType( const QModelIndex & parent, const NifData & data, int atRow ); - QList getRootLinks() const; QList getChildLinks( int block ) const; QList getParentLinks( int block ) const; @@ -241,23 +174,10 @@ class NifModel final : public BaseModel */ int getParent( const QModelIndex & index ) const; - //! Is it a child or parent link? - bool isLink( const QModelIndex & index, bool * ischildLink = 0 ) const; - //! Return a block number if the index is a valid link - qint32 getLink( const QModelIndex & index ) const; - - /*! Get the block number of a link - * - * @param parent The parent of the link - * @param name The name of the link - */ - int getLink( const QModelIndex & parent, const QString & name ) const; - QVector getLinkArray( const QModelIndex & array ) const; - QVector getLinkArray( const QModelIndex & parent, const QString & name ) const; - bool setLink( const QModelIndex & index, qint32 l ); - bool setLink( const QModelIndex & parent, const QString & name, qint32 l ); - bool setLinkArray( const QModelIndex & array, const QVector & links ); - bool setLinkArray( const QModelIndex & parent, const QString & name, const QVector & links ); + //! Is an item's value a child or parent link? + bool isLink( const NifItem * item ) const; + //! Is a model index' value a child or parent link? + bool isLink( const QModelIndex & index ) const; void mapLinks( const QMap & map ); @@ -269,13 +189,6 @@ class NifModel final : public BaseModel static bool isAncestor( const QString & name ); //! Is name a NiBlock identifier ( or )? bool isAncestorOrNiBlock( const QString & name ) const override final; - //! Returns true if name inherits ancestor. - bool inherits( const QString & name, const QString & ancestor ) const override final; - //! Returns true if name inherits any ancestors in list. - bool inherits( const QString & name, const QStringList & ancestors ) const; - // returns true if the block containing index inherits ancestor - bool inherits( const QModelIndex & index, const QString & ancestor ) const; - bool inherits( const QModelIndex & index, const QStringList & ancestors ) const; //! Is this version supported? static bool isVersionSupported( quint32 ); @@ -284,23 +197,435 @@ class NifModel final : public BaseModel static quint32 version2number( const QString & ); //! Check whether the current NIF file version lies in the range [since, until] - bool checkVersion( quint32 since, quint32 until ) const; + bool checkVersion( quint32 since, quint32 until = 0 ) const; - quint32 getUserVersion() const { return get( getHeader(), "User Version" ); } + quint32 getUserVersion() const { return get( getHeaderItem(), "User Version" ); } quint32 getBSVersion() const { return bsVersion; } - QString string( const QModelIndex & index, bool extraInfo = false ) const; - QString string( const QModelIndex & index, const QString & name, bool extraInfo = false ) const; - - bool assignString( const QModelIndex & index, const QString & string, bool replace = false ); - bool assignString( const QModelIndex & index, const QString & name, const QString & string, bool replace = false ); - //! Create and return delegate for SpellBook static QAbstractItemDelegate * createDelegate( QObject * parent, SpellBookPtr book ); //! Undo Stack for changes to NifModel QUndoStack * undoStack = nullptr; + // Basic block functions +protected: + constexpr int firstBlockRow() const; + int lastBlockRow() const; + +public: + //! Get the number of NiBlocks + int getBlockCount() const; + + //! Get the numerical index (or link) of the block an item belongs to. + // Return -1 if the item is the root or header or footer or null. + int getBlockNumber( const NifItem * item ) const; + //! Get the numerical index (or link) of the block an item belongs to. + // Return -1 if the item is the root or header or footer or null. + int getBlockNumber( const QModelIndex & index ) const; + + //! Insert or append ( row == -1 ) a new NiBlock + QModelIndex insertNiBlock( const QString & identifier, int row = -1 ); + //! Remove a block from the list + void removeNiBlock( int blocknum ); + //! Move a block in the list + void moveNiBlock( int src, int dst ); + + //! Returns a list with all known NiXXX ids () + static QStringList allNiBlocks(); + //! Reorders the blocks according to a list of new block numbers + void reorderBlocks( const QVector & order ); + //! Moves all niblocks from this nif to another nif, returns a map which maps old block numbers to new block numbers + QMap moveAllNiBlocks( NifModel * targetnif, bool update = true ); + //! Convert a block from one type to another + void convertNiBlock( const QString & identifier, const QModelIndex & index ); + + // Block item getters +private: + const NifItem * _getBlockItem( const NifItem * block, const QString & ancestor ) const; + const NifItem * _getBlockItem( const NifItem * block, const QLatin1String & ancestor ) const; + const NifItem * _getBlockItem( const NifItem * block, const std::initializer_list & ancestors ) const; + const NifItem * _getBlockItem( const NifItem * block, const QStringList & ancestors ) const; + +public: + //! Get a block NifItem by its number. + const NifItem * getBlockItem( qint32 link ) const; + //! Get a block NifItem by its number, with a check that it inherits ancestor. + const NifItem * getBlockItem( qint32 link, const QString & ancestor ) const; + //! Get a block NifItem by its number, with a check that it inherits ancestor. + const NifItem * getBlockItem( qint32 link, const QLatin1String & ancestor ) const; + //! Get a block NifItem by its number, with a check that it inherits ancestor. + const NifItem * getBlockItem( qint32 link, const char * ancestor ) const; + //! Get a block NifItem by its number, with a check that it inherits any of ancestors. + const NifItem * getBlockItem( qint32 link, const std::initializer_list & ancestors ) const; + //! Get a block NifItem by its number, with a check that it inherits any of ancestors. + const NifItem * getBlockItem( qint32 link, const QStringList & ancestors ) const; + + //! Get the block NifItem an item belongs to. + const NifItem * getBlockItem( const NifItem * item ) const; + //! Get the block NifItem an item belongs to, with a check that it inherits ancestor. + const NifItem * getBlockItem( const NifItem * item, const QString & ancestor ) const; + //! Get the block NifItem an item belongs to, with a check that it inherits ancestor. + const NifItem * getBlockItem( const NifItem * item, const QLatin1String & ancestor ) const; + //! Get the block NifItem an item belongs to, with a check that it inherits ancestor. + const NifItem * getBlockItem( const NifItem * item, const char * ancestor ) const; + //! Get the block NifItem an item belongs to, with a check that it inherits any of ancestors. + const NifItem * getBlockItem( const NifItem * item, const std::initializer_list & ancestors ) const; + //! Get the block NifItem an item belongs to, with a check that it inherits any of ancestors. + const NifItem * getBlockItem( const NifItem * item, const QStringList & ancestors ) const; + + //! Get the block NifItem a model index belongs to. + const NifItem * getBlockItem( const QModelIndex & index ) const; + //! Get the block NifItem a model index belongs to, with a check that it inherits ancestor. + const NifItem * getBlockItem( const QModelIndex & index, const QString & ancestor ) const; + //! Get the block NifItem a model index belongs to, with a check that it inherits ancestor. + const NifItem * getBlockItem( const QModelIndex & index, const QLatin1String & ancestor ) const; + //! Get the block NifItem a model index belongs to, with a check that it inherits ancestor. + const NifItem * getBlockItem( const QModelIndex & index, const char * ancestor ) const; + //! Get the block NifItem a model index belongs to, with a check that it inherits any of ancestors. + const NifItem * getBlockItem( const QModelIndex & index, const std::initializer_list & ancestors ) const; + //! Get the block NifItem a model index belongs to, with a check that it inherits any of ancestors. + const NifItem * getBlockItem( const QModelIndex & index, const QStringList & ancestors ) const; + + //! Get a block NifItem by its number. + NifItem * getBlockItem( qint32 link ); + //! Get a block NifItem by its number, with a check that it inherits ancestor. + NifItem * getBlockItem( qint32 link, const QString & ancestor ); + //! Get a block NifItem by its number, with a check that it inherits ancestor. + NifItem * getBlockItem( qint32 link, const QLatin1String & ancestor ); + //! Get a block NifItem by its number, with a check that it inherits ancestor. + NifItem * getBlockItem( qint32 link, const char * ancestor ); + //! Get a block NifItem by its number, with a check that it inherits any of ancestors. + NifItem * getBlockItem( qint32 link, const std::initializer_list & ancestors ); + //! Get a block NifItem by its number, with a check that it inherits any of ancestors. + NifItem * getBlockItem( qint32 link, const QStringList & ancestors ); + + //! Get the block NifItem an item belongs to. + NifItem * getBlockItem( const NifItem * item ); + //! Get the block NifItem an item belongs to, with a check that it inherits ancestor. + NifItem * getBlockItem( const NifItem * item, const QString & ancestor ); + //! Get the block NifItem an item belongs to, with a check that it inherits ancestor. + NifItem * getBlockItem( const NifItem * item, const QLatin1String & ancestor ); + //! Get the block NifItem an item belongs to, with a check that it inherits ancestor. + NifItem * getBlockItem( const NifItem * item, const char * ancestor ); + //! Get the block NifItem an item belongs to, with a check that it inherits any of ancestors. + NifItem * getBlockItem( const NifItem * item, const std::initializer_list & ancestors ); + //! Get the block NifItem an item belongs to, with a check that it inherits any of ancestors. + NifItem * getBlockItem( const NifItem * item, const QStringList & ancestors ); + + //! Get the block NifItem a model index belongs to. + NifItem * getBlockItem( const QModelIndex & index ); + //! Get the block NifItem a model index belongs to, with a check that it inherits ancestor. + NifItem * getBlockItem( const QModelIndex & index, const QString & ancestor ); + //! Get the block NifItem a model index belongs to, with a check that it inherits ancestor. + NifItem * getBlockItem( const QModelIndex & index, const QLatin1String & ancestor ); + //! Get the block NifItem a model index belongs to, with a check that it inherits ancestor. + NifItem * getBlockItem( const QModelIndex & index, const char * ancestor ); + //! Get the block NifItem a model index belongs to, with a check that it inherits any of ancestors. + NifItem * getBlockItem( const QModelIndex & index, const std::initializer_list & ancestors ); + //! Get the block NifItem a model index belongs to, with a check that it inherits any of ancestors. + NifItem * getBlockItem( const QModelIndex & index, const QStringList & ancestors ); + + // Block index getters +public: + //! Get a block model index by its number. + QModelIndex getBlockIndex( qint32 link ) const; + //! Get a block model index by its number, with a check that it inherits ancestor. + QModelIndex getBlockIndex( qint32 link, const QString & ancestor ) const; + //! Get a block model index by its number, with a check that it inherits ancestor. + QModelIndex getBlockIndex( qint32 link, const QLatin1String & ancestor ) const; + //! Get a block model index by its number, with a check that it inherits ancestor. + QModelIndex getBlockIndex( qint32 link, const char * ancestor ) const; + //! Get a block model index by its number, with a check that it inherits any of ancestors. + QModelIndex getBlockIndex( qint32 link, const std::initializer_list & ancestors ) const; + //! Get a block model index by its number, with a check that it inherits any of ancestors. + QModelIndex getBlockIndex( qint32 link, const QStringList & ancestors ) const; + + //! Get the block model index an item belongs to. + QModelIndex getBlockIndex( const NifItem * item ) const; + //! Get the block model index an item belongs to, with a check that it inherits ancestor. + QModelIndex getBlockIndex( const NifItem * item, const QString & ancestor ) const; + //! Get the block model index an item belongs to, with a check that it inherits ancestor. + QModelIndex getBlockIndex( const NifItem * item, const QLatin1String & ancestor ) const; + //! Get the block model index an item belongs to, with a check that it inherits ancestor. + QModelIndex getBlockIndex( const NifItem * item, const char * ancestor ) const; + //! Get the block model index an item belongs to, with a check that it inherits any of ancestors. + QModelIndex getBlockIndex( const NifItem * item, const std::initializer_list & ancestors ) const; + //! Get the block model index an item belongs to, with a check that it inherits any of ancestors. + QModelIndex getBlockIndex( const NifItem * item, const QStringList & ancestors ) const; + + //! Get the block model index a model index belongs to. + QModelIndex getBlockIndex( const QModelIndex & index ) const; + //! Get the block model index a model index belongs to, with a check that it inherits ancestor. + QModelIndex getBlockIndex( const QModelIndex & index, const QString & ancestor ) const; + //! Get the block model index a model index belongs to, with a check that it inherits ancestor. + QModelIndex getBlockIndex( const QModelIndex & index, const QLatin1String & ancestor ) const; + //! Get the block model index a model index belongs to, with a check that it inherits ancestor. + QModelIndex getBlockIndex( const QModelIndex & index, const char * ancestor ) const; + //! Get the block model index a model index belongs to, with a check that it inherits any of ancestors. + QModelIndex getBlockIndex( const QModelIndex & index, const std::initializer_list & ancestors ) const; + //! Get the block model index a model index belongs to, with a check that it inherits any of ancestors. + QModelIndex getBlockIndex( const QModelIndex & index, const QStringList & ancestors ) const; + + // isNiBlock +public: + //! Determine if a value is a NiBlock identifier (). + static bool isNiBlock( const QString & name ); + + //! Check if a given item is a NiBlock. + bool isNiBlock( const NifItem * item ) const; + //! Check if a given item is a NiBlock of testType. + bool isNiBlock( const NifItem * item, const QString & testType ) const; + //! Check if a given item is a NiBlock of testType. + bool isNiBlock( const NifItem * item, const QLatin1String & testType ) const; + //! Check if a given item is a NiBlock of testType. + bool isNiBlock( const NifItem * item, const char * testType ) const; + //! Check if a given item is a NiBlock of one of testTypes. + bool isNiBlock( const NifItem * item, const std::initializer_list & testTypes ) const; + //! Check if a given item is a NiBlock of one of testTypes. + bool isNiBlock( const NifItem * item, const QStringList & testTypes ) const; + //! Check if a given model index is a NiBlock. + bool isNiBlock( const QModelIndex & index ) const; + //! Check if a given model index is a NiBlock of testType. + bool isNiBlock( const QModelIndex & index, const QString & testType ) const; + //! Check if a given model index is a NiBlock of testType. + bool isNiBlock( const QModelIndex & index, const QLatin1String & testType ) const; + //! Check if a given model index is a NiBlock of testType. + bool isNiBlock( const QModelIndex & index, const char * testType ) const; + //! Check if a given model index is a NiBlock of one of testTypes. + bool isNiBlock( const QModelIndex & index, const std::initializer_list & testTypes ) const; + //! Check if a given model index is a NiBlock of one of testTypes. + bool isNiBlock( const QModelIndex & index, const QStringList & testTypes ) const; + + // Block inheritance +public: + //! Returns true if blockName inherits ancestor. + bool inherits( const QString & blockName, const QString & ancestor ) const override final; + //! Returns true if blockName inherits ancestor. + bool inherits( const QString & blockName, const QLatin1String & ancestor ) const; + //! Returns true if blockName inherits ancestor. + bool inherits( const QString & blockName, const char * ancestor ) const; + //! Returns true if blockName inherits any of ancestors. + bool inherits( const QString & blockName, const std::initializer_list & ancestors ) const; + //! Returns true if blockName inherits any of ancestors. + bool inherits( const QString & blockName, const QStringList & ancestors ) const; + + //! Returns true if the block containing an item inherits ancestor. + bool blockInherits( const NifItem * item, const QString & ancestor ) const; + //! Returns true if the block containing an item inherits ancestor. + bool blockInherits( const NifItem * item, const QLatin1String & ancestor ) const; + //! Returns true if the block containing an item inherits ancestor. + bool blockInherits( const NifItem * item, const char * ancestor ) const; + //! Returns true if the block containing an item inherits any of ancestors. + bool blockInherits( const NifItem * item, const std::initializer_list & ancestors ) const; + //! Returns true if the block containing an item inherits any of ancestors. + bool blockInherits( const NifItem * item, const QStringList & ancestors ) const; + + //! Returns true if the block containing a model index inherits ancestor. + bool blockInherits( const QModelIndex & index, const QString & ancestor ) const; + //! Returns true if the block containing a model index inherits ancestor. + bool blockInherits( const QModelIndex & index, const QLatin1String & ancestor ) const; + //! Returns true if the block containing a model index inherits ancestor. + bool blockInherits( const QModelIndex & index, const char * ancestor ) const; + //! Returns true if the block containing a model index inherits any of ancestors. + bool blockInherits( const QModelIndex & index, const std::initializer_list & ancestors ) const; + //! Returns true if the block containing a model index inherits any of ancestors. + bool blockInherits( const QModelIndex & index, const QStringList & ancestors ) const; + + // Item value getters +public: + //! Get the value of an item. + template T get( const NifItem * item ) const; + //! Get the value of a child item. + template T get( const NifItem * itemParent, int itemIndex ) const; + //! Get the value of a child item. + template T get( const NifItem * itemParent, const QString & itemName ) const; + //! Get the value of a child item. + template T get( const NifItem * itemParent, const QLatin1String & itemName ) const; + //! Get the value of a child item. + template T get( const NifItem * itemParent, const char * itemName ) const; + //! Get the value of a model index. + template T get( const QModelIndex & index ) const; + //! Get the value of a child item. + template T get( const QModelIndex & itemParent, int itemIndex ) const; + //! Get the value of a child item. + template T get( const QModelIndex & itemParent, const QString & itemName ) const; + //! Get the value of a child item. + template T get( const QModelIndex & itemParent, const QLatin1String & itemName ) const; + //! Get the value of a child item. + template T get( const QModelIndex & itemParent, const char * itemName ) const; + + // Item value setters +public: + //! Set the value of an item. + template bool set( NifItem * item, const T & val ); + //! Set the value of a child item. + template bool set( const NifItem * itemParent, int itemIndex, const T & val ); + //! Set the value of a child item. + template bool set( const NifItem * itemParent, const QString & itemName, const T & val ); + //! Set the value of a child item. + template bool set( const NifItem * itemParent, const QLatin1String & itemName, const T & val ); + //! Set the value of a child item. + template bool set( const NifItem * itemParent, const char * itemName, const T & val ); + //! Set the value of a model index. + template bool set( const QModelIndex & index, const T & val ); + //! Set the value of a child item. + template bool set( const QModelIndex & itemParent, int itemIndex, const T & val ); + //! Set the value of a child item. + template bool set( const QModelIndex & itemParent, const QString & itemName, const T & val ); + //! Set the value of a child item. + template bool set( const QModelIndex & itemParent, const QLatin1String & itemName, const T & val ); + //! Set the value of a child item. + template bool set( const QModelIndex & itemParent, const char * itemName, const T & val ); + + // String resolving ("get ex") +public: + //! Get the string value of an item, expanding string indices or subitems if necessary. + QString resolveString( const NifItem * item ) const; + //! Get the string value of a child item, expanding string indices or subitems if necessary. + QString resolveString( const NifItem * itemParent, int itemIndex ) const; + //! Get the string value of a child item, expanding string indices or subitems if necessary. + QString resolveString( const NifItem * itemParent, const QString & itemName ) const; + //! Get the string value of a child item, expanding string indices or subitems if necessary. + QString resolveString( const NifItem * itemParent, const QLatin1String & itemName ) const; + //! Get the string value of a child item, expanding string indices or subitems if necessary. + QString resolveString( const NifItem * itemParent, const char * itemName ) const; + //! Get the string value of a model index, expanding string indices or subitems if necessary. + QString resolveString( const QModelIndex & index ) const; + //! Get the string value of a child item, expanding string indices or subitems if necessary. + QString resolveString( const QModelIndex & itemParent, int itemIndex ) const; + //! Get the string value of a child item, expanding string indices or subitems if necessary. + QString resolveString( const QModelIndex & itemParent, const QString & itemName ) const; + //! Get the string value of a child item, expanding string indices or subitems if necessary. + QString resolveString( const QModelIndex & itemParent, const QLatin1String & itemName ) const; + //! Get the string value of a child item, expanding string indices or subitems if necessary. + QString resolveString( const QModelIndex & itemParent, const char * itemName ) const; + + // String assigning ("set ex") +public: + //! Set the string value of an item, updating string indices or subitems if necessary. + bool assignString( NifItem * item, const QString & string, bool replace = false ); + //! Set the string value of a child item, updating string indices or subitems if necessary. + bool assignString( const NifItem * itemParent, int itemIndex, const QString & string, bool replace = false ); + //! Set the string value of a child item, updating string indices or subitems if necessary. + bool assignString( const NifItem * itemParent, const QString & itemName, const QString & string, bool replace = false ); + //! Set the string value of a child item, updating string indices or subitems if necessary. + bool assignString( const NifItem * itemParent, const QLatin1String & itemName, const QString & string, bool replace = false ); + //! Set the string value of a child item, updating string indices or subitems if necessary. + bool assignString( const NifItem * itemParent, const char * itemName, const QString & string, bool replace = false ); + //! Set the string value of a model index, updating string indices or subitems if necessary. + bool assignString( const QModelIndex & index, const QString & string, bool replace = false ); + //! Set the string value of a child item, updating string indices or subitems if necessary. + bool assignString( const QModelIndex & itemParent, int itemIndex, const QString & string, bool replace = false ); + //! Set the string value of a child item, updating string indices or subitems if necessary. + bool assignString( const QModelIndex & itemParent, const QString & itemName, const QString & string, bool replace = false ); + //! Set the string value of a child item, updating string indices or subitems if necessary. + bool assignString( const QModelIndex & itemParent, const QLatin1String & itemName, const QString & string, bool replace = false ); + //! Set the string value of a child item, updating string indices or subitems if necessary. + bool assignString( const QModelIndex & itemParent, const char * itemName, const QString & string, bool replace = false ); + + // Link getters +public: + //! Return the link value (block number) of an item if it's a valid link, otherwise -1. + qint32 getLink( const NifItem * item ) const; + //! Return the link value (block number) of a child item if it's a valid link, otherwise -1. + qint32 getLink( const NifItem * itemParent, int itemIndex ) const; + //! Return the link value (block number) of a child item if it's a valid link, otherwise -1. + qint32 getLink( const NifItem * itemParent, const QString & itemName ) const; + //! Return the link value (block number) of a child item if it's a valid link, otherwise -1. + qint32 getLink( const NifItem * itemParent, const QLatin1String & itemName ) const; + //! Return the link value (block number) of a child item if it's a valid link, otherwise -1. + qint32 getLink( const NifItem * itemParent, const char * itemName ) const; + //! Return the link value (block number) of a model index if it's a valid link, otherwise -1. + qint32 getLink( const QModelIndex & index ) const; + //! Return the link value (block number) of a child item if it's a valid link, otherwise -1. + qint32 getLink( const QModelIndex & itemParent, int itemIndex ) const; + //! Return the link value (block number) of a child item if it's a valid link, otherwise -1. + qint32 getLink( const QModelIndex & itemParent, const QString & itemName ) const; + //! Return the link value (block number) of a child item if it's a valid link, otherwise -1. + qint32 getLink( const QModelIndex & itemParent, const QLatin1String & itemName ) const; + //! Return the link value (block number) of a child item if it's a valid link, otherwise -1. + qint32 getLink( const QModelIndex & itemParent, const char * itemName ) const; + + // Link setters +public: + //! Set the link value (block number) of an item if it's a valid link. + bool setLink( NifItem * item, qint32 link ); + //! Set the link value (block number) of a child item if it's a valid link. + bool setLink( const NifItem * itemParent, int itemIndex, qint32 link ); + //! Set the link value (block number) of a child item if it's a valid link. + bool setLink( const NifItem * itemParent, const QString & itemName, qint32 link ); + //! Set the link value (block number) of a child item if it's a valid link. + bool setLink( const NifItem * itemParent, const QLatin1String & itemName, qint32 link ); + //! Set the link value (block number) of a child item if it's a valid link. + bool setLink( const NifItem * itemParent, const char * itemName, qint32 link ); + //! Set the link value (block number) of a model index if it's a valid link. + bool setLink( const QModelIndex & index, qint32 link ); + //! Set the link value (block number) of a child item if it's a valid link. + bool setLink( const QModelIndex & itemParent, int itemIndex, qint32 link ); + //! Set the link value (block number) of a child item if it's a valid link. + bool setLink( const QModelIndex & itemParent, const QString & itemName, qint32 link ); + //! Set the link value (block number) of a child item if it's a valid link. + bool setLink( const QModelIndex & itemParent, const QLatin1String & itemName, qint32 link ); + //! Set the link value (block number) of a child item if it's a valid link. + bool setLink( const QModelIndex & itemParent, const char * itemName, qint32 link ); + + // Link array getters +public: + //! Return a QVector of link values (block numbers) of an item if it's a valid link array. + QVector getLinkArray( const NifItem * arrayRootItem ) const; + //! Return a QVector of link values (block numbers) of a child item if it's a valid link array. + QVector getLinkArray( const NifItem * arrayParent, int arrayIndex ) const; + //! Return a QVector of link values (block numbers) of a child item if it's a valid link array. + QVector getLinkArray( const NifItem * arrayParent, const QString & arrayName ) const; + //! Return a QVector of link values (block numbers) of a child item if it's a valid link array. + QVector getLinkArray( const NifItem * arrayParent, const QLatin1String & arrayName ) const; + //! Return a QVector of link values (block numbers) of a child item if it's a valid link array. + QVector getLinkArray( const NifItem * arrayParent, const char * arrayName ) const; + //! Return a QVector of link values (block numbers) of a model index if it's a valid link array. + QVector getLinkArray( const QModelIndex & iArray ) const; + //! Return a QVector of link values (block numbers) of a child item if it's a valid link array. + QVector getLinkArray( const QModelIndex & arrayParent, int arrayIndex ) const; + //! Return a QVector of link values (block numbers) of a child item if it's a valid link array. + QVector getLinkArray( const QModelIndex & arrayParent, const QString & arrayName ) const; + //! Return a QVector of link values (block numbers) of a child item if it's a valid link array. + QVector getLinkArray( const QModelIndex & arrayParent, const QLatin1String & arrayName ) const; + //! Return a QVector of link values (block numbers) of a child item if it's a valid link array. + QVector getLinkArray( const QModelIndex & arrayParent, const char * arrayName ) const; + + // Link array setters +public: + //! Write a QVector of link values (block numbers) to an item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( NifItem * arrayRootItem, const QVector & links ); + //! Write a QVector of link values (block numbers) to a child item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const NifItem * arrayParent, int arrayIndex, const QVector & links ); + //! Write a QVector of link values (block numbers) to a child item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const NifItem * arrayParent, const QString & arrayName, const QVector & links ); + //! Write a QVector of link values (block numbers) to a child item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const NifItem * arrayParent, const QLatin1String & arrayName, const QVector & links ); + //! Write a QVector of link values (block numbers) to a child item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const NifItem * arrayParent, const char * arrayName, const QVector & links ); + //! Write a QVector of link values (block numbers) to a model index if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const QModelIndex & iArray, const QVector & links ); + //! Write a QVector of link values (block numbers) to a child item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const QModelIndex & arrayParent, int arrayIndex, const QVector & links ); + //! Write a QVector of link values (block numbers) to a child item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const QModelIndex & arrayParent, const QString & arrayName, const QVector & links ); + //! Write a QVector of link values (block numbers) to a child item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const QModelIndex & arrayParent, const QLatin1String & arrayName, const QVector & links ); + //! Write a QVector of link values (block numbers) to a child item if it's a valid link array. + // The size of QVector must match the current size of the array. + bool setLinkArray( const QModelIndex & arrayParent, const char * arrayName, const QVector & links ); + public slots: void updateSettings(); @@ -312,45 +637,35 @@ public slots: protected: // BaseModel - NifItem * getItem( NifItem * parent, const QString & name ) const override final; - - bool setItemValue( NifItem * item, const NifValue & v ) override final; - - bool updateArrayItem( NifItem * array ) override final; + bool updateArraySizeImpl( NifItem * array ) override final; + bool updateByteArraySize( NifItem * array ); + bool updateChildArraySizes( NifItem * parent ); QString ver2str( quint32 v ) const override final { return version2string( v ); } quint32 str2ver( QString s ) const override final { return version2number( s ); } - bool evalVersion( NifItem * item, bool chkParents = false ) const override final; + //! Get condition value cache NifItem for an item. + // If the item has no cache NifItem, returns the item itself. + const NifItem * getConditionCacheItem( const NifItem * item ) const; - bool setHeaderString( const QString &, uint ver = 0 ) override final; + bool evalVersionImpl( const NifItem * item ) const override final; - template T get( NifItem * parent, const QString & name ) const; - template T get( NifItem * item ) const; - template bool set( NifItem * parent, const QString & name, const T & d ); - template bool set( NifItem * item, const T & d ); + bool evalConditionImpl( const NifItem * item ) const override final; + + bool setHeaderString( const QString &, uint ver = 0 ) override final; // end BaseModel bool loadItem( NifItem * parent, NifIStream & stream ); bool loadHeader( NifItem * parent, NifIStream & stream ); - bool saveItem( NifItem * parent, NifOStream & stream ) const; - bool fileOffset( NifItem * parent, NifItem * target, NifSStream & stream, int & ofs ) const; - - NifItem * getHeaderItem() const; - NifItem * getFooterItem() const; - NifItem * getBlockItem( int ) const; - - int getBlockNumber( NifItem * item ) const; - bool itemIsLink( NifItem * item, bool * ischildLink = 0 ) const; + bool saveItem( const NifItem * parent, NifOStream & stream ) const; + bool fileOffset( const NifItem * parent, const NifItem * target, NifSStream & stream, int & ofs ) const; +protected: void insertAncestor( NifItem * parent, const QString & identifier, int row = -1 ); void insertType( NifItem * parent, const NifData & data, int row = -1 ); NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); - bool updateByteArrayItem( NifItem * array ); - bool updateArrays( NifItem * parent ); - void updateLinks( int block = -1 ); void updateLinks( int block, NifItem * parent ); void checkLinks( int block, QStack & parents ); @@ -358,7 +673,6 @@ public slots: void mapLinks( NifItem * parent, const QMap & map ); static void updateStrings( NifModel * src, NifModel * tgt, NifItem * item ); - bool assignString( NifItem * parent, const QString & string, bool replace = false ); //! NIF file version quint32 version; @@ -382,7 +696,12 @@ public slots: void updateModel( UpdateType value = utAll ); quint32 bsVersion; - void cacheBSVersion(NifItem* headerItem); + void cacheBSVersion( const NifItem * headerItem ); + + QString topItemRepr( const NifItem * item ) const override final; + void onItemValueChange( NifItem * item ) override final; + + void invalidateItemConditions( NifItem * item ); //! Parse the XML file using a NifXmlHandler static QString parseXmlDescription( const QString & filename ); @@ -422,16 +741,36 @@ class NifModelEval // Inlines -inline const NifModel * NifModel::fromIndex( const QModelIndex& index ) +inline const NifModel * NifModel::fromIndex( const QModelIndex & index ) { - return static_cast(index.model()); // qobject_cast + return static_cast( index.model() ); // qobject_cast } -inline const NifModel * NifModel::fromValidIndex( const QModelIndex& index ) +inline const NifModel * NifModel::fromValidIndex( const QModelIndex & index ) { return index.isValid() ? NifModel::fromIndex( index ) : nullptr; } +inline QString NifModel::createRTTIName( const QModelIndex & iBlock ) const +{ + return createRTTIName( getItem(iBlock) ); +} + +inline NifItem * NifModel::getHeaderItem() +{ + return const_cast( const_cast(this)->getHeaderItem() ); +} + +inline QModelIndex NifModel::getHeaderIndex() const +{ + return itemToIndex( getHeaderItem() ); +} + +inline NifItem * NifModel::getFooterItem() +{ + return const_cast( const_cast(this)->getFooterItem() ); +} + inline QStringList NifModel::allNiBlocks() { QStringList lst; @@ -489,12 +828,14 @@ inline QList NifModel::getParentLinks( int block ) const return parentLinks.value( block ); } -inline bool NifModel::itemIsLink( NifItem * item, bool * isChildLink ) const +inline bool NifModel::isLink( const NifItem * item ) const { - if ( isChildLink ) - *isChildLink = ( item->value().type() == NifValue::tLink ); + return item ? item->valueIsLink() : false; +} - return item->value().isLink(); +inline bool NifModel::isLink( const QModelIndex & index ) const +{ + return isLink( getItem(index) ); } inline bool NifModel::checkVersion( quint32 since, quint32 until ) const @@ -502,95 +843,665 @@ inline bool NifModel::checkVersion( quint32 since, quint32 until ) const return (( since == 0 || since <= version ) && ( until == 0 || version <= until )); } +constexpr inline int NifModel::firstBlockRow() const +{ + return 1; // The fist root's child is always the header +} -// Templates +inline int NifModel::lastBlockRow() const +{ + return root->childCount() - 2; // The last root's child is always the footer. +} +inline int NifModel::getBlockCount() const +{ + return std::max( lastBlockRow() - firstBlockRow() + 1, 0 ); +} -template inline T NifModel::get( const QModelIndex & index ) const +inline int NifModel::getBlockNumber( const QModelIndex & index ) const { - return BaseModel::get( index ); + return getBlockNumber( getItem(index) ); } -template inline T NifModel::get( NifItem * item ) const + +// Block item getters + +inline const NifItem * NifModel::getBlockItem( qint32 link, const QString & ancestor ) const { - return BaseModel::get( item ); + return _getBlockItem( getBlockItem(link), ancestor ); +} +inline const NifItem * NifModel::getBlockItem( qint32 link, const QLatin1String & ancestor ) const +{ + return _getBlockItem( getBlockItem(link), ancestor ); +} +inline const NifItem * NifModel::getBlockItem( qint32 link, const char * ancestor ) const +{ + return _getBlockItem( getBlockItem(link), QLatin1Literal(ancestor) ); +} +inline const NifItem * NifModel::getBlockItem( qint32 link, const std::initializer_list & ancestors ) const +{ + return _getBlockItem( getBlockItem(link), ancestors ); +} +inline const NifItem * NifModel::getBlockItem( qint32 link, const QStringList & ancestors ) const +{ + return _getBlockItem( getBlockItem(link), ancestors ); } -template inline T NifModel::get( NifItem * parent, const QString & name ) const +inline const NifItem * NifModel::getBlockItem( const NifItem * item, const QString & ancestor ) const { - return BaseModel::get( parent, name ); + return _getBlockItem( getBlockItem(item), ancestor ); +} +inline const NifItem * NifModel::getBlockItem( const NifItem * item, const QLatin1String & ancestor ) const +{ + return _getBlockItem( getBlockItem(item), ancestor ); +} +inline const NifItem * NifModel::getBlockItem( const NifItem * item, const char * ancestor ) const +{ + return _getBlockItem( getBlockItem(item), QLatin1String(ancestor) ); +} +inline const NifItem * NifModel::getBlockItem( const NifItem * item, const std::initializer_list & ancestors ) const +{ + return _getBlockItem( getBlockItem(item), ancestors ); +} +inline const NifItem * NifModel::getBlockItem( const NifItem * item, const QStringList & ancestors ) const +{ + return _getBlockItem( getBlockItem(item), ancestors ); } -template inline T NifModel::get( const QModelIndex & parent, const QString & name ) const +inline const NifItem * NifModel::getBlockItem( const QModelIndex & index ) const +{ + return getBlockItem( getItem(index) ); +} +inline const NifItem * NifModel::getBlockItem( const QModelIndex & index, const QString & ancestor ) const +{ + return getBlockItem( getItem(index), ancestor ); +} +inline const NifItem * NifModel::getBlockItem( const QModelIndex & index, const QLatin1String & ancestor ) const +{ + return getBlockItem( getItem(index), ancestor ); +} +inline const NifItem * NifModel::getBlockItem( const QModelIndex & index, const char * ancestor ) const { - return BaseModel::get( parent, name ); + return getBlockItem( getItem(index), QLatin1String(ancestor) ); +} +inline const NifItem * NifModel::getBlockItem( const QModelIndex & index, const std::initializer_list & ancestors ) const +{ + return getBlockItem( getItem(index), ancestors ); +} +inline const NifItem * NifModel::getBlockItem( const QModelIndex & index, const QStringList & ancestors ) const +{ + return getBlockItem( getItem(index), ancestors ); } -template inline bool NifModel::set( const QModelIndex & index, const T & d ) +#define _NIFMODEL_NONCONST_GETBLOCKITEM_1(arg) const_cast( const_cast(this)->getBlockItem( arg ) ) +#define _NIFMODEL_NONCONST_GETBLOCKITEM_2(arg1, arg2) const_cast( const_cast(this)->getBlockItem( arg1, arg2 ) ) + +inline NifItem * NifModel::getBlockItem( qint32 link ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_1( link ); +} +inline NifItem * NifModel::getBlockItem( qint32 link, const QString & ancestor ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( link, ancestor ); +} +inline NifItem * NifModel::getBlockItem( qint32 link, const QLatin1String & ancestor ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( link, ancestor ); +} +inline NifItem * NifModel::getBlockItem( qint32 link, const char * ancestor ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( link, QLatin1String(ancestor) ); +} +inline NifItem * NifModel::getBlockItem( qint32 link, const std::initializer_list & ancestors ) { - bool result = BaseModel::set( index, d ); - if ( result ) - invalidateDependentConditions( index ); - return result; + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( link, ancestors ); +} +inline NifItem * NifModel::getBlockItem( qint32 link, const QStringList & ancestors ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( link, ancestors ); } -template inline bool NifModel::set( NifItem * item, const T & d ) +inline NifItem * NifModel::getBlockItem( const NifItem * item ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_1( item ); +} +inline NifItem * NifModel::getBlockItem( const NifItem * item, const QString & ancestor ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( item, ancestor ); +} +inline NifItem * NifModel::getBlockItem( const NifItem * item, const QLatin1String & ancestor ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( item, ancestor ); +} +inline NifItem * NifModel::getBlockItem( const NifItem * item, const char * ancestor ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( item, QLatin1String(ancestor) ); +} +inline NifItem * NifModel::getBlockItem( const NifItem * item, const std::initializer_list & ancestors ) { - bool result = BaseModel::set( item, d ); - if ( result ) - invalidateDependentConditions( item ); - return result; + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( item, ancestors ); +} +inline NifItem * NifModel::getBlockItem( const NifItem * item, const QStringList & ancestors ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( item, ancestors ); } -template inline bool NifModel::set( const QModelIndex & parent, const QString & name, const T & d ) +inline NifItem * NifModel::getBlockItem( const QModelIndex & index ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_1( index ); +} +inline NifItem * NifModel::getBlockItem( const QModelIndex & index, const QString & ancestor ) { - bool result = BaseModel::set( parent, name, d ); - if ( result ) - invalidateDependentConditions( getIndex( parent, name ) ); - return result; + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( index, ancestor ); } +inline NifItem * NifModel::getBlockItem( const QModelIndex & index, const QLatin1String & ancestor ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( index, ancestor ); +} +inline NifItem * NifModel::getBlockItem( const QModelIndex & index, const char * ancestor ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( index, QLatin1String(ancestor) ); +} +inline NifItem * NifModel::getBlockItem( const QModelIndex & index, const std::initializer_list & ancestors ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( index, ancestors ); +} +inline NifItem * NifModel::getBlockItem( const QModelIndex & index, const QStringList & ancestors ) +{ + return _NIFMODEL_NONCONST_GETBLOCKITEM_2( index, ancestors ); +} + + +// Block index getters -template inline bool NifModel::set( NifItem * parent, const QString & name, const T & d ) +#define _NIFMODEL_GETBLOCKINDEX_1(arg) itemToIndex( getBlockItem( arg ) ) +#define _NIFMODEL_GETBLOCKINDEX_2(arg1, arg2) itemToIndex( getBlockItem( arg1, arg2 ) ) + +inline QModelIndex NifModel::getBlockIndex( qint32 link ) const +{ + return _NIFMODEL_GETBLOCKINDEX_1( link ); +} +inline QModelIndex NifModel::getBlockIndex( qint32 link, const QString & ancestor ) const { - bool result = BaseModel::set( parent, name, d ); - if ( result ) - invalidateDependentConditions( getItem( parent, name ) ); - return result; + return _NIFMODEL_GETBLOCKINDEX_2( link, ancestor ); +} +inline QModelIndex NifModel::getBlockIndex( qint32 link, const QLatin1String & ancestor ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( link, ancestor ); +} +inline QModelIndex NifModel::getBlockIndex( qint32 link, const char * ancestor ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( link, QLatin1String(ancestor) ); +} +inline QModelIndex NifModel::getBlockIndex( qint32 link, const std::initializer_list & ancestors ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( link, ancestors ); +} +inline QModelIndex NifModel::getBlockIndex( qint32 link, const QStringList & ancestors ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( link, ancestors ); } -template <> inline QString NifModel::get( const QModelIndex & index ) const +inline QModelIndex NifModel::getBlockIndex( const NifItem * item ) const +{ + return _NIFMODEL_GETBLOCKINDEX_1( item ); +} +inline QModelIndex NifModel::getBlockIndex( const NifItem * item, const QString & ancestor ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( item, ancestor ); +} +inline QModelIndex NifModel::getBlockIndex( const NifItem * item, const QLatin1String & ancestor ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( item, ancestor ); +} +inline QModelIndex NifModel::getBlockIndex( const NifItem * item, const char * ancestor ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( item, QLatin1String(ancestor) ); +} +inline QModelIndex NifModel::getBlockIndex( const NifItem * item, const std::initializer_list & ancestors ) const { - return this->string( index ); + return _NIFMODEL_GETBLOCKINDEX_2( item, ancestors ); +} +inline QModelIndex NifModel::getBlockIndex( const NifItem * item, const QStringList & ancestors ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( item, ancestors ); } -//template <> inline QString NifModel::get( NifItem * item ) const { -// return this->string( this->item ); -//} +inline QModelIndex NifModel::getBlockIndex( const QModelIndex & index ) const +{ + return _NIFMODEL_GETBLOCKINDEX_1( index ); +} +inline QModelIndex NifModel::getBlockIndex( const QModelIndex & index, const QString & ancestor ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( index, ancestor ); +} +inline QModelIndex NifModel::getBlockIndex( const QModelIndex & index, const QLatin1String & ancestor ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( index, ancestor ); +} +inline QModelIndex NifModel::getBlockIndex( const QModelIndex & index, const char * ancestor ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( index, QLatin1String(ancestor) ); +} +inline QModelIndex NifModel::getBlockIndex( const QModelIndex & index, const std::initializer_list & ancestors ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( index, ancestors ); +} +inline QModelIndex NifModel::getBlockIndex( const QModelIndex & index, const QStringList & ancestors ) const +{ + return _NIFMODEL_GETBLOCKINDEX_2( index, ancestors ); +} -//template <> inline QString NifModel::get( NifItem * parent, const QString & name ) const { -// return this->string(parent, name); -//} -template <> inline QString NifModel::get( const QModelIndex & parent, const QString & name ) const +// isNiBlock + +inline bool NifModel::isNiBlock( const NifItem * item, const QString & testType ) const +{ + return isNiBlock(item) && item->hasName(testType); +} +inline bool NifModel::isNiBlock( const NifItem * item, const QLatin1String & testType ) const +{ + return isNiBlock(item) && item->hasName(testType); +} +inline bool NifModel::isNiBlock( const NifItem * item, const char * testType ) const +{ + return isNiBlock( item, QLatin1String(testType) ); +} +inline bool NifModel::isNiBlock( const QModelIndex & index ) const +{ + return isNiBlock( getItem(index) ); +} +inline bool NifModel::isNiBlock( const QModelIndex & index, const QString & testType ) const +{ + return isNiBlock( getItem(index), testType ); +} +inline bool NifModel::isNiBlock( const QModelIndex & index, const QLatin1String & testType ) const +{ + return isNiBlock( getItem(index), testType ); +} +inline bool NifModel::isNiBlock( const QModelIndex & index, const char * testType ) const +{ + return isNiBlock( getItem(index), QLatin1String(testType) ); +} +inline bool NifModel::isNiBlock( const QModelIndex & index, const std::initializer_list & testTypes ) const +{ + return isNiBlock( getItem(index), testTypes ); +} +inline bool NifModel::isNiBlock( const QModelIndex & index, const QStringList & testTypes ) const +{ + return isNiBlock( getItem(index), testTypes ); +} + + +// Block inheritance + +inline bool NifModel::inherits( const QString & blockName, const char * ancestor ) const +{ + return inherits( blockName, QLatin1String(ancestor) ); +} +inline bool NifModel::blockInherits( const NifItem * item, const char * ancestor ) const +{ + return blockInherits( item, QLatin1String(ancestor) ); +} +inline bool NifModel::blockInherits( const QModelIndex & index, const QString & ancestor ) const +{ + return blockInherits( getItem(index), ancestor ); +} +inline bool NifModel::blockInherits( const QModelIndex & index, const QLatin1String & ancestor ) const +{ + return blockInherits( getItem(index), ancestor ); +} +inline bool NifModel::blockInherits( const QModelIndex & index, const char * ancestor ) const +{ + return blockInherits( getItem(index), QLatin1String(ancestor) ); +} +inline bool NifModel::blockInherits( const QModelIndex & index, const std::initializer_list & ancestors ) const +{ + return blockInherits( getItem(index), ancestors ); +} +inline bool NifModel::blockInherits( const QModelIndex & index, const QStringList & ancestors ) const +{ + return blockInherits( getItem(index), ancestors ); +} + + +// Item value getters + +template inline T NifModel::get( const NifItem * item ) const +{ + return BaseModel::get( item ); +} +template <> inline QString NifModel::get( const NifItem * item ) const +{ + return resolveString( item ); +} +template inline T NifModel::get( const NifItem * itemParent, int itemIndex ) const +{ + return get( getItem(itemParent, itemIndex) ); +} +template inline T NifModel::get( const NifItem * itemParent, const QString & itemName ) const +{ + return get( getItem(itemParent, itemName) ); +} +template inline T NifModel::get( const NifItem * itemParent, const QLatin1String & itemName ) const +{ + return get( getItem(itemParent, itemName) ); +} +template inline T NifModel::get( const NifItem * itemParent, const char * itemName ) const +{ + return get( getItem(itemParent, QLatin1String(itemName)) ); +} +template inline T NifModel::get( const QModelIndex & index ) const +{ + return get( getItem(index) ); +} +template inline T NifModel::get( const QModelIndex & itemParent, int itemIndex ) const +{ + return get( getItem(itemParent, itemIndex) ); +} +template inline T NifModel::get( const QModelIndex & itemParent, const QString & itemName ) const +{ + return get( getItem(itemParent, itemName) ); +} +template inline T NifModel::get( const QModelIndex & itemParent, const QLatin1String & itemName ) const { - return this->string( parent, name ); + return get( getItem(itemParent, itemName) ); } +template inline T NifModel::get( const QModelIndex & itemParent, const char * itemName ) const +{ + return get( getItem(itemParent, QLatin1String(itemName)) ); +} + + +// Item value setters -template <> inline bool NifModel::set( const QModelIndex & index, const QString & d ) +template inline bool NifModel::set( NifItem * item, const T & val ) { - return this->assignString( index, d ); + return BaseModel::set( item, val ); +} +template <> inline bool NifModel::set( NifItem * item, const QString & val ) +{ + return assignString( item, val ); +} +template inline bool NifModel::set( const NifItem * itemParent, int itemIndex, const T & val ) +{ + return set( getItem(itemParent, itemIndex, true), val ); +} +template inline bool NifModel::set( const NifItem * itemParent, const QString & itemName, const T & val ) +{ + return set( getItem(itemParent, itemName, true), val ); +} +template inline bool NifModel::set( const NifItem * itemParent, const QLatin1String & itemName, const T & val ) +{ + return set( getItem(itemParent, itemName, true), val ); +} +template inline bool NifModel::set( const NifItem * itemParent, const char * itemName, const T & val ) +{ + return set( getItem(itemParent, QLatin1String(itemName), true), val ); +} +template inline bool NifModel::set( const QModelIndex & index, const T & val ) +{ + return set( getItem(index), val ); +} +template inline bool NifModel::set( const QModelIndex & itemParent, int itemIndex, const T & val ) +{ + return set( getItem(itemParent, itemIndex, true), val ); +} +template inline bool NifModel::set( const QModelIndex & itemParent, const QString & itemName, const T & val ) +{ + return set( getItem(itemParent, itemName, true), val ); +} +template inline bool NifModel::set( const QModelIndex & itemParent, const QLatin1String & itemName, const T & val ) +{ + return set( getItem(itemParent, itemName, true), val ); +} +template inline bool NifModel::set( const QModelIndex & itemParent, const char * itemName, const T & val ) +{ + return set( getItem(itemParent, QLatin1String(itemName), true), val ); } -//template <> inline bool NifModel::set( NifItem * item, const QString & d ) { -// return this->assignString( item, d ); -//} -template <> inline bool NifModel::set( const QModelIndex & parent, const QString & name, const QString & d ) +// String resolving ("get ex") + +inline QString NifModel::resolveString( const NifItem * itemParent, int itemIndex ) const +{ + return resolveString( getItem(itemParent, itemIndex) ); +} +inline QString NifModel::resolveString( const NifItem * itemParent, const QString & itemName ) const +{ + return resolveString( getItem(itemParent, itemName) ); +} +inline QString NifModel::resolveString( const NifItem * itemParent, const QLatin1String & itemName ) const +{ + return resolveString( getItem(itemParent, itemName) ); +} +inline QString NifModel::resolveString( const NifItem * itemParent, const char * itemName ) const +{ + return resolveString( getItem(itemParent, QLatin1String(itemName)) ); +} +inline QString NifModel::resolveString( const QModelIndex & index ) const +{ + return resolveString( getItem(index) ); +} +inline QString NifModel::resolveString( const QModelIndex & itemParent, int itemIndex ) const +{ + return resolveString( getItem(itemParent, itemIndex) ); +} +inline QString NifModel::resolveString( const QModelIndex & itemParent, const QString & itemName ) const +{ + return resolveString( getItem(itemParent, itemName) ); +} +inline QString NifModel::resolveString( const QModelIndex & itemParent, const QLatin1String & itemName ) const +{ + return resolveString( getItem(itemParent, itemName) ); +} +inline QString NifModel::resolveString( const QModelIndex & itemParent, const char * itemName ) const +{ + return resolveString( getItem(itemParent, QLatin1String(itemName)) ); +} + + +// String assigning ("set ex") + +inline bool NifModel::assignString( const NifItem * itemParent, int itemIndex, const QString & string, bool replace ) +{ + return assignString( getItem(itemParent, itemIndex, true), string, replace ); +} +inline bool NifModel::assignString( const NifItem * itemParent, const QString & itemName, const QString & string, bool replace ) +{ + return assignString( getItem(itemParent, itemName, true), string, replace ); +} +inline bool NifModel::assignString( const NifItem * itemParent, const QLatin1String & itemName, const QString & string, bool replace ) +{ + return assignString( getItem(itemParent, itemName, true), string, replace ); +} +inline bool NifModel::assignString( const NifItem * itemParent, const char * itemName, const QString & string, bool replace ) +{ + return assignString( getItem(itemParent, QLatin1String(itemName), true), string, replace ); +} +inline bool NifModel::assignString( const QModelIndex & index, const QString & string, bool replace ) +{ + return assignString( getItem(index), string, replace ); +} +inline bool NifModel::assignString( const QModelIndex & itemParent, int itemIndex, const QString & string, bool replace ) +{ + return assignString( getItem(itemParent, itemIndex, true), string, replace ); +} +inline bool NifModel::assignString( const QModelIndex & itemParent, const QString & itemName, const QString & string, bool replace ) +{ + return assignString( getItem(itemParent, itemName, true), string, replace ); +} +inline bool NifModel::assignString( const QModelIndex & itemParent, const QLatin1String & itemName, const QString & string, bool replace ) +{ + return assignString( getItem(itemParent, itemName, true), string, replace ); +} +inline bool NifModel::assignString( const QModelIndex & itemParent, const char * itemName, const QString & string, bool replace ) +{ + return assignString( getItem(itemParent, QLatin1String(itemName), true), string, replace ); +} + + +// Link getters + +inline qint32 NifModel::getLink( const NifItem * item ) const +{ + return NifItem::valueToLink( item ); +} +inline qint32 NifModel::getLink( const NifItem * itemParent, int itemIndex ) const +{ + return getLink( getItem(itemParent, itemIndex) ); +} +inline qint32 NifModel::getLink( const NifItem * itemParent, const QString & itemName ) const +{ + return getLink( getItem(itemParent, itemName) ); +} +inline qint32 NifModel::getLink( const NifItem * itemParent, const QLatin1String & itemName ) const +{ + return getLink( getItem(itemParent, itemName) ); +} +inline qint32 NifModel::getLink( const NifItem * itemParent, const char * itemName ) const +{ + return getLink( getItem(itemParent, QLatin1String(itemName)) ); +} +inline qint32 NifModel::getLink( const QModelIndex & index ) const +{ + return getLink( getItem(index) ); +} +inline qint32 NifModel::getLink( const QModelIndex & itemParent, int itemIndex ) const +{ + return getLink( getItem(itemParent, itemIndex) ); +} +inline qint32 NifModel::getLink( const QModelIndex & itemParent, const QString & itemName ) const +{ + return getLink( getItem(itemParent, itemName) ); +} +inline qint32 NifModel::getLink( const QModelIndex & itemParent, const QLatin1String & itemName ) const +{ + return getLink( getItem(itemParent, itemName) ); +} +inline qint32 NifModel::getLink( const QModelIndex & itemParent, const char * itemName ) const +{ + return getLink( getItem(itemParent, QLatin1String(itemName)) ); +} + + +// Link setters + +inline bool NifModel::setLink( const NifItem * itemParent, int itemIndex, qint32 link ) +{ + return setLink( getItem(itemParent, itemIndex), link ); +} +inline bool NifModel::setLink( const NifItem * itemParent, const QString & itemName, qint32 link ) +{ + return setLink( getItem(itemParent, itemName), link ); +} +inline bool NifModel::setLink( const NifItem * itemParent, const QLatin1String & itemName, qint32 link ) +{ + return setLink( getItem(itemParent, itemName), link ); +} +inline bool NifModel::setLink( const NifItem * itemParent, const char * itemName, qint32 link ) +{ + return setLink( getItem(itemParent, QLatin1String(itemName)), link ); +} +inline bool NifModel::setLink( const QModelIndex & index, qint32 link ) +{ + return setLink( getItem(index), link ); +} +inline bool NifModel::setLink( const QModelIndex & itemParent, int itemIndex, qint32 link ) +{ + return setLink( getItem(itemParent, itemIndex), link ); +} +inline bool NifModel::setLink( const QModelIndex & itemParent, const QString & itemName, qint32 link ) +{ + return setLink( getItem(itemParent, itemName), link ); +} +inline bool NifModel::setLink( const QModelIndex & itemParent, const QLatin1String & itemName, qint32 link ) +{ + return setLink( getItem(itemParent, itemName), link ); +} +inline bool NifModel::setLink( const QModelIndex & itemParent, const char * itemName, qint32 link ) +{ + return setLink( getItem(itemParent, QLatin1String(itemName)), link ); +} + + +// Link array getters + +inline QVector NifModel::getLinkArray( const NifItem * arrayParent, int arrayIndex ) const +{ + return getLinkArray( getItem(arrayParent, arrayIndex) ); +} +inline QVector NifModel::getLinkArray( const NifItem * arrayParent, const QString & arrayName ) const +{ + return getLinkArray( getItem(arrayParent, arrayName) ); +} +inline QVector NifModel::getLinkArray( const NifItem * arrayParent, const QLatin1String & arrayName ) const +{ + return getLinkArray( getItem(arrayParent, arrayName) ); +} +inline QVector NifModel::getLinkArray( const NifItem * arrayParent, const char * arrayName ) const +{ + return getLinkArray( getItem(arrayParent, QLatin1String(arrayName)) ); +} +inline QVector NifModel::getLinkArray( const QModelIndex & iArray ) const +{ + return getLinkArray( getItem(iArray) ); +} +inline QVector NifModel::getLinkArray( const QModelIndex & arrayParent, int arrayIndex ) const +{ + return getLinkArray( getItem(arrayParent, arrayIndex) ); +} +inline QVector NifModel::getLinkArray( const QModelIndex & arrayParent, const QString & arrayName ) const +{ + return getLinkArray( getItem(arrayParent, arrayName) ); +} +inline QVector NifModel::getLinkArray( const QModelIndex & arrayParent, const QLatin1String & arrayName ) const +{ + return getLinkArray( getItem(arrayParent, arrayName) ); +} +inline QVector NifModel::getLinkArray( const QModelIndex & arrayParent, const char * arrayName ) const +{ + return getLinkArray( getItem(arrayParent, QLatin1String(arrayName)) ); +} + + +// Link array setters + +inline bool NifModel::setLinkArray( const NifItem * arrayParent, int arrayIndex, const QVector & links ) +{ + return setLinkArray( getItem(arrayParent, arrayIndex), links ); +} +inline bool NifModel::setLinkArray( const NifItem * arrayParent, const QString & arrayName, const QVector & links ) +{ + return setLinkArray( getItem(arrayParent, arrayName), links ); +} +inline bool NifModel::setLinkArray( const NifItem * arrayParent, const QLatin1String & arrayName, const QVector & links ) +{ + return setLinkArray( getItem(arrayParent, arrayName), links ); +} +inline bool NifModel::setLinkArray( const NifItem * arrayParent, const char * arrayName, const QVector & links ) +{ + return setLinkArray( getItem(arrayParent, QLatin1String(arrayName)), links ); +} +inline bool NifModel::setLinkArray( const QModelIndex & iArray, const QVector & links ) +{ + return setLinkArray( getItem(iArray), links ); +} +inline bool NifModel::setLinkArray( const QModelIndex & arrayParent, int arrayIndex, const QVector & links ) +{ + return setLinkArray( getItem(arrayParent, arrayIndex), links ); +} +inline bool NifModel::setLinkArray( const QModelIndex & arrayParent, const QString & arrayName, const QVector & links ) +{ + return setLinkArray( getItem(arrayParent, arrayName), links ); +} +inline bool NifModel::setLinkArray( const QModelIndex & arrayParent, const QLatin1String & arrayName, const QVector & links ) +{ + return setLinkArray( getItem(arrayParent, arrayName), links ); +} +inline bool NifModel::setLinkArray( const QModelIndex & arrayParent, const char * arrayName, const QVector & links ) { - return this->assignString( parent, name, d ); + return setLinkArray( getItem(arrayParent, QLatin1String(arrayName)), links ); } -//template <> inline bool NifModel::set( NifItem * parent, const QString & name, const QString & d ) { -// return this->assignString(parent, name, d); -//} #endif diff --git a/src/model/nifproxymodel.cpp b/src/model/nifproxymodel.cpp index 1898f65cb..1cb736213 100644 --- a/src/model/nifproxymodel.cpp +++ b/src/model/nifproxymodel.cpp @@ -413,7 +413,7 @@ QModelIndex NifProxyModel::mapTo( const QModelIndex & idx ) const if ( !item ) return QModelIndex(); - QModelIndex nifidx = nif->getBlock( item->block() ); + QModelIndex nifidx = nif->getBlockIndex( item->block() ); if ( nifidx.isValid() ) nifidx = nifidx.sibling( nifidx.row(), ( idx.column() ? NifModel::ValueCol : NifModel::NameCol ) ); diff --git a/src/model/undocommands.cpp b/src/model/undocommands.cpp index 4c894ce8f..35a4f4ad4 100644 --- a/src/model/undocommands.cpp +++ b/src/model/undocommands.cpp @@ -196,7 +196,7 @@ void ArrayUpdateCommand::redo() { if ( idx.isValid() ) { oldSize = nif->rowCount( idx ); - nif->updateArray( idx ); + nif->updateArraySize( idx ); newSize = nif->rowCount( idx ); } } @@ -205,6 +205,6 @@ void ArrayUpdateCommand::undo() { if ( idx.isValid() ) { // TODO: Actually attempt to set the array size back - nif->updateArray( idx ); + nif->updateArraySize( idx ); } } diff --git a/src/nifskope.cpp b/src/nifskope.cpp index a08412063..b326c8bc4 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -440,7 +440,7 @@ void NifSkope::select( const QModelIndex & index ) if ( sender() != list ) { if ( list->model() == proxy ) { - QModelIndex idxProxy = proxy->mapFrom( nif->getBlock( idx ), list->currentIndex() ); + QModelIndex idxProxy = proxy->mapFrom( nif->getBlockIndex( idx ), list->currentIndex() ); // Fix for NiDefaultAVObjectPalette (et al.) bug // mapFrom() stops at the first result for the given block number, @@ -473,13 +473,13 @@ void NifSkope::select( const QModelIndex & index ) } } else if ( list->model() == nif ) { - list->setCurrentIndex( nif->getBlockOrHeader( idx ) ); + list->setCurrentIndex( nif->getTopIndex( idx ) ); } } if ( sender() != tree ) { if ( dList->isVisible() ) { - QModelIndex root = nif->getBlockOrHeader( idx ); + QModelIndex root = nif->getTopIndex( idx ); if ( tree->rootIndex() != root ) tree->setRootIndex( root ); diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 04fba4b22..f778bd543 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -808,11 +808,11 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) // Scroll panel back to top tree->scrollTo( nif->index( 0, 0 ) ); - select( nif->getHeader() ); + select( nif->getHeaderIndex() ); - header->setRootIndex( nif->getHeader() ); + header->setRootIndex( nif->getHeaderIndex() ); // Refresh the header rows - header->updateConditions( nif->getHeader().child( 0, 0 ), nif->getHeader().child( 20, 0 ) ); + header->updateConditions( nif->getHeaderIndex().child( 0, 0 ), nif->getHeaderIndex().child( 20, 0 ) ); ogl->setOrientation( GLView::ViewFront ); @@ -1412,7 +1412,7 @@ void NifSkope::on_aHeader_triggered() if ( tree ) tree->clearRootIndex(); - select( nif->getHeader() ); + select( nif->getHeaderIndex() ); } diff --git a/src/spellbook.cpp b/src/spellbook.cpp index 44e60a9cf..060964dc7 100644 --- a/src/spellbook.cpp +++ b/src/spellbook.cpp @@ -140,7 +140,7 @@ void SpellBook::cast( NifModel * nif, const QModelIndex & index, SpellPtr spell nif->resetState(); // Refresh the header - nif->invalidateConditions( nif->getHeader(), true ); + nif->invalidateHeaderConditions(); nif->updateHeader(); if ( noSignals && nif->getProcessingResult() ) { diff --git a/src/spells/animation.cpp b/src/spells/animation.cpp index 3e724a9b0..2b152ed25 100644 --- a/src/spells/animation.cpp +++ b/src/spells/animation.cpp @@ -55,7 +55,7 @@ class spAttachKf final : public Spell QPersistentModelIndex iRoot; for ( const auto l : kf.getRootLinks() ) { - QModelIndex iSeq = kf.getBlock( l, "NiControllerSequence" ); + QModelIndex iSeq = kf.getBlockIndex( l, "NiControllerSequence" ); if ( !iSeq.isValid() ) throw QString( Spell::tr( "this is not a normal .kf file; there should be only NiControllerSequences as root blocks" ) ); @@ -80,17 +80,17 @@ class spAttachKf final : public Spell QStringList missingNodes; for ( const auto lSeq : kf.getRootLinks() ) { - QModelIndex iSeq = kf.getBlock( lSeq, "NiControllerSequence" ); + QModelIndex iSeq = kf.getBlockIndex( lSeq, "NiControllerSequence" ); QList controlledNodes; QModelIndex iCtrlBlcks = kf.getIndex( iSeq, "Controlled Blocks" ); for ( int r = 0; r < kf.rowCount( iCtrlBlcks ); r++ ) { - QString nodeName = kf.string( iCtrlBlcks.child( r, 0 ), "Node Name", false ); + QString nodeName = kf.resolveString( iCtrlBlcks.child( r, 0 ), "Node Name" ); if ( nodeName.isEmpty() ) - nodeName = kf.string( iCtrlBlcks.child( r, 0 ), "Target Name", false ); // 10.0.1.0 + nodeName = kf.resolveString( iCtrlBlcks.child( r, 0 ), "Target Name" ); // 10.0.1.0 if ( nodeName.isEmpty() ) { QModelIndex iNodeName = kf.getIndex( iCtrlBlcks.child( r, 0 ), "Node Name Offset" ); @@ -118,7 +118,7 @@ class spAttachKf final : public Spell setLinkArray( nif, iMultiTransformer, "Extra Targets", controlledNodes ); - QPersistentModelIndex iObjPalette = nif->getBlock( nif->getLink( iCtrlManager, "Object Palette" ), "NiDefaultAVObjectPalette" ); + QPersistentModelIndex iObjPalette = nif->getBlockIndex( nif->getLink( iCtrlManager, "Object Palette" ), "NiDefaultAVObjectPalette" ); if ( !iObjPalette.isValid() ) { iObjPalette = nif->insertNiBlock( "NiDefaultAVObjectPalette", nif->getBlockNumber( iCtrlManager ) + 1 ); @@ -139,9 +139,9 @@ class spAttachKf final : public Spell qint32 nSeq = map.value( lSeq ); int numSeq = nif->get( iCtrlManager, "Num Controller Sequences" ); nif->set( iCtrlManager, "Num Controller Sequences", numSeq + 1 ); - nif->updateArray( iCtrlManager, "Controller Sequences" ); + nif->updateArraySize( iCtrlManager, "Controller Sequences" ); nif->setLink( nif->getIndex( iCtrlManager, "Controller Sequences" ).child( numSeq, 0 ), nSeq ); - QModelIndex iSeq = nif->getBlock( nSeq, "NiControllerSequence" ); + QModelIndex iSeq = nif->getBlockIndex( nSeq, "NiControllerSequence" ); nif->setLink( iSeq, "Manager", nif->getBlockNumber( iCtrlManager ) ); QModelIndex iCtrlBlcks = nif->getIndex( iSeq, "Controlled Blocks" ); @@ -175,7 +175,7 @@ class spAttachKf final : public Spell static QModelIndex findChildNode( const NifModel * nif, const QModelIndex & parent, const QString & name ) { - if ( !nif->inherits( parent, "NiAVObject" ) ) + if ( !nif->blockInherits( parent, "NiAVObject" ) ) return QModelIndex(); QString thisName = nif->get( parent, "Name" ); @@ -184,7 +184,7 @@ class spAttachKf final : public Spell return parent; for ( const auto l : nif->getChildLinks( nif->getBlockNumber( parent ) ) ) { - QModelIndex child = findChildNode( nif, nif->getBlock( l ), name ); + QModelIndex child = findChildNode( nif, nif->getBlockIndex( l ), name ); if ( child.isValid() ) return child; @@ -196,7 +196,7 @@ class spAttachKf final : public Spell static QModelIndex findRootTarget( const NifModel * nif, const QString & name ) { for ( const auto l : nif->getRootLinks() ) { - QModelIndex root = findChildNode( nif, nif->getBlock( l ), name ); + QModelIndex root = findChildNode( nif, nif->getBlockIndex( l ), name ); if ( root.isValid() ) return root; @@ -208,10 +208,10 @@ class spAttachKf final : public Spell static QModelIndex findController( const NifModel * nif, const QModelIndex & node, const QString & ctrltype ) { for ( const auto l : nif->getChildLinks( nif->getBlockNumber( node ) ) ) { - QModelIndex iCtrl = nif->getBlock( l, "NiTimeController" ); + QModelIndex iCtrl = nif->getBlockIndex( l, "NiTimeController" ); if ( iCtrl.isValid() ) { - if ( nif->inherits( iCtrl, ctrltype ) ) + if ( nif->blockInherits( iCtrl, ctrltype ) ) return iCtrl; iCtrl = findController( nif, iCtrl, ctrltype ); @@ -255,7 +255,7 @@ class spAttachKf final : public Spell } nif->set( iNum, links.count() ); - nif->updateArray( iArray ); + nif->updateArraySize( iArray ); nif->setLinkArray( iArray, links ); } @@ -284,7 +284,7 @@ class spAttachKf final : public Spell int r = nif->get( iNum ); nif->set( iNum, r + blocksToAdd.count() ); - nif->updateArray( iArray ); + nif->updateArraySize( iArray ); for ( const QPersistentModelIndex& idx : blocksToAdd ) { nif->set( iArray.child( r, 0 ), "Name", nif->get( idx, "Name" ) ); nif->setLink( iArray.child( r, 0 ), "AV Object", nif->getBlockNumber( idx ) ); @@ -312,7 +312,7 @@ class spConvertQuatsToEulers final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iBlock = nif->getBlock( index, "NiKeyframeData" ); + QModelIndex iBlock = nif->getBlockIndex( index, "NiKeyframeData" ); return iBlock.isValid() && nif->get( iBlock, "Rotation Type" ) != 4; } @@ -322,7 +322,7 @@ class spConvertQuatsToEulers final : public Spell QModelIndex iQuats = nif->getIndex( index, "Quaternion Keys" ); int rotationType = nif->get( index, "Rotation Type" ); nif->set( index, "Rotation Type", 4 ); - nif->updateArray( index, "XYZ Rotations" ); + nif->updateArraySize( index, "XYZ Rotations" ); QModelIndex iRots = nif->getIndex( index, "XYZ Rotations" ); for( int i = 0; i < 3; i++ ) @@ -330,7 +330,7 @@ class spConvertQuatsToEulers final : public Spell QModelIndex iRot = iRots.child( i, 0 ); nif->set( iRot, "Num Keys", nif->get(index, "Num Rotation Keys") ); nif->set( iRot, "Interpolation", rotationType ); - nif->updateArray( iRot, "Keys" ); + nif->updateArraySize( iRot, "Keys" ); } for ( int q = 0; q < nif->rowCount( iQuats ); q++ ) @@ -369,14 +369,14 @@ class spFixAVObjectPalette final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iBlock = nif->getBlock( index, "NiDefaultAVObjectPalette" ); + QModelIndex iBlock = nif->getBlockIndex( index, "NiDefaultAVObjectPalette" ); return iBlock.isValid(); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - auto iHeader = nif->getHeader(); + auto iHeader = nif->getHeaderIndex(); auto numStrings = nif->get( iHeader, "Num Strings" ); auto strings = nif->getArray( iHeader, "Strings" ); @@ -400,10 +400,10 @@ class spFixAVObjectPalette final : public Spell auto stringIndex = strings.indexOf( name ); if ( stringIndex >= 0 ) { for ( int j = 0; j < numBlocks; j++ ) { - auto iBlock = nif->getBlock( j ); + auto iBlock = nif->getBlockIndex( j ); - if ( nif->inherits( iBlock, "NiAVObject" ) ) { - auto blockName = nif->get( nif->getBlock( j ), "Name" ); + if ( nif->blockInherits( iBlock, "NiAVObject" ) ) { + auto blockName = nif->get( nif->getBlockIndex( j ), "Name" ); if ( name == blockName ) { nif->setLink( iAV, j ); fixed++; diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 683bd32ec..064a92cf8 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -199,10 +199,10 @@ QStringList getStringsArray( NifModel * nif, const QModelIndex & parent, if ( name.isEmpty() ) { for ( int i = 0; i < nif->rowCount( iArr ); i++ ) - strings << nif->string( iArr.child( i, 0 ) ); + strings << nif->resolveString( iArr.child( i, 0 ) ); } else { for ( int i = 0; i < nif->rowCount( iArr ); i++ ) - strings << nif->string( iArr.child( i, 0 ), name, false ); + strings << nif->resolveString( iArr.child( i, 0 ), name ); } return strings; @@ -230,7 +230,7 @@ QStringList getNiObjectRootStrings( NifModel * nif, const QModelIndex & iBlock ) for ( int i = 0; i < nif->rowCount( iBlock ); i++ ) { auto iString = iBlock.child( i, 0 ); if ( rootStringList.contains( nif->itemName( iString ) ) ) - strings << nif->string( iString ); + strings << nif->resolveString( iString ); } return strings; @@ -278,12 +278,12 @@ QStringList getStringsNiSequence( NifModel * nif, const QModelIndex & iBlock ) for ( int i = 0; i < nif->rowCount( iControlledBlocks ); i++ ) { auto iChild = iControlledBlocks.child( i, 0 ); - strings << nif->string( iChild, "Target Name", false ) - << nif->string( iChild, "Node Name", false ) - << nif->string( iChild, "Property Type", false ) - << nif->string( iChild, "Controller Type", false ) - << nif->string( iChild, "Controller ID", false ) - << nif->string( iChild, "Interpolator ID", false ); + strings << nif->resolveString( iChild, "Target Name" ) + << nif->resolveString( iChild, "Node Name" ) + << nif->resolveString( iChild, "Property Type" ) + << nif->resolveString( iChild, "Controller Type" ) + << nif->resolveString( iChild, "Controller ID" ) + << nif->resolveString( iChild, "Interpolator ID" ); } return strings; @@ -363,7 +363,7 @@ bool addLink( NifModel * nif, const QModelIndex & iParent, const QString & array if ( iArray.isValid() && ( iArray.flags() & Qt::ItemIsEnabled ) ) { int numlinks = nif->get( iSize ); nif->set( iSize, numlinks + 1 ); - nif->updateArray( iArray ); + nif->updateArraySize( iArray ); nif->setLink( iArray.child( numlinks, 0 ), link ); return true; } @@ -374,7 +374,7 @@ bool addLink( NifModel * nif, const QModelIndex & iParent, const QString & array if ( nif->isArray( iArray ) && item ) { for ( int c = 0; c < item->childCount(); c++ ) { - if ( item->child( c )->value().toLink() == -1 ) { + if ( item->child( c )->valueToLink() == -1 ) { nif->setLink( iArray.child( c, 0 ), link ); return true; } @@ -401,7 +401,7 @@ void delLink( NifModel * nif, const QModelIndex & iParent, QString array, int li if ( iSize.isValid() && iArray.isValid() && links.contains( link ) ) { links.removeAll( link ); nif->set( iSize, links.count() ); - nif->updateArray( iArray ); + nif->updateArraySize( iArray ); nif->setLinkArray( iArray, links.toVector() ); } } @@ -415,42 +415,42 @@ void delLink( NifModel * nif, const QModelIndex & iParent, QString array, int li */ void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & iBlock ) { - if ( nif->isLink( index ) && nif->inherits( iBlock, nif->itemTmplt( index ) ) ) { + if ( nif->isLink( index ) && nif->blockInherits( iBlock, nif->itemTmplt( index ) ) ) { nif->setLink( index, nif->getBlockNumber( iBlock ) ); } - if ( nif->inherits( index, "NiNode" ) && nif->inherits( iBlock, "NiAVObject" ) ) { + if ( nif->blockInherits( index, "NiNode" ) && nif->blockInherits( iBlock, "NiAVObject" ) ) { addLink( nif, index, "Children", nif->getBlockNumber( iBlock ) ); - if ( nif->inherits( iBlock, "NiDynamicEffect" ) ) { + if ( nif->blockInherits( iBlock, "NiDynamicEffect" ) ) { addLink( nif, index, "Effects", nif->getBlockNumber( iBlock ) ); } - } else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiProperty" ) ) { + } else if ( nif->blockInherits( index, "NiAVObject" ) && nif->blockInherits( iBlock, "NiProperty" ) ) { if ( !addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ) ) { // Absent in Bethesda 20.2.0.7 stream version > 34 - if ( nif->inherits( nif->getBlockName( iBlock ), "BSShaderProperty" ) ) { + if ( nif->inherits( nif->itemName( iBlock ), "BSShaderProperty" ) ) { nif->setLink( index, "Shader Property", nif->getBlockNumber( iBlock ) ); - } else if ( nif->getBlockName( iBlock ) == "NiAlphaProperty" ) { + } else if ( nif->itemName( iBlock ) == "NiAlphaProperty" ) { nif->setLink( index, "Alpha Property", nif->getBlockNumber( iBlock ) ); } } - } else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiExtraData" ) ) { + } else if ( nif->blockInherits( index, "NiAVObject" ) && nif->blockInherits( iBlock, "NiExtraData" ) ) { addLink( nif, index, "Extra Data List", nif->getBlockNumber( iBlock ) ); - } else if ( nif->inherits( index, "NiObjectNET" ) && nif->inherits( iBlock, "NiTimeController" ) ) { + } else if ( nif->blockInherits( index, "NiObjectNET" ) && nif->blockInherits( iBlock, "NiTimeController" ) ) { if ( nif->getLink( index, "Controller" ) > 0 ) { - blockLink( nif, nif->getBlock( nif->getLink( index, "Controller" ) ), iBlock ); + blockLink( nif, nif->getBlockIndex( nif->getLink( index, "Controller" ) ), iBlock ); } else { nif->setLink( index, "Controller", nif->getBlockNumber( iBlock ) ); nif->setLink( iBlock, "Target", nif->getBlockNumber( index ) ); } - } else if ( nif->inherits( index, "NiTimeController" ) && nif->inherits( iBlock, "NiTimeController" ) ) { + } else if ( nif->blockInherits( index, "NiTimeController" ) && nif->blockInherits( iBlock, "NiTimeController" ) ) { if ( nif->getLink( index, "Next Controller" ) > 0 ) { - blockLink( nif, nif->getBlock( nif->getLink( index, "Next Controller" ) ), iBlock ); + blockLink( nif, nif->getBlockIndex( nif->getLink( index, "Next Controller" ) ), iBlock ); } else { nif->setLink( index, "Next Controller", nif->getBlockNumber( iBlock ) ); nif->setLink( iBlock, "Target", nif->getLink( index, "Target" ) ); } - } else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiCollisionObject" ) ) { + } else if ( nif->blockInherits( index, "NiAVObject" ) && nif->blockInherits( iBlock, "NiCollisionObject" ) ) { nif->setLink( index, "Collision Object", nif->getBlockNumber( iBlock ) ); } } @@ -466,7 +466,7 @@ static qint32 getBlockByName( NifModel * nif, const QString & tn ) return -1; for ( int b = 0; b < nif->getBlockCount(); b++ ) { - QModelIndex iBlock = nif->getBlock( b ); + QModelIndex iBlock = nif->getBlockIndex( b ); if ( nif->itemName( iBlock ) == type && nif->get( iBlock, "Name" ) == name ) return b; @@ -492,7 +492,7 @@ static void removeChildren( NifModel * nif, const QPersistentModelIndex & iBlock // Build list of child links QVector iChildren; for ( const auto link : nif->getChildLinks( nif->getBlockNumber( iBlock ) ) ) { - iChildren.append( nif->getBlock( link ) ); + iChildren.append( nif->getBlockIndex( link ) ); } // Remove children of child links @@ -715,10 +715,10 @@ class spAttachProperty final : public Spell return false; if ( nif->getUserVersion() < 12 ) - return nif->inherits( index, "NiAVObject" ); // Not Skyrim + return nif->blockInherits( index, "NiAVObject" ); // Not Skyrim // Skyrim and later - return nif->inherits( index, "NiGeometry" ) || nif->inherits( index, "BSTriShape" ); + return nif->blockInherits( index, "NiGeometry" ) || nif->blockInherits( index, "BSTriShape" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -727,7 +727,7 @@ class spAttachProperty final : public Spell QStringList ids = nif->allNiBlocks(); ids.sort(); for ( const QString& id : ids ) { - if ( (nif->inherits(index, "NiGeometry") || nif->inherits(index, "BSTriShape")) && nif->getBSVersion() > 34 ) { + if ( (nif->blockInherits(index, "NiGeometry") || nif->blockInherits(index, "BSTriShape")) && nif->getBSVersion() > 34 ) { if ( !(id == "BSLightingShaderProperty" || id == "BSEffectShaderProperty" || id == "NiAlphaProperty") ) continue; } @@ -747,7 +747,7 @@ class spAttachProperty final : public Spell if ( !addLink( nif, iParent, "Properties", nif->getBlockNumber( iProperty ) ) ) { // Skyrim and later - auto name = nif->getBlockName( iProperty ); + auto name = nif->itemName( iProperty ); if ( name == "BSLightingShaderProperty" || name == "BSEffectShaderProperty" ) { if ( !nif->setLink( iParent, "Shader Property", nif->getBlockNumber( iProperty ) ) ) { qCWarning( nsSpell ) << Spell::tr( "Failed to attach property." ); @@ -777,7 +777,7 @@ class spAttachNode final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->isNiBlock( index ) && nif->inherits( index, "NiNode" ); + return nif->isNiBlock( index ) && nif->blockInherits( index, "NiNode" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -830,13 +830,13 @@ class spAddNewRef final : public Spell std::list allIds = nif->allNiBlocks().toStdList(); blockFilter( nif, allIds, type ); - auto iBlock = nif->getBlock( index ); + auto iBlock = nif->getBlockIndex( index ); std::list ids; auto ctlrFilter = [nif, &ids, &allIds, &iBlock] ( QMultiMap m ) { auto i = m.begin(); while ( i != m.end() ) { - if ( nif->inherits( nif->getBlockName( iBlock ), i.key() ) ) + if ( nif->inherits( nif->itemName( iBlock ), i.key() ) ) for ( const auto & id : allIds ) if ( nif->inherits( id, i.value() ) ) ids.push_back( id ); @@ -847,7 +847,7 @@ class spAddNewRef final : public Spell auto interpFilter = [nif, &ids, &allIds, &iBlock]( QMultiMap m ) { auto i = m.begin(); while ( i != m.end() ) { - if ( nif->inherits( nif->getBlockName( iBlock ), i.key() ) ) + if ( nif->inherits( nif->itemName( iBlock ), i.key() ) ) for ( const auto & id : allIds ) for ( const auto & s : i.value() ) if ( nif->inherits( id, s ) ) @@ -858,8 +858,8 @@ class spAddNewRef final : public Spell if ( nif->inherits( type, "NiTimeController" ) ) { // Show only applicable types for controller links for the given block - if ( nif->inherits( iBlock, "NiTimeController" ) && item->hasName("Next Controller") ) - iBlock = nif->getBlock( nif->getLink( index.parent(), "Target" ) ); + if ( nif->blockInherits( iBlock, "NiTimeController" ) && item->hasName("Next Controller") ) + iBlock = nif->getBlockIndex( nif->getLink( index.parent(), "Target" ) ); if ( nif->getVersionNumber() > 0x14050000 ) { ctlrMapping.insertMulti( "NiNode", "NiSkinningLODController" ); @@ -870,7 +870,7 @@ class spAddNewRef final : public Spell if ( nif->getBSVersion() > 0 ) ctlrFilter( ctlrMappingBS ); - } else if ( nif->inherits( iBlock, "NiTimeController" ) + } else if ( nif->blockInherits( iBlock, "NiTimeController" ) && nif->inherits( type, "NiInterpolator" ) ) { // Show only applicable types for interpolator links for the given block interpFilter( interpMapping ); @@ -908,7 +908,7 @@ class spAddNewRef final : public Spell qCWarning( nsSpell ) << tr( "Failed to attach link." ); } - if ( nif->inherits( nif->getBlockName( newindex ), "NiTimeController" ) ) { + if ( nif->inherits( nif->itemName( newindex ), "NiTimeController" ) ) { auto blk = nif->getBlockNumber( iBlock ); nif->setLink( newindex, "Target", blk ); } @@ -936,7 +936,7 @@ class spAttachLight final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->isNiBlock( index ) && nif->inherits( index, "NiNode" ); + return nif->isNiBlock( index ) && nif->blockInherits( index, "NiNode" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -960,8 +960,8 @@ class spAttachLight final : public Spell if ( nif->checkVersion( 0, 0x04000002 ) ) { nif->set( iLight, "Num Affected Nodes", 1 ); - nif->updateArray( iLight, "Affected Nodes" ); - nif->updateArray( iLight, "Affected Node Pointers" ); + nif->updateArraySize( iLight, "Affected Nodes" ); + nif->updateArraySize( iLight, "Affected Node Pointers" ); } if ( act->text() == "NiTextureEffect" ) { @@ -988,7 +988,7 @@ class spAttachExtraData final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->isNiBlock( index ) && nif->inherits( index, "NiObjectNET" ) && nif->checkVersion( 0x0a000100, 0 ); + return nif->isNiBlock( index ) && nif->blockInherits( index, "NiObjectNET" ) && nif->checkVersion( 0x0a000100, 0 ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -1291,7 +1291,7 @@ QModelIndex spCopyBranch::cast( NifModel * nif, const QModelIndex & index ) for ( const auto link : nif->getParentLinks( block ) ) { if ( !blocks.contains( link ) && !parentMap.contains( link ) ) { QString failMessage = Spell::tr( "parent link invalid" ); - QModelIndex iParent = nif->getBlock( link ); + QModelIndex iParent = nif->getBlockIndex( link ); if ( iParent.isValid() ) { failMessage = Spell::tr( "parent unnamed" ); @@ -1306,9 +1306,9 @@ QModelIndex spCopyBranch::cast( NifModel * nif, const QModelIndex & index ) Message::append( tr( B_ERR ).arg( name() ), tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) .arg( link ) - .arg( nif->itemName( nif->getBlock( link ) ) ) + .arg( nif->itemName( nif->getBlockIndex( link ) ) ) .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) + .arg( nif->itemName( nif->getBlockIndex( block ) ) ) .arg( failMessage ), QMessageBox::Critical ); @@ -1327,7 +1327,7 @@ QModelIndex spCopyBranch::cast( NifModel * nif, const QModelIndex & index ) ds << parentMap; for ( const auto block : blocks ) { - auto iBlock = nif->getBlock( block ); + auto iBlock = nif->getBlockIndex( block ); auto bType = nif->createRTTIName( iBlock ); ds << bType; @@ -1569,20 +1569,20 @@ class spFlattenBranch final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ), "NiNode" ); - return nif->inherits( index, "NiNode" ) && iParent.isValid(); + QModelIndex iParent = nif->getBlockIndex( nif->getParent( nif->getBlockNumber( index ) ), "NiNode" ); + return nif->blockInherits( index, "NiNode" ) && iParent.isValid(); } QModelIndex cast( NifModel * nif, const QModelIndex & iNode ) override final { - QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( iNode ) ), "NiNode" ); + QModelIndex iParent = nif->getBlockIndex( nif->getParent( nif->getBlockNumber( iNode ) ), "NiNode" ); doNode( nif, iNode, iParent, Transform() ); return iNode; } void doNode( NifModel * nif, const QModelIndex & iNode, const QModelIndex & iParent, const Transform & tp ) { - if ( !nif->inherits( iNode, "NiNode" ) ) + if ( !nif->blockInherits( iNode, "NiNode" ) ) return; Transform t = tp * Transform( nif, iNode ); @@ -1590,7 +1590,7 @@ class spFlattenBranch final : public Spell QList links; for ( const auto l : nif->getLinkArray( iNode, "Children" ) ) { - QModelIndex iChild = nif->getBlock( l ); + QModelIndex iChild = nif->getBlockIndex( l ); if ( nif->getParent( nif->getBlockNumber( iChild ) ) == nif->getBlockNumber( iNode ) ) { Transform tc = t * Transform( nif, iChild ); @@ -1602,7 +1602,7 @@ class spFlattenBranch final : public Spell } for ( const auto l : links ) { - doNode( nif, nif->getBlock( l, "NiNode" ), iParent, tp ); + doNode( nif, nif->getBlockIndex( l, "NiNode" ), iParent, tp ); } } }; @@ -1626,7 +1626,7 @@ class spMoveBlockUp final : public Spell { int ix = nif->getBlockNumber( iBlock ); nif->moveNiBlock( ix, ix - 1 ); - return nif->getBlock( ix - 1 ); + return nif->getBlockIndex( ix - 1 ); } }; @@ -1649,7 +1649,7 @@ class spMoveBlockDown final : public Spell { int ix = nif->getBlockNumber( iBlock ); nif->moveNiBlock( ix, ix + 1 ); - return nif->getBlock( ix + 1 ); + return nif->getBlockIndex( ix + 1 ); } }; @@ -1687,7 +1687,7 @@ class spRemoveBlocksById final : public Spell int n = 0; while ( n < nif->getBlockCount() ) { - QModelIndex iBlock = nif->getBlock( n ); + QModelIndex iBlock = nif->getBlockIndex( n ); if ( nif->itemName( iBlock ).indexOf( exp ) >= 0 ) nif->removeNiBlock( n ); @@ -1774,7 +1774,7 @@ class spConvertBlock final : public Spell QStringList ids = nif->allNiBlocks(); ids.sort(); - QString btype = nif->getBlockName( index ); + QString btype = nif->itemName( index ); QMap map; for ( const QString& id : ids ) { @@ -1842,9 +1842,9 @@ class spDuplicateBlock final : public Spell if ( buffer.open( QIODevice::WriteOnly ) && nif->saveIndex( buffer, index ) ) { // from spPasteBlock if ( buffer.open( QIODevice::ReadOnly ) ) { - QModelIndex block = nif->insertNiBlock( nif->getBlockName( index ), nif->getBlockCount() ); + QModelIndex block = nif->insertNiBlock( nif->itemName( index ), nif->getBlockCount() ); nif->loadIndex( buffer, block ); - blockLink( nif, nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ) ), block ); + blockLink( nif, nif->getBlockIndex( nif->getParent( nif->getBlockNumber( index ) ) ), block ); return block; } } @@ -1879,7 +1879,7 @@ QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) for ( const auto link : nif->getParentLinks( block ) ) { if ( !blocks.contains( link ) && !parentMap.contains( link ) ) { QString failMessage = Spell::tr( "parent link invalid" ); - QModelIndex iParent = nif->getBlock( link ); + QModelIndex iParent = nif->getBlockIndex( link ); if ( iParent.isValid() ) { failMessage = Spell::tr( "parent unnamed" ); @@ -1894,9 +1894,9 @@ QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) Message::append( tr( B_ERR ).arg( name() ), tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) .arg( link ) - .arg( nif->itemName( nif->getBlock( link ) ) ) + .arg( nif->itemName( nif->getBlockIndex( link ) ) ) .arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ) + .arg( nif->itemName( nif->getBlockIndex( block ) ) ) .arg( failMessage ), QMessageBox::Critical ); @@ -1914,12 +1914,12 @@ QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) ds << blockMap; ds << parentMap; for ( const auto block : blocks ) { - ds << nif->itemName( nif->getBlock( block ) ); + ds << nif->itemName( nif->getBlockIndex( block ) ); - if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { + if ( !nif->saveIndex( buffer, nif->getBlockIndex( block ) ) ) { Message::append( tr( B_ERR ).arg( name() ), tr( "failed to save block %1 %2." ).arg( block ) - .arg( nif->itemName( nif->getBlock( block ) ) ), + .arg( nif->itemName( nif->getBlockIndex( block ) ) ), QMessageBox::Critical ); return index; @@ -1978,7 +1978,7 @@ QModelIndex spDuplicateBranch::cast( NifModel * nif, const QModelIndex & index ) iRoot = block; } nif->holdUpdates( false ); - blockLink( nif, nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ) ), iRoot ); + blockLink( nif, nif->getBlockIndex( nif->getParent( nif->getBlockNumber( index ) ) ), iRoot ); return iRoot; } @@ -2004,7 +2004,7 @@ class spSortBlockNames final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex iBlock = nif->getBlock( n ); + QModelIndex iBlock = nif->getBlockIndex( n ); if ( index.isValid() ) { iBlock = index; @@ -2022,7 +2022,7 @@ class spSortBlockNames final : public Spell qint32 l = nif->getLink( iChildren.child( r, 0 ) ); if ( l >= 0 ) - links.append( QPair( nif->get( nif->getBlock( l ), "Name" ), l ) ); + links.append( QPair( nif->get( nif->getBlockIndex( l ), "Name" ), l ) ); } std::stable_sort( links.begin(), links.end() ); @@ -2032,7 +2032,7 @@ class spSortBlockNames final : public Spell nif->setLink( iChildren.child( r, 0 ), links[r].second ); nif->set( iNumChildren, links.count() ); - nif->updateArray( iChildren ); + nif->updateArraySize( iChildren ); } } } @@ -2065,7 +2065,7 @@ class spAttachParentNode final : public Spell int thisBlockNumber = nif->getBlockNumber( index ); // find our parent; most functions won't break if it doesn't exist, // so we don't care if it doesn't exist - QModelIndex iParent = nif->getBlock( nif->getParent( thisBlockNumber ) ); + QModelIndex iParent = nif->getBlockIndex( nif->getParent( thisBlockNumber ) ); // find our index into the parent children array QVector parentChildLinks = nif->getLinkArray( iParent, "Children" ); diff --git a/src/spells/bounds.cpp b/src/spells/bounds.cpp index 53bd7c87d..bb85ca22e 100644 --- a/src/spells/bounds.cpp +++ b/src/spells/bounds.cpp @@ -28,7 +28,7 @@ class spEditBounds final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - NifBlockEditor * edit = new NifBlockEditor( nif, nif->getBlock( index ) ); + NifBlockEditor * edit = new NifBlockEditor( nif, nif->getBlockIndex( index ) ); if ( nif->get( index, "Has Bounding Box" ) == true || nif->itemName( index ) == "Bounding Box" || nif->itemName( index.parent() ) == "Bounding Box" ) { QModelIndex iBound; diff --git a/src/spells/color.cpp b/src/spells/color.cpp index cc205421c..cffe3be6a 100644 --- a/src/spells/color.cpp +++ b/src/spells/color.cpp @@ -63,9 +63,9 @@ class spSetAllColor final : public Spell auto typ = nif->getValue( colorIdx ).type(); if ( typ == NifValue::tColor3 ) - nif->setArray( index, ColorWheel::choose( nif->get( colorIdx ) ) ); + nif->fillArray( index, ColorWheel::choose( nif->get( colorIdx ) ) ); else if ( typ == NifValue::tColor4 ) - nif->setArray( index, ColorWheel::choose( nif->get( colorIdx ) ) ); + nif->fillArray( index, ColorWheel::choose( nif->get( colorIdx ) ) ); return index; } diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index 0be88fcbc..d13714524 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -53,14 +53,14 @@ class spEditFlags : public Spell if ( nif->isNiBlock( index ) ) return nif->getIndex( index, "Flags" ); - if ( nif->inherits( nif->getBlock( index ), "bhkRigidBody" ) ) { - QModelIndex iFlags = nif->getIndex( nif->getBlock( index ), "Col Filter" ); + if ( nif->blockInherits( index, "bhkRigidBody" ) ) { + QModelIndex iFlags = nif->getIndex( nif->getBlockIndex( index ), "Col Filter" ); iFlags = iFlags.sibling( iFlags.row(), NifModel::ValueCol ); if ( index == iFlags ) return iFlags; - } else if ( nif->inherits( nif->getBlock( index ), "BSXFlags" ) ) { - QModelIndex iFlags = nif->getIndex( nif->getBlock( index ), "Integer Data" ); + } else if ( nif->blockInherits( index, "BSXFlags" ) ) { + QModelIndex iFlags = nif->getIndex( nif->getBlockIndex( index ), "Integer Data" ); iFlags = iFlags.sibling( iFlags.row(), NifModel::ValueCol ); if ( index == iFlags ) @@ -104,7 +104,7 @@ class spEditFlags : public Spell return ZBuffer; } else if ( name == "BSXFlags" ) { return BSX; - } else if ( nif->inherits( index.parent(), "NiAVObject" ) && nif->getVersionNumber() == 0x14020007 ) { + } else if ( nif->blockInherits( index.parent(), "NiAVObject" ) && nif->getVersionNumber() == 0x14020007 ) { return NiAVObject; } } @@ -226,7 +226,7 @@ class spEditFlags : public Spell cmbTest->setCurrentIndex( flags >> 10 & 0x07 ); QSpinBox * spnTest = dlgSpin( vbox, Spell::tr( "Alpha Test Threshold" ), 0x00, 0xff ); - spnTest->setValue( nif->get( nif->getBlock( index ), "Threshold" ) ); + spnTest->setValue( nif->get( nif->getBlockIndex( index ), "Threshold" ) ); QCheckBox * chkSort = dlgCheck( vbox, Spell::tr( "No Sorter" ) ); chkSort->setChecked( ( flags & 0x2000 ) != 0 ); @@ -250,7 +250,7 @@ class spEditFlags : public Spell } flags = ( flags & 0xe3ff ) | ( cmbTest->currentIndex() << 10 ); - nif->set( nif->getBlock( index ), "Threshold", spnTest->value() ); + nif->set( nif->getBlockIndex( index ), "Threshold", spnTest->value() ); flags = ( flags & 0xdfff ) | ( chkSort->isChecked() ? 0x2000 : 0 ); @@ -427,7 +427,7 @@ class spEditFlags : public Spell QComboBox * cmbFunc = dlgCombo( vbox, Spell::tr( "Z Buffer Test Function" ), compareFunc, chkEnable ); if ( nif->checkVersion( 0x0401000C, 0x14000005 ) ) - cmbFunc->setCurrentIndex( nif->get( nif->getBlock( index ), "Function" ) ); + cmbFunc->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Function" ) ); else cmbFunc->setCurrentIndex( ( flags >> 2 ) & 0x07 ); @@ -447,7 +447,7 @@ class spEditFlags : public Spell flags = ( flags & 0xfffd ) | ( chkROnly->isChecked() ? 0 : 2 ); if ( nif->checkVersion( 0x0401000C, 0x14000005 ) ) { - nif->set( nif->getBlock( index ), "Function", cmbFunc->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Function", cmbFunc->currentIndex() ); } if ( nif->checkVersion( 0x14010003, 0 ) || ( setFlags != 0 && setFlags->isChecked() ) ) { @@ -607,7 +607,7 @@ class spEditFlags : public Spell // this value doesn't exist before 10.1.0.0 // ROTATE_ABOUT_UP2 is too hard to put in and possibly meaningless cmbMode->addItem( Spell::tr( "Rigid Face Center" ) ); - cmbMode->setCurrentIndex( nif->get( nif->getBlock( index ), "Billboard Mode" ) ); + cmbMode->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Billboard Mode" ) ); } else { cmbMode->setCurrentIndex( flags >> 5 & 3 ); } @@ -619,7 +619,7 @@ class spEditFlags : public Spell flags = ( flags & 0xfff9 ) | ( cmbCollision->currentIndex() << 1); if ( nif->checkVersion( 0x0A010000, 0 ) ) { - nif->set( nif->getBlock( index ), "Billboard Mode", cmbMode->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Billboard Mode", cmbMode->currentIndex() ); } else { flags = ( flags & 0xff9f ) | ( cmbMode->currentIndex() << 5 ); flags = ( flags & 0xfff7 ) | 8; // seems to always be set but has no known effect @@ -684,12 +684,12 @@ class spEditFlags : public Spell if ( nif->checkVersion( 0, 0x14000005 ) ) { // set based on Stencil Enabled, Stencil Function, Fail Action, Z Fail Action, Pass Action, Draw Mode // Possibly include Stencil Ref and Stencil Mask except they don't seem to ever vary from the default - chkEnable->setChecked( nif->get( nif->getBlock( index ), "Stencil Enabled" ) ); - cmbFail->setCurrentIndex( nif->get( nif->getBlock( index ), "Fail Action" ) ); - cmbZFail->setCurrentIndex( nif->get( nif->getBlock( index ), "Z Fail Action" ) ); - cmbPass->setCurrentIndex( nif->get( nif->getBlock( index ), "Pass Action" ) ); - cmbDrawMode->setCurrentIndex( nif->get( nif->getBlock( index ), "Draw Mode" ) ); - cmbFunc->setCurrentIndex( nif->get( nif->getBlock( index ), "Stencil Function" ) ); + chkEnable->setChecked( nif->get( nif->getBlockIndex( index ), "Stencil Enabled" ) ); + cmbFail->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Fail Action" ) ); + cmbZFail->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Z Fail Action" ) ); + cmbPass->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Pass Action" ) ); + cmbDrawMode->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Draw Mode" ) ); + cmbFunc->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Stencil Function" ) ); } else { // set based on flags itself chkEnable->setChecked( flags & 1 ); @@ -704,12 +704,12 @@ class spEditFlags : public Spell if ( dlg.exec() == QDialog::Accepted ) { if ( nif->checkVersion( 0, 0x14000005 ) ) { - nif->set( nif->getBlock( index ), "Stencil Enabled", chkEnable->isChecked() ); - nif->set( nif->getBlock( index ), "Fail Action", cmbFail->currentIndex() ); - nif->set( nif->getBlock( index ), "Z Fail Action", cmbZFail->currentIndex() ); - nif->set( nif->getBlock( index ), "Pass Action", cmbPass->currentIndex() ); - nif->set( nif->getBlock( index ), "Draw Mode", cmbDrawMode->currentIndex() ); - nif->set( nif->getBlock( index ), "Stencil Function", cmbFunc->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Stencil Enabled", chkEnable->isChecked() ); + nif->set( nif->getBlockIndex( index ), "Fail Action", cmbFail->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Z Fail Action", cmbZFail->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Pass Action", cmbPass->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Draw Mode", cmbDrawMode->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Stencil Function", cmbFunc->currentIndex() ); } else { flags = ( flags & 0xfffe ) | ( chkEnable->isChecked() ? 1 : 0 ); flags = ( flags & 0xfff1 ) | ( cmbFail->currentIndex() << 1 ); @@ -756,8 +756,8 @@ class spEditFlags : public Spell // Use enums in preference to flags since they probably have a higher priority if ( nif->checkVersion( 0, 0x14000005 ) ) { - cmbLight->setCurrentIndex( nif->get( nif->getBlock( index ), "Lighting Mode" ) ); - cmbVert->setCurrentIndex( nif->get( nif->getBlock( index ), "Vertex Mode" ) ); + cmbLight->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Lighting Mode" ) ); + cmbVert->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Vertex Mode" ) ); } else { cmbLight->setCurrentIndex( flags >> 3 & 1 ); cmbVert->setCurrentIndex( flags >> 4 & 3 ); @@ -767,8 +767,8 @@ class spEditFlags : public Spell if ( dlg.exec() == QDialog::Accepted ) { if ( nif->checkVersion( 0, 0x14000005 ) ) { - nif->set( nif->getBlock( index ), "Lighting Mode", cmbLight->currentIndex() ); - nif->set( nif->getBlock( index ), "Vertex Mode", cmbVert->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Lighting Mode", cmbLight->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Vertex Mode", cmbVert->currentIndex() ); } if ( nif->checkVersion( 0x14010003, 0 ) || ( setFlags != 0 && setFlags->isChecked() ) ) { @@ -807,7 +807,7 @@ class spEditFlags : public Spell // Target Color enum exists as of 10.1.0.0 if ( nif->checkVersion( 0x0A010000, 0 ) ) { - cmbColor->setCurrentIndex( nif->get( nif->getBlock( index ), "Target Color" ) ); + cmbColor->setCurrentIndex( nif->get( nif->getBlockIndex( index ), "Target Color" ) ); } else { cmbColor->setCurrentIndex( flags >> 4 & 3 ); } @@ -819,7 +819,7 @@ class spEditFlags : public Spell flags = ( flags & 0xfff9 ) | ( cmbLoop->currentIndex() << 1 ); if ( nif->checkVersion( 0x0A010000, 0 ) ) { - nif->set( nif->getBlock( index ), "Target Color", cmbColor->currentIndex() ); + nif->set( nif->getBlockIndex( index ), "Target Color", cmbColor->currentIndex() ); } else { flags = ( flags & 0xffcf ) | ( cmbColor->currentIndex() << 4 ); } @@ -971,13 +971,13 @@ class spEditVertexDesc final : public spEditFlags bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->inherits( index.parent(), "BSTriShape" ) && nif->itemName( index ) == "Vertex Desc"; + return nif->blockInherits( index.parent(), "BSTriShape" ) && nif->itemName( index ) == "Vertex Desc"; } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { auto desc = nif->get( index ); - bool dynamic = nif->inherits( index.parent(), "BSDynamicTriShape" ); + bool dynamic = nif->blockInherits( index.parent(), "BSDynamicTriShape" ); QStringList flagNames { Spell::tr( "Vertex" ), // VA_POSITION = 0x0, @@ -1033,7 +1033,7 @@ class spEditVertexDesc final : public spEditFlags if ( iDataSize.isValid() ) nif->set( iDataSize, desc.GetVertexSize() * numVerts + 6 * numTris ); - nif->updateArray( index.parent(), "Vertex Data" ); + nif->updateArraySize( index.parent(), "Vertex Data" ); } } diff --git a/src/spells/fo3only.cpp b/src/spells/fo3only.cpp index 8409cf45b..dc6fc5adf 100644 --- a/src/spells/fo3only.cpp +++ b/src/spells/fo3only.cpp @@ -26,19 +26,19 @@ class spFO3FixShapeDataName final : public Spell if ( !nif->checkVersion( 0x14020007, 0x14020007 ) || (nif->getUserVersion() != 11) ) return false; - return !index.isValid() || nif->getBlock( index, "NiGeometryData" ).isValid(); + return !index.isValid() || nif->getBlockIndex( index, "NiGeometryData" ).isValid(); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - if ( index.isValid() && nif->getBlock( index, "NiGeometryData" ).isValid() ) { + if ( index.isValid() && nif->getBlockIndex( index, "NiGeometryData" ).isValid() ) { nif->set( index, "Unknown ID", 0 ); } else { // set all blocks for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex iBlock = nif->getBlock( n ); + QModelIndex iBlock = nif->getBlockIndex( n ); - if ( nif->getBlock( iBlock, "NiGeometryData" ).isValid() ) { + if ( nif->getBlockIndex( iBlock, "NiGeometryData" ).isValid() ) { cast( nif, iBlock ); } } diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index a7dfc6799..d23ff8a42 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -36,11 +36,11 @@ class spCreateCVS final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - if ( !(nif->inherits( index, "NiTriBasedGeom" ) || nif->inherits( index, "BSTriShape" )) + if ( !(nif->blockInherits( index, "NiTriBasedGeom" ) || nif->blockInherits( index, "BSTriShape" )) || !nif->getBSVersion() ) return false; - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); if ( !iData.isValid() && nif->getIndex( index, "Vertex Data" ).isValid() ) iData = index; @@ -49,7 +49,7 @@ class spCreateCVS final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); if ( !iData.isValid() ) iData = nif->getIndex( index, "Vertex Data" ); @@ -176,21 +176,21 @@ class spCreateCVS final : public Spell /* set CVS verts */ nif->set( iCVS, "Num Vertices", convex_verts.count() ); - nif->updateArray( iCVS, "Vertices" ); + nif->updateArraySize( iCVS, "Vertices" ); nif->setArray( iCVS, "Vertices", convex_verts ); /* set CVS norms */ nif->set( iCVS, "Num Normals", convex_norms.count() ); - nif->updateArray( iCVS, "Normals" ); + nif->updateArraySize( iCVS, "Normals" ); nif->setArray( iCVS, "Normals", convex_norms ); // radius is always 0.1? // TODO: Figure out if radius is not arbitrarily set in vanilla NIFs nif->set( iCVS, "Radius", spnRadius->value() ); - QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ) ); + QModelIndex iParent = nif->getBlockIndex( nif->getParent( nif->getBlockNumber( index ) ) ); QModelIndex collisionLink = nif->getIndex( iParent, "Collision Object" ); - QModelIndex collisionObject = nif->getBlock( nif->getLink( collisionLink ) ); + QModelIndex collisionObject = nif->getBlockIndex( nif->getLink( collisionLink ) ); // create bhkCollisionObject if ( !collisionObject.isValid() ) { @@ -201,7 +201,7 @@ class spCreateCVS final : public Spell } QModelIndex rigidBodyLink = nif->getIndex( collisionObject, "Body" ); - QModelIndex rigidBody = nif->getBlock( nif->getLink( rigidBodyLink ) ); + QModelIndex rigidBody = nif->getBlockIndex( nif->getLink( rigidBodyLink ) ); // create bhkRigidBody if ( !rigidBody.isValid() ) { @@ -211,7 +211,7 @@ class spCreateCVS final : public Spell } QPersistentModelIndex shapeLink = nif->getIndex( rigidBody, "Shape" ); - QPersistentModelIndex shape = nif->getBlock( nif->getLink( shapeLink ) ); + QPersistentModelIndex shape = nif->getBlockIndex( nif->getLink( shapeLink ) ); QVector shapeLinks; bool replace = true; @@ -222,7 +222,7 @@ class spCreateCVS final : public Spell QString questionBody = tr( "This collision object already has a shape. Combine into a list shape? 'No' will replace the shape." ); bool isListShape = false; - if ( nif->inherits( shape, "bhkListShape" ) ) { + if ( nif->blockInherits( shape, "bhkListShape" ) ) { isListShape = true; questionTitle = tr( "Add to List Shape" ); questionBody = tr( "This collision object already has a list shape. Add to list shape? 'No' will replace the list shape." ); @@ -239,10 +239,10 @@ class spCreateCVS final : public Spell shapeLinks << nif->getBlockNumber( iCVS ); nif->set( iListShape, "Num Sub Shapes", shapeLinks.size() ); - nif->updateArray( iListShape, "Sub Shapes" ); + nif->updateArraySize( iListShape, "Sub Shapes" ); nif->setLinkArray( iListShape, "Sub Shapes", shapeLinks ); nif->set( iListShape, "Num Unknown Ints", shapeLinks.size() ); - nif->updateArray( iListShape, "Unknown Ints" ); + nif->updateArraySize( iListShape, "Unknown Ints" ); replace = false; } } @@ -276,20 +276,20 @@ class spConstraintHelper final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif && - nif->isNiBlock( nif->getBlock( index ), - { "bhkMalleableConstraint", + static QStringList blockNames = { + "bhkMalleableConstraint", "bhkBreakableConstraint", "bhkRagdollConstraint", "bhkLimitedHingeConstraint", "bhkHingeConstraint", - "bhkPrismaticConstraint" } - ); + "bhkPrismaticConstraint" + }; + return nif && nif->isNiBlock( nif->getBlockIndex( index ), blockNames ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iConstraint = nif->getBlock( index ); + QModelIndex iConstraint = nif->getBlockIndex( index ); QString name = nif->itemName( iConstraint ); if ( name == "bhkMalleableConstraint" || name == "bhkBreakableConstraint" ) { @@ -302,8 +302,8 @@ class spConstraintHelper final : public Spell } } - QModelIndex iBodyA = nif->getBlock( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity A" ) ), "bhkRigidBody" ); - QModelIndex iBodyB = nif->getBlock( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity B" ) ), "bhkRigidBody" ); + QModelIndex iBodyA = nif->getBlockIndex( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity A" ) ), "bhkRigidBody" ); + QModelIndex iBodyB = nif->getBlockIndex( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity B" ) ), "bhkRigidBody" ); if ( !iBodyA.isValid() || !iBodyB.isValid() ) { Message::warning( nullptr, Spell::tr( "Couldn't find the bodies for this constraint." ) ); @@ -386,18 +386,18 @@ class spStiffSpringHelper final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & idx ) override final { - return nif && nif->isNiBlock( nif->getBlock( idx ), "bhkStiffSpringConstraint" ); + return nif && nif->isNiBlock( nif->getBlockIndex( idx ), "bhkStiffSpringConstraint" ); } QModelIndex cast( NifModel * nif, const QModelIndex & idx ) override final { - QModelIndex iConstraint = nif->getBlock( idx ); + QModelIndex iConstraint = nif->getBlockIndex( idx ); QModelIndex iSpring = nif->getIndex( iConstraint, "Stiff Spring" ); if ( !iSpring.isValid() ) iSpring = iConstraint; - QModelIndex iBodyA = nif->getBlock( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity A" ) ), "bhkRigidBody" ); - QModelIndex iBodyB = nif->getBlock( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity B" ) ), "bhkRigidBody" ); + QModelIndex iBodyA = nif->getBlockIndex( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity A" ) ), "bhkRigidBody" ); + QModelIndex iBodyB = nif->getBlockIndex( nif->getLink( bhkGetEntity( nif, iConstraint, "Entity B" ) ), "bhkRigidBody" ); if ( !iBodyA.isValid() || !iBodyB.isValid() ) { Message::warning( nullptr, Spell::tr( "Couldn't find the bodies for this constraint" ) ); @@ -441,7 +441,7 @@ class spPackHavokStrips final : public Spell QVector normals; for ( const auto lData : nif->getLinkArray( iShape, "Strips Data" ) ) { - QModelIndex iData = nif->getBlock( lData, "NiTriStripsData" ); + QModelIndex iData = nif->getBlockIndex( lData, "NiTriStripsData" ); if ( iData.isValid() ) { QVector vrts = nif->getArray( iData, "Vertices" ); @@ -487,7 +487,7 @@ class spPackHavokStrips final : public Spell nif->set( iPackedShape, "Num Sub Shapes", 1 ); QModelIndex iSubShapes = nif->getIndex( iPackedShape, "Sub Shapes" ); - nif->updateArray( iSubShapes ); + nif->updateArraySize( iSubShapes ); nif->set( iSubShapes.child( 0, 0 ), "Layer", 1 ); nif->set( iSubShapes.child( 0, 0 ), "Num Vertices", vertices.count() ); nif->set( iSubShapes.child( 0, 0 ), "Material", nif->get( iShape, "Material" ) ); @@ -500,7 +500,7 @@ class spPackHavokStrips final : public Spell nif->set( iPackedData, "Num Triangles", triangles.count() ); QModelIndex iTriangles = nif->getIndex( iPackedData, "Triangles" ); - nif->updateArray( iTriangles ); + nif->updateArraySize( iTriangles ); for ( int t = 0; t < triangles.size(); t++ ) { nif->set( iTriangles.child( t, 0 ), "Triangle", triangles[ t ] ); @@ -509,7 +509,7 @@ class spPackHavokStrips final : public Spell nif->set( iPackedData, "Num Vertices", vertices.count() ); QModelIndex iVertices = nif->getIndex( iPackedData, "Vertices" ); - nif->updateArray( iVertices ); + nif->updateArraySize( iVertices ); nif->setArray( iVertices, vertices ); QMap lnkmap; @@ -543,7 +543,7 @@ class spConvertListShape final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) override final { QPersistentModelIndex iShape( iBlock ); - QPersistentModelIndex iRigidBody = nif->getBlock( nif->getParent( iShape ) ); + QPersistentModelIndex iRigidBody = nif->getBlockIndex( nif->getParent( iShape ) ); if ( !iRigidBody.isValid() ) return {}; @@ -551,7 +551,7 @@ class spConvertListShape final : public Spell nif->set( iCLS, "Num Sub Shapes", nif->get( iShape, "Num Sub Shapes" ) ); nif->set( iCLS, "Material", nif->get( iShape, "Material" ) ); - nif->updateArray( iCLS, "Sub Shapes" ); + nif->updateArraySize( iCLS, "Sub Shapes" ); nif->setLinkArray( iCLS, "Sub Shapes", nif->getLinkArray( iShape, "Sub Shapes" ) ); nif->setLinkArray( iShape, "Sub Shapes", {} ); @@ -580,7 +580,7 @@ class spConvertConvexListShape final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) override final { QPersistentModelIndex iShape( iBlock ); - QPersistentModelIndex iRigidBody = nif->getBlock( nif->getParent( iShape ) ); + QPersistentModelIndex iRigidBody = nif->getBlockIndex( nif->getParent( iShape ) ); if ( !iRigidBody.isValid() ) return {}; @@ -589,8 +589,8 @@ class spConvertConvexListShape final : public Spell nif->set( iLS, "Num Sub Shapes", nif->get( iShape, "Num Sub Shapes" ) ); nif->set( iLS, "Num Unknown Ints", nif->get( iShape, "Num Sub Shapes" ) ); nif->set( iLS, "Material", nif->get( iShape, "Material" ) ); - nif->updateArray( iLS, "Sub Shapes" ); - nif->updateArray( iLS, "Unknown Ints" ); + nif->updateArraySize( iLS, "Sub Shapes" ); + nif->updateArraySize( iLS, "Unknown Ints" ); nif->setLinkArray( iLS, "Sub Shapes", nif->getLinkArray( iShape, "Sub Shapes" ) ); nif->setLinkArray( iShape, "Sub Shapes", {} ); diff --git a/src/spells/headerstring.cpp b/src/spells/headerstring.cpp index 0a2519100..ab30005d1 100644 --- a/src/spells/headerstring.cpp +++ b/src/spells/headerstring.cpp @@ -80,13 +80,14 @@ class spEditStringIndex final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - NifValue::Type type = nif->getValue( index ).type(); - - if ( type == NifValue::tStringIndex ) - return true; - - if ( (type == NifValue::tString || type == NifValue::tFilePath) && nif->checkVersion( 0x14010003, 0 ) ) - return true; + const NifItem * item = nif->getItem( index ); + if ( item ) { + auto vt = item->valueType(); + if ( vt == NifValue::tStringIndex ) + return true; + if ( nif->checkVersion( 0x14010003, 0 ) && ( vt == NifValue::tString || vt == NifValue::tFilePath ) ) + return true; + } return false; } @@ -100,7 +101,7 @@ class spEditStringIndex final : public Spell if ( nif->getValue( index ).type() != NifValue::tStringIndex || !nif->checkVersion( 0x14010003, 0 ) ) return index; - QModelIndex header = nif->getHeader(); + QModelIndex header = nif->getHeaderIndex(); QVector stringVector = nif->getArray( header, "Strings" ); strings = stringVector.toList(); diff --git a/src/spells/light.cpp b/src/spells/light.cpp index 1db937a1d..2918a5646 100644 --- a/src/spells/light.cpp +++ b/src/spells/light.cpp @@ -68,14 +68,14 @@ class spLightEdit final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iBlock = nif->getBlock( index ); + QModelIndex iBlock = nif->getBlockIndex( index ); QModelIndex sibling = index.sibling( index.row(), 0 ); - return index.isValid() && nif->inherits( iBlock, "NiLight" ) && ( iBlock == sibling || nif->getIndex( iBlock, "Name" ) == sibling ); + return index.isValid() && nif->blockInherits( iBlock, "NiLight" ) && ( iBlock == sibling || nif->getIndex( iBlock, "Name" ) == sibling ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iLight = nif->getBlock( index ); + QModelIndex iLight = nif->getBlockIndex( index ); NifBlockEditor * le = new NifBlockEditor( nif, iLight ); le->pushLayout( new QHBoxLayout() ); diff --git a/src/spells/materialedit.cpp b/src/spells/materialedit.cpp index b9b40716c..ae92bb81f 100644 --- a/src/spells/materialedit.cpp +++ b/src/spells/materialedit.cpp @@ -108,14 +108,14 @@ class spMaterialEdit final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iBlock = nif->getBlock( index, "NiMaterialProperty" ); + QModelIndex iBlock = nif->getBlockIndex( index, "NiMaterialProperty" ); QModelIndex sibling = index.sibling( index.row(), 0 ); return index.isValid() && ( iBlock == sibling || nif->getIndex( iBlock, "Name" ) == sibling ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iMaterial = nif->getBlock( index ); + QModelIndex iMaterial = nif->getBlockIndex( index ); NifBlockEditor * me = new NifBlockEditor( nif, iMaterial ); me->pushLayout( new QHBoxLayout ); diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index 9377752e7..a8d61ded2 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -17,13 +17,13 @@ //! Find shape data of triangle geometry static QModelIndex getShape( const NifModel * nif, const QModelIndex & index ) { - QModelIndex iShape = nif->getBlock( index ); + QModelIndex iShape = nif->getBlockIndex( index ); if ( nif->isNiBlock( iShape, "NiTriBasedGeomData" ) ) - iShape = nif->getBlock( nif->getParent( nif->getBlockNumber( iShape ) ) ); + iShape = nif->getBlockIndex( nif->getParent( nif->getBlockNumber( iShape ) ) ); if ( nif->isNiBlock( iShape, { "NiTriShape", "BSLODTriShape", "NiTriStrips" } ) ) - if ( nif->getBlock( nif->getLink( iShape, "Data" ), "NiTriBasedGeomData" ).isValid() ) + if ( nif->getBlockIndex( nif->getLink( iShape, "Data" ), "NiTriBasedGeomData" ).isValid() ) return iShape; @@ -33,15 +33,15 @@ static QModelIndex getShape( const NifModel * nif, const QModelIndex & index ) //! Find triangle geometry /*! * Subtly different to getShape(); that requires - * nif->getBlock( nif->getLink( getShape( nif, index ), "Data" ) ); + * nif->getBlockIndex( nif->getLink( getShape( nif, index ), "Data" ) ); * to return the same result. */ static QModelIndex getTriShapeData( const NifModel * nif, const QModelIndex & index ) { - QModelIndex iData = nif->getBlock( index ); + QModelIndex iData = nif->getBlockIndex( index ); if ( nif->isNiBlock( index, { "NiTriShape", "BSLODTriShape" } ) ) - iData = nif->getBlock( nif->getLink( index, "Data" ) ); + iData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); if ( nif->isNiBlock( iData, "NiTriShapeData" ) ) return iData; @@ -168,23 +168,23 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons nif->setArray( iPoints.child( r, 0 ), strips[r] ); nif->set( iData, "Num Vertices", verts.count() ); - nif->updateArray( iData, "Vertices" ); + nif->updateArraySize( iData, "Vertices" ); nif->setArray( iData, "Vertices", verts ); - nif->updateArray( iData, "Normals" ); + nif->updateArraySize( iData, "Normals" ); nif->setArray( iData, "Normals", norms ); - nif->updateArray( iData, "Vertex Colors" ); + nif->updateArraySize( iData, "Vertex Colors" ); nif->setArray( iData, "Vertex Colors", colors ); for ( int r = 0; r < nif->rowCount( iUVSets ); r++ ) { - nif->updateArray( iUVSets.child( r, 0 ) ); + nif->updateArraySize( iUVSets.child( r, 0 ) ); nif->setArray( iUVSets.child( r, 0 ), texco[r] ); } // process NiSkinData - QModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); + QModelIndex iSkinInst = nif->getBlockIndex( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); - QModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ); + QModelIndex iSkinData = nif->getBlockIndex( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ); QModelIndex iBones = nif->getIndex( iSkinData, "Bone List" ); for ( int b = 0; b < nif->rowCount( iBones ); b++ ) { @@ -210,7 +210,7 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons } nif->set( iBones.child( b, 0 ), "Num Vertices", weights.count() ); - nif->updateArray( iWeights ); + nif->updateArraySize( iWeights ); for ( int w = 0; w < weights.count(); w++ ) { nif->set( iWeights.child( w, 0 ), "Index", weights[w].first ); @@ -220,10 +220,10 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons // process NiSkinPartition - QModelIndex iSkinPart = nif->getBlock( nif->getLink( iSkinInst, "Skin Partition" ), "NiSkinPartition" ); + QModelIndex iSkinPart = nif->getBlockIndex( nif->getLink( iSkinInst, "Skin Partition" ), "NiSkinPartition" ); if ( !iSkinPart.isValid() ) - iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); + iSkinPart = nif->getBlockIndex( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); if ( iSkinPart.isValid() ) { nif->removeNiBlock( nif->getBlockNumber( iSkinPart ) ); @@ -245,7 +245,7 @@ class spFlipTexCoords final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->itemType( index ).toLower() == "texcoord" || nif->inherits( index, "NiTriBasedGeomData" ); + return nif->itemType( index ).toLower() == "texcoord" || nif->blockInherits( index, "NiTriBasedGeomData" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -253,7 +253,7 @@ class spFlipTexCoords final : public Spell QModelIndex idx = index; if ( nif->itemType( index ).toLower() != "texcoord" ) { - idx = nif->getIndex( nif->getBlock( index ), "UV Sets" ); + idx = nif->getIndex( nif->getBlockIndex( index ), "UV Sets" ); } QMenu menu; @@ -445,7 +445,7 @@ class spPruneRedundantTriangles final : public Spell Message::info( nullptr, Spell::tr( "Removed %1 triangles" ).arg( cnt ) ); nif->set( iData, "Num Triangles", tris.count() ); nif->set( iData, "Num Triangle Points", tris.count() * 3 ); - nif->updateArray( iData, "Triangles" ); + nif->updateArraySize( iData, "Triangles" ); nif->setArray( iData, "Triangles", tris.toVector() ); } @@ -472,7 +472,7 @@ class spRemoveDuplicateVertices final : public Spell try { QModelIndex iShape = getShape( nif, index ); - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( iShape, "Data" ) ); // read the data @@ -598,7 +598,7 @@ class spRemoveWasteVertices final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { QModelIndex iShape = getShape( nif, index ); - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( iShape, "Data" ) ); removeWasteVertices( nif, iData, iShape ); @@ -613,12 +613,12 @@ REGISTER_SPELL( spRemoveWasteVertices ) */ bool spUpdateCenterRadius::isApplicable( const NifModel * nif, const QModelIndex & index ) { - return nif->getBlock( index, "NiGeometryData" ).isValid(); + return nif->getBlockIndex( index, "NiGeometryData" ).isValid(); } QModelIndex spUpdateCenterRadius::cast( NifModel * nif, const QModelIndex & index ) { - QModelIndex iData = nif->getBlock( index ); + QModelIndex iData = nif->getBlockIndex( index ); QVector verts = nif->getArray( iData, "Vertices" ); @@ -686,7 +686,7 @@ class spUpdateBounds final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->inherits( index, "BSTriShape" ) && nif->getIndex( index, "Vertex Data" ).isValid(); + return nif->blockInherits( index, "BSTriShape" ) && nif->getIndex( index, "Vertex Data" ).isValid(); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -734,7 +734,7 @@ class spUpdateAllBounds final : public Spell spUpdateBounds updBounds; for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex idx = nif->getBlock( n ); + QModelIndex idx = nif->getBlockIndex( n ); if ( updBounds.isApplicable( nif, idx ) ) indices << idx; @@ -759,9 +759,9 @@ bool spUpdateTrianglesFromSkin::isApplicable( const NifModel * nif, const QModel QModelIndex spUpdateTrianglesFromSkin::cast( NifModel * nif, const QModelIndex & index ) { - auto iData = nif->getBlock( nif->getLink( index, "Data" ) ); - auto iSkin = nif->getBlock( nif->getLink( index, "Skin Instance" ) ); - auto iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ) ); + auto iData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); + auto iSkin = nif->getBlockIndex( nif->getLink( index, "Skin Instance" ) ); + auto iSkinPart = nif->getBlockIndex( nif->getLink( iSkin, "Skin Partition" ) ); if ( !iSkinPart.isValid() || !iData.isValid() ) return QModelIndex(); @@ -773,7 +773,7 @@ QModelIndex spUpdateTrianglesFromSkin::cast( NifModel * nif, const QModelIndex & nif->set( iData, "Has Triangles", true ); nif->set( iData, "Num Triangles", tris.size() ); nif->set( iData, "Num Triangle Points", tris.size() * 3 ); - nif->updateArray( iData, "Triangles" ); + nif->updateArraySize( iData, "Triangles" ); nif->setArray( iData, "Triangles", tris ); return index; diff --git a/src/spells/misc.cpp b/src/spells/misc.cpp index 151bb579e..00304e9fe 100644 --- a/src/spells/misc.cpp +++ b/src/spells/misc.cpp @@ -67,7 +67,8 @@ class spUpdateHeader final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return ( nif->getHeader() == nif->getBlockOrHeader( index ) ); + auto block = nif->getTopItem( index ); + return ( block && block == nif->getHeaderItem() ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -88,7 +89,8 @@ class spUpdateFooter final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return ( nif->getFooter() == nif->getBlockOrHeader( index ) ); + auto block = nif->getTopItem( index ); + return ( block && block == nif->getFooterItem() ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -116,7 +118,7 @@ class spFollowLink final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - QModelIndex idx = nif->getBlock( nif->getLink( index ) ); + QModelIndex idx = nif->getBlockIndex( nif->getLink( index ) ); if ( idx.isValid() ) return idx; @@ -177,14 +179,14 @@ class spExportBinary final : public Spell dataItem = item->child( 0 ); } - if ( dataItem && dataItem->value().isByteArray() ) { - auto bytes = dataItem->value().get(); + if ( dataItem && dataItem->valueIsByteArray() ) { + auto bytes = dataItem->get(); data.append( *bytes ); } // Get parent block name and number int blockNum = nif->getBlockNumber( index ); - QString suffix = QString( "%1_%2" ).arg( nif->getBlockName( nif->getBlock( blockNum ) ) ).arg( blockNum ); + QString suffix = QString( "%1_%2" ).arg( nif->itemName( nif->getBlockIndex( blockNum ) ) ).arg( blockNum ); QString filestring = QString( "%1-%2" ).arg( nif->getFilename() ).arg( suffix ); QString filename = QFileDialog::getSaveFileName( qApp->activeWindow(), tr( "Export Binary File" ), @@ -235,7 +237,7 @@ class spImportBinary final : public Spell if ( parent->isArray() && parent->isBinary() ) { // NOTE: This will only work on byte arrays where the array length is not an expression nif->set( iParent.parent(), parent->arr1(), data.count() ); - nif->updateArray( iParent ); + nif->updateArraySize( iParent ); } nif->set( idx, data ); @@ -253,7 +255,7 @@ REGISTER_SPELL( spImportBinary ) bool spCollapseArray::isApplicable( const NifModel * nif, const QModelIndex & index ) { if ( nif->isArray( index ) && index.isValid() - && ( nif->getBlockType( index ) == "Ref" || nif->getBlockType( index ) == "Ptr" ) ) + && ( nif->itemType( index ) == "Ref" || nif->itemType( index ) == "Ptr" ) ) { // copy from spUpdateArray when that changes return true; @@ -264,10 +266,10 @@ bool spCollapseArray::isApplicable( const NifModel * nif, const QModelIndex & in QModelIndex spCollapseArray::cast( NifModel * nif, const QModelIndex & index ) { - nif->updateArray( index ); + nif->updateArraySize( index ); // There's probably an easier way of doing this hiding in NifModel somewhere NifItem * item = static_cast( index.internalPointer() ); - QModelIndex size = nif->getIndex( nif->getBlock( index.parent() ), item->arr1() ); + QModelIndex size = nif->getIndex( nif->getBlockIndex( index.parent() ), item->arr1() ); QModelIndex array = static_cast( index ); return numCollapser( nif, size, array ); } @@ -286,7 +288,7 @@ QModelIndex spCollapseArray::numCollapser( NifModel * nif, QModelIndex & iNumEle if ( links.count() < nif->rowCount( iArray ) ) { nif->set( iNumElem, links.count() ); - nif->updateArray( iArray ); + nif->updateArraySize( iArray ); nif->setLinkArray( iArray, links ); } } diff --git a/src/spells/moppcode.cpp b/src/spells/moppcode.cpp index fbf7b3767..33a006257 100644 --- a/src/spells/moppcode.cpp +++ b/src/spells/moppcode.cpp @@ -138,7 +138,7 @@ class spMoppCode final : public Spell return false; if ( TheHavokCode.Initialize() ) { - //QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + //QModelIndex iData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); if ( nif->isNiBlock( index, "bhkMoppBvTreeShape" ) ) { return ( nif->checkVersion( 0x14000004, 0x14000005 ) @@ -158,14 +158,14 @@ class spMoppCode final : public Spell QPersistentModelIndex ibhkMoppBvTreeShape = iBlock; - QModelIndex ibhkPackedNiTriStripsShape = nif->getBlock( nif->getLink( ibhkMoppBvTreeShape, "Shape" ) ); + QModelIndex ibhkPackedNiTriStripsShape = nif->getBlockIndex( nif->getLink( ibhkMoppBvTreeShape, "Shape" ) ); if ( !nif->isNiBlock( ibhkPackedNiTriStripsShape, "bhkPackedNiTriStripsShape" ) ) { Message::warning( nullptr, Spell::tr( "Only bhkPackedNiTriStripsShape is supported at this time." ) ); return iBlock; } - QModelIndex ihkPackedNiTriStripsData = nif->getBlock( nif->getLink( ibhkPackedNiTriStripsShape, "Data" ) ); + QModelIndex ihkPackedNiTriStripsData = nif->getBlockIndex( nif->getLink( ibhkPackedNiTriStripsShape, "Data" ) ); if ( !nif->isNiBlock( ihkPackedNiTriStripsData, "hkPackedNiTriStripsData" ) ) return iBlock; @@ -224,7 +224,7 @@ class spMoppCode final : public Spell if ( iCodeSize.isValid() && iCode.isValid() ) { nif->set( iCodeSize, moppcode.size() ); - nif->updateArray( iCode ); + nif->updateArraySize( iCode ); nif->set( iCode, moppcode ); } } @@ -264,7 +264,7 @@ class spAllMoppCodes final : public Spell spMoppCode TSpacer; for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex idx = nif->getBlock( n ); + QModelIndex idx = nif->getBlockIndex( n ); if ( TSpacer.isApplicable( nif, idx ) ) indices << idx; diff --git a/src/spells/morphctrl.cpp b/src/spells/morphctrl.cpp index 176129b73..d10f36b2c 100644 --- a/src/spells/morphctrl.cpp +++ b/src/spells/morphctrl.cpp @@ -50,12 +50,12 @@ class spMorphFrameSave final : public Spell qCWarning( nsSpell ) << Spell::tr( "overriding base key frame, all other frames will be cleared" ); nif->set( iMorphData, "Num Vertices", nif->get( iMeshData, "Num Vertices" ) ); QVector verts = nif->getArray( iMeshData, "Vertices" ); - nif->updateArray( iFrames.child( 0, 0 ), "Vectors" ); + nif->updateArraySize( iFrames.child( 0, 0 ), "Vectors" ); nif->setArray( iFrames.child( 0, 0 ), "Vectors", verts ); verts.fill( Vector3() ); for ( int f = 1; f < nif->rowCount( iFrames ); f++ ) { - nif->updateArray( iFrames.child( f, 0 ), "Vectors" ); + nif->updateArraySize( iFrames.child( f, 0 ), "Vectors" ); nif->setArray( iFrames.child( f, 0 ), "Vectors", verts ); } } else { @@ -76,12 +76,12 @@ class spMorphFrameSave final : public Spell //! Helper function to get the Mesh data QModelIndex getMeshData( const NifModel * nif, const QModelIndex & iMorpher ) { - QModelIndex iMesh = nif->getBlock( nif->getParent( nif->getBlockNumber( iMorpher ) ) ); + QModelIndex iMesh = nif->getBlockIndex( nif->getParent( nif->getBlockNumber( iMorpher ) ) ); - if ( nif->inherits( iMesh, "NiTriBasedGeom" ) ) { - QModelIndex iData = nif->getBlock( nif->getLink( iMesh, "Data" ) ); + if ( nif->blockInherits( iMesh, "NiTriBasedGeom" ) ) { + QModelIndex iData = nif->getBlockIndex( nif->getLink( iMesh, "Data" ) ); - if ( nif->inherits( iData, "NiTriBasedGeomData" ) ) + if ( nif->blockInherits( iData, "NiTriBasedGeomData" ) ) return iData; return QModelIndex(); @@ -93,7 +93,7 @@ class spMorphFrameSave final : public Spell //! Helper function to get the morph data QModelIndex getMorphData( const NifModel * nif, const QModelIndex & iMorpher ) { - return nif->getBlock( nif->getLink( iMorpher, "Data" ), "NiMorphData" ); + return nif->getBlockIndex( nif->getLink( iMorpher, "Data" ), "NiMorphData" ); } //! Helper function to get the morph frame array diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index 5104f05c5..40abfebc9 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -25,10 +25,10 @@ class spFaceNormals final : public Spell static QModelIndex getShapeData( const NifModel * nif, const QModelIndex & index ) { - QModelIndex iData = nif->getBlock( index ); + QModelIndex iData = nif->getBlockIndex( index ); if ( nif->isNiBlock( index, { "NiTriShape", "BSLODTriShape", "NiTriStrips" } ) ) - iData = nif->getBlock( nif->getLink( index, "Data" ) ); + iData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); if ( nif->isNiBlock( iData, { "NiTriShapeData", "NiTriStripsData" } ) ) return iData; @@ -38,8 +38,8 @@ class spFaceNormals final : public Spell if ( (vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( index, "Skin" ) ); - auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); - auto iPartBlock = nif->getBlock( partID, "NiSkinPartition" ); + auto partID = nif->getLink( nif->getBlockIndex( skinID, "NiSkinInstance" ), "Skin Partition" ); + auto iPartBlock = nif->getBlockIndex( partID, "NiSkinPartition" ); if ( iPartBlock.isValid() ) return nif->getIndex( iPartBlock, "Vertex Data" ); } @@ -98,7 +98,7 @@ class spFaceNormals final : public Spell faceNormals( verts, triangles, norms ); nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); + nif->updateArraySize( iData, "Normals" ); nif->setArray( iData, "Normals", norms ); } else { QVector triangles; diff --git a/src/spells/optimize.cpp b/src/spells/optimize.cpp index 51dcf2d56..f17e84c58 100644 --- a/src/spells/optimize.cpp +++ b/src/spells/optimize.cpp @@ -49,7 +49,7 @@ class spCombiProps final : public Spell map.clear(); for ( qint32 b = 0; b < nif->getBlockCount(); b++ ) { - QModelIndex iBlock = nif->getBlock( b ); + QModelIndex iBlock = nif->getBlockIndex( b ); QString original_material_name; if ( nif->isNiBlock( iBlock, "NiMaterialProperty" ) ) { @@ -61,12 +61,12 @@ class spCombiProps final : public Spell nif->set( iBlock, "Name", "Default" ); } - if ( nif->inherits( iBlock, "BSShaderProperty" ) || nif->isNiBlock( iBlock, "BSShaderTextureSet" ) ) { + if ( nif->blockInherits( iBlock, "BSShaderProperty" ) || nif->isNiBlock( iBlock, "BSShaderTextureSet" ) ) { // these need to be unique continue; } - if ( nif->inherits( iBlock, "NiProperty" ) || nif->inherits( iBlock, "NiSourceTexture" ) ) { + if ( nif->blockInherits( iBlock, "NiProperty" ) || nif->blockInherits( iBlock, "NiSourceTexture" ) ) { QBuffer data; data.open( QBuffer::WriteOnly ); data.write( nif->itemName( iBlock ).toLatin1() ); @@ -134,7 +134,7 @@ class spUniqueProps final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { for ( int b = 0; b < nif->getBlockCount(); b++ ) { - QModelIndex iAVObj = nif->getBlock( b, "NiAVObject" ); + QModelIndex iAVObj = nif->getBlockIndex( b, "NiAVObject" ); if ( iAVObj.isValid() ) { QVector props = nif->getLinkArray( iAVObj, "Properties" ); @@ -142,14 +142,14 @@ class spUniqueProps final : public Spell while ( it.hasNext() ) { qint32 & l = it.next(); - QModelIndex iProp = nif->getBlock( l, "NiProperty" ); + QModelIndex iProp = nif->getBlockIndex( l, "NiProperty" ); if ( iProp.isValid() && nif->getParent( l ) != b ) { QMap map; if ( nif->isNiBlock( iProp, "NiTexturingProperty" ) ) { for ( const auto sl : nif->getChildLinks( nif->getBlockNumber( iProp ) ) ) { - QModelIndex iSrc = nif->getBlock( sl, "NiSourceTexture" ); + QModelIndex iSrc = nif->getBlockIndex( sl, "NiSourceTexture" ); if ( iSrc.isValid() && !map.contains( sl ) ) { QModelIndex iSrc2 = nif->insertNiBlock( "NiSourceTexture", nif->getBlockCount() + 1 ); @@ -213,7 +213,7 @@ class spRemoveBogusNodes final : public Spell removed = false; for ( int b = 0; b < nif->getBlockCount(); b++ ) { - QModelIndex iNode = nif->getBlock( b, "NiNode" ); + QModelIndex iNode = nif->getBlockIndex( b, "NiNode" ); if ( iNode.isValid() ) { if ( nif->getChildLinks( b ).isEmpty() && nif->getParentLinks( b ).isEmpty() ) { @@ -282,7 +282,7 @@ class spCombiTris final : public Spell for ( const auto lChild : nif->getLinkArray( iParent, "Children" ) ) { if ( nif->getParent( lChild ) == nif->getBlockNumber( iParent ) ) { - QModelIndex iChild = nif->getBlock( lChild ); + QModelIndex iChild = nif->getBlockIndex( lChild ); if ( nif->isNiBlock( iChild, { "NiTriShape", "NiTriStrips" } ) ) lTris << lChild; @@ -299,7 +299,7 @@ class spCombiTris final : public Spell continue; for ( const auto lTriB : lTris ) { - if ( matches( nif, nif->getBlock( lTriA ), nif->getBlock( lTriB ) ) ) { + if ( matches( nif, nif->getBlockIndex( lTriA ), nif->getBlockIndex( lTriB ) ) ) { match[ lTriA ] << lTriB; found << lTriB; } @@ -314,15 +314,15 @@ class spCombiTris final : public Spell QList remove; for ( const auto lTriA : match.keys() ) { - ApplyTransform.cast( nif, nif->getBlock( lTriA ) ); + ApplyTransform.cast( nif, nif->getBlockIndex( lTriA ) ); for ( const auto lTriB : match[lTriA] ) { - ApplyTransform.cast( nif, nif->getBlock( lTriB ) ); - combine( nif, nif->getBlock( lTriA ), nif->getBlock( lTriB ) ); - remove << nif->getBlock( lTriB ); + ApplyTransform.cast( nif, nif->getBlockIndex( lTriB ) ); + combine( nif, nif->getBlockIndex( lTriA ), nif->getBlockIndex( lTriB ) ); + remove << nif->getBlockIndex( lTriB ); } - TSpace.castIfApplicable( nif, nif->getBlock( lTriA ) ); + TSpace.castIfApplicable( nif, nif->getBlockIndex( lTriA ) ); } // remove the now obsolete shapes @@ -359,7 +359,7 @@ class spCombiTris final : public Spell if ( lPrpsA.contains( l ) ) continue; - QModelIndex iBlock = nif->getBlock( l ); + QModelIndex iBlock = nif->getBlockIndex( l ); if ( nif->isNiBlock( iBlock, "NiTriShapeData" ) ) continue; @@ -380,7 +380,7 @@ class spCombiTris final : public Spell if ( lPrpsB.contains( l ) ) continue; - QModelIndex iBlock = nif->getBlock( l ); + QModelIndex iBlock = nif->getBlockIndex( l ); if ( nif->isNiBlock( iBlock, "NiTriShapeData" ) ) continue; @@ -397,8 +397,8 @@ class spCombiTris final : public Spell return false; } - QModelIndex iDataA = nif->getBlock( nif->getLink( iTriA, "Data" ), "NiTriBasedGeomData" ); - QModelIndex iDataB = nif->getBlock( nif->getLink( iTriB, "Data" ), "NiTriBasedGeomData" ); + QModelIndex iDataA = nif->getBlockIndex( nif->getLink( iTriA, "Data" ), "NiTriBasedGeomData" ); + QModelIndex iDataB = nif->getBlockIndex( nif->getLink( iTriB, "Data" ), "NiTriBasedGeomData" ); return dataMatches( nif, iDataA, iDataB ); } @@ -427,27 +427,27 @@ class spCombiTris final : public Spell { nif->set( iTriB, "Flags", nif->get( iTriB, "Flags" ) | 1 ); - QModelIndex iDataA = nif->getBlock( nif->getLink( iTriA, "Data" ), "NiTriBasedGeomData" ); - QModelIndex iDataB = nif->getBlock( nif->getLink( iTriB, "Data" ), "NiTriBasedGeomData" ); + QModelIndex iDataA = nif->getBlockIndex( nif->getLink( iTriA, "Data" ), "NiTriBasedGeomData" ); + QModelIndex iDataB = nif->getBlockIndex( nif->getLink( iTriB, "Data" ), "NiTriBasedGeomData" ); int numA = nif->get( iDataA, "Num Vertices" ); int numB = nif->get( iDataB, "Num Vertices" ); nif->set( iDataA, "Num Vertices", numA + numB ); - nif->updateArray( iDataA, "Vertices" ); + nif->updateArraySize( iDataA, "Vertices" ); nif->setArray( iDataA, "Vertices", nif->getArray( iDataA, "Vertices" ).mid( 0, numA ) + nif->getArray( iDataB, "Vertices" ) ); - nif->updateArray( iDataA, "Normals" ); + nif->updateArraySize( iDataA, "Normals" ); nif->setArray( iDataA, "Normals", nif->getArray( iDataA, "Normals" ).mid( 0, numA ) + nif->getArray( iDataB, "Normals" ) ); - nif->updateArray( iDataA, "Vertex Colors" ); + nif->updateArraySize( iDataA, "Vertex Colors" ); nif->setArray( iDataA, "Vertex Colors", nif->getArray( iDataA, "Vertex Colors" ).mid( 0, numA ) + nif->getArray( iDataB, "Vertex Colors" ) ); QModelIndex iUVa = nif->getIndex( iDataA, "UV Sets" ); QModelIndex iUVb = nif->getIndex( iDataB, "UV Sets" ); for ( int r = 0; r < nif->rowCount( iUVa ); r++ ) { - nif->updateArray( iUVa.child( r, 0 ) ); + nif->updateArraySize( iUVa.child( r, 0 ) ); nif->setArray( iUVa.child( r, 0 ), nif->getArray( iUVa.child( r, 0 ) ).mid( 0, numA ) + nif->getArray( iUVb.child( r, 0 ) ) ); } @@ -466,15 +466,15 @@ class spCombiTris final : public Spell tri[2] += numA; } - nif->updateArray( iDataA, "Triangles" ); + nif->updateArraySize( iDataA, "Triangles" ); nif->setArray( iDataA, "Triangles", triangles + nif->getArray( iDataA, "Triangles" ) ); int stripCntA = nif->get( iDataA, "Num Strips" ); int stripCntB = nif->get( iDataB, "Num Strips" ); nif->set( iDataA, "Num Strips", stripCntA + stripCntB ); - nif->updateArray( iDataA, "Strip Lengths" ); - nif->updateArray( iDataA, "Points" ); + nif->updateArraySize( iDataA, "Strip Lengths" ); + nif->updateArraySize( iDataA, "Points" ); for ( int r = 0; r < stripCntB; r++ ) { QVector strip = nif->getArray( nif->getIndex( iDataB, "Points" ).child( r, 0 ) ); @@ -484,7 +484,7 @@ class spCombiTris final : public Spell it.next() += numA; nif->set( nif->getIndex( iDataA, "Strip Lengths" ).child( r + stripCntA, 0 ), strip.size() ); - nif->updateArray( nif->getIndex( iDataA, "Points" ).child( r + stripCntA, 0 ) ); + nif->updateArraySize( nif->getIndex( iDataA, "Points" ).child( r + stripCntA, 0 ) ); nif->setArray( nif->getIndex( iDataA, "Points" ).child( r + stripCntA, 0 ), strip ); } @@ -538,7 +538,7 @@ class spRemoveUnusedStrings final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & ) override final { - auto originalStrings = nif->getArray( nif->getHeader(), "Strings" ); + auto originalStrings = nif->getArray( nif->getHeaderIndex(), "Strings" ); // FO4 workaround for apparently unused but necessary BSClothExtraData string int cedIdx = originalStrings.indexOf( "CED" ); @@ -547,7 +547,7 @@ class spRemoveUnusedStrings final : public Spell QMap usedStrings; for ( qint32 b = 0; b < nif->getBlockCount(); b++ ) - scan( nif->getBlock( b ), nif, usedStrings, hasCED ); + scan( nif->getBlockIndex( b ), nif, usedStrings, hasCED ); QVector newStrings( usedStrings.size() ); for ( auto kv : usedStrings.toStdMap() ) @@ -560,9 +560,9 @@ class spRemoveUnusedStrings final : public Spell newSize++; } - nif->set( nif->getHeader(), "Num Strings", newSize ); - nif->updateArray( nif->getHeader(), "Strings" ); - nif->setArray( nif->getHeader(), "Strings", newStrings ); + nif->set( nif->getHeaderIndex(), "Num Strings", newSize ); + nif->updateArraySize( nif->getHeaderIndex(), "Strings" ); + nif->setArray( nif->getHeaderIndex(), "Strings", newStrings ); nif->updateHeader(); // Remove new from original to see what was removed diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index 4dab31aca..6ac9d04a2 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -53,7 +53,7 @@ class spReorderLinks : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & ) override { for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex iBlock = nif->getBlock( n ); + QModelIndex iBlock = nif->getBlockIndex( n ); QModelIndex iNumChildren = nif->getIndex( iBlock, "Num Children" ); QModelIndex iChildren = nif->getIndex( iBlock, "Children" ); @@ -65,7 +65,7 @@ class spReorderLinks : public Spell qint32 l = nif->getLink( iChildren.child( r, 0 ) ); if ( l >= 0 ) { - links.append( QPair( l, nif->inherits(nif->getBlock(l), {"NiTriBasedGeom", "BSTriShape"}) ) ); + links.append( QPair( l, nif->blockInherits(nif->getBlockIndex(l), {"NiTriBasedGeom", "BSTriShape"}) ) ); } } @@ -83,7 +83,7 @@ class spReorderLinks : public Spell // update child count & array even if there are no rows (i.e. prune empty children) nif->set( iNumChildren, links.count() ); - nif->updateArray( iChildren ); + nif->updateArraySize( iChildren ); } } @@ -110,7 +110,7 @@ class spSanitizeLinkArrays final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & ) override final { for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex iBlock = nif->getBlock( n ); + QModelIndex iBlock = nif->getBlockIndex( n ); spCollapseArray arrayCollapser; @@ -157,7 +157,7 @@ class spAdjustTextureSources final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & ) override final { for ( int i = 0; i < nif->getBlockCount(); i++ ) { - QModelIndex iTexSrc = nif->getBlock( i, "NiSourceTexture" ); + QModelIndex iTexSrc = nif->getBlockIndex( i, "NiSourceTexture" ); if ( iTexSrc.isValid() ) { QModelIndex iFileName = nif->getIndex( iTexSrc, "File Name" ); @@ -194,11 +194,11 @@ bool spSanitizeBlockOrder::isApplicable( const NifModel *, const QModelIndex & i bool spSanitizeBlockOrder::childBeforeParent( NifModel * nif, qint32 block ) { // get index to the block - QModelIndex iBlock( nif->getBlock( block ) ); + QModelIndex iBlock( nif->getBlockIndex( block ) ); // check its type return ( - nif->inherits( iBlock, "bhkRefObject" ) - && !nif->inherits( iBlock, "bhkConstraint" ) + nif->blockInherits( iBlock, "bhkRefObject" ) + && !nif->blockInherits( iBlock, "bhkConstraint" ) ); } @@ -212,9 +212,9 @@ void spSanitizeBlockOrder::addTree( NifModel * nif, qint32 block, QList // special case: add bhkConstraint entities before bhkConstraint // (these are actually links, not refs) - QModelIndex iBlock( nif->getBlock( block ) ); + QModelIndex iBlock( nif->getBlockIndex( block ) ); - if ( nif->inherits( iBlock, "bhkConstraint" ) ) { + if ( nif->blockInherits( iBlock, "bhkConstraint" ) ) { for ( const auto entity : nif->getLinkArray( iBlock, "Entities" ) ) { addTree( nif, entity, newblocks ); } @@ -294,7 +294,7 @@ class spSanityCheckLinks final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & ) override final { for ( int b = 0; b < nif->getBlockCount(); b++ ) { - QModelIndex iBlock = nif->getBlock( b ); + QModelIndex iBlock = nif->getBlockIndex( b ); QModelIndex idx = check( nif, iBlock ); if ( idx.isValid() ) @@ -308,14 +308,13 @@ class spSanityCheckLinks final : public Spell { for ( int r = 0; r < nif->rowCount( iParent ); r++ ) { QModelIndex idx = iParent.child( r, 0 ); - bool child; - if ( nif->isLink( idx, &child ) ) { + if ( nif->isLink( idx ) ) { qint32 l = nif->getLink( idx ); if ( l < 0 ) { /* - if ( ! child ) + if ( ! isChildLink ) { qDebug() << "unassigned parent link"; return idx; @@ -328,9 +327,9 @@ class spSanityCheckLinks final : public Spell QString tmplt = nif->itemTmplt( idx ); if ( !tmplt.isEmpty() ) { - QModelIndex iBlock = nif->getBlock( l ); + QModelIndex iBlock = nif->getBlockIndex( l ); - if ( !nif->inherits( iBlock, tmplt ) ) { + if ( !nif->blockInherits( iBlock, tmplt ) ) { qCCritical( nsSpell ) << Spell::tr( "Link '%1' points to wrong block type." ).arg( QString::number(l) ); return idx; } @@ -362,7 +361,7 @@ class spFixInvalidNames final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif && nif->getIndex( nif->getHeader(), "Num Strings" ).isValid() && !index.isValid(); + return nif && nif->getIndex( nif->getHeaderIndex(), "Num Strings" ).isValid() && !index.isValid(); } QModelIndex cast( NifModel * nif, const QModelIndex & ) override final @@ -371,7 +370,7 @@ class spFixInvalidNames final : public Spell QVector shapeNames; QMap modifiedBlocks; - auto iHeader = nif->getHeader(); + auto iHeader = nif->getHeaderIndex(); auto numStrings = nif->get( iHeader, "Num Strings" ); auto strings = nif->getArray( iHeader, "Strings" ); @@ -403,18 +402,18 @@ class spFixInvalidNames final : public Spell }; for ( int i = 0; i < nif->getBlockCount(); i++ ) { - QModelIndex iBlock = nif->getBlock( i ); - if ( !(nif->inherits( iBlock, "NiObjectNET" ) || nif->inherits( iBlock, "NiExtraData" )) ) + QModelIndex iBlock = nif->getBlockIndex( i ); + if ( !(nif->blockInherits( iBlock, "NiObjectNET" ) || nif->blockInherits( iBlock, "NiExtraData" )) ) continue; auto nameIdx = nif->get( iBlock, "Name" ); auto nameString = nif->get( iBlock, "Name" ); // Ignore Editor Markers and AddOnNodes - if ( nameString.contains( "EditorMarker" ) || nif->inherits( iBlock, "BSValueNode" ) ) + if ( nameString.contains( "EditorMarker" ) || nif->blockInherits( iBlock, "BSValueNode" ) ) continue; - QModelIndex iBlockParent = nif->getBlock( nif->getParent( i ) ); + QModelIndex iBlockParent = nif->getBlockIndex( nif->getParent( i ) ); auto parentNameString = nif->get( iBlockParent, "Name" ); if ( !iBlockParent.isValid() ) { parentNameString = nif->getFilename(); @@ -425,7 +424,7 @@ class spFixInvalidNames final : public Spell bool isOutOfBounds = nameIdx >= numStrings; bool isProp = nif->isNiBlock( iBlock, { "BSLightingShaderProperty", "BSEffectShaderProperty", "NiAlphaProperty" } ); - bool isNiAV = nif->inherits( iBlock, "NiAVObject" ); + bool isNiAV = nif->blockInherits( iBlock, "NiAVObject" ); // Fix 'BSX' strings if ( nif->isNiBlock( iBlock, "BSXFlags" ) ) { @@ -502,7 +501,7 @@ class spFixInvalidNames final : public Spell // Update header nif->set( iHeader, "Num Strings", strings.count() ); - nif->updateArray( iHeader, "Strings" ); + nif->updateArraySize( iHeader, "Strings" ); nif->setArray( iHeader, "Strings", strings ); nif->updateHeader(); @@ -530,14 +529,14 @@ class spFillBlankControllerTypes final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif && nif->getIndex( nif->getHeader(), "Num Strings" ).isValid() && !index.isValid(); + return nif && nif->getIndex( nif->getHeaderIndex(), "Num Strings" ).isValid() && !index.isValid(); } QModelIndex cast( NifModel * nif, const QModelIndex & ) override final { QVector modifiedNames; - auto iHeader = nif->getHeader(); + auto iHeader = nif->getHeaderIndex(); auto numStrings = nif->get( iHeader, "Num Strings" ); auto strings = nif->getArray( iHeader, "Strings" ); @@ -557,8 +556,8 @@ class spFillBlankControllerTypes final : public Spell } for ( int i = 0; i < nif->getBlockCount(); i++ ) { - QModelIndex iBlock = nif->getBlock( i ); - if ( !(nif->inherits( iBlock, "NiControllerSequence" )) ) + QModelIndex iBlock = nif->getBlockIndex( i ); + if ( !(nif->blockInherits( iBlock, "NiControllerSequence" )) ) continue; auto controlledBlocks = nif->getIndex( iBlock, "Controlled Blocks" ); @@ -578,7 +577,7 @@ class spFillBlankControllerTypes final : public Spell // Update header nif->set( iHeader, "Num Strings", strings.count() ); - nif->updateArray( iHeader, "Strings" ); + nif->updateArraySize( iHeader, "Strings" ); nif->setArray( iHeader, "Strings", strings ); nif->updateHeader(); @@ -607,15 +606,15 @@ bool spErrorNoneRefs::isApplicable( const NifModel *, const QModelIndex & index QModelIndex spErrorNoneRefs::cast( NifModel * nif, const QModelIndex & ) { for ( int i = 0; i < nif->getBlockCount(); i++ ) { - auto iNiAV = nif->getBlock( i, "NiAVObject" ); + auto iNiAV = nif->getBlockIndex( i, "NiAVObject" ); if ( iNiAV.isValid() ) checkArray( nif, iNiAV, {"Properties"} ); // "Children" - auto iNiNET = nif->getBlock( i, "NiObjectNET" ); + auto iNiNET = nif->getBlockIndex( i, "NiObjectNET" ); if ( iNiNET.isValid() ) checkArray( nif, iNiNET, {"Extra Data List"} ); - auto iBSSI = nif->getBlock( i, "BSSkin::Instance" ); + auto iBSSI = nif->getBlockIndex( i, "BSSkin::Instance" ); if ( iBSSI.isValid() ) checkRef( nif, iBSSI, "Skeleton Root" ); } @@ -654,21 +653,21 @@ bool spErrorInvalidPaths::isApplicable( const NifModel *, const QModelIndex & in QModelIndex spErrorInvalidPaths::cast( NifModel * nif, const QModelIndex & ) { for ( int i = 0; i < nif->getBlockCount(); i++ ) { - auto iBSSTS = nif->getBlock( i, "BSShaderTextureSet" ); + auto iBSSTS = nif->getBlockIndex( i, "BSShaderTextureSet" ); if ( iBSSTS.isValid() ) checkPath( nif, iBSSTS, "Textures" ); - auto iBSSNLP = nif->getBlock( i, "BSShaderNoLightingProperty" ); + auto iBSSNLP = nif->getBlockIndex( i, "BSShaderNoLightingProperty" ); if ( iBSSNLP.isValid() ) checkPath( nif, iBSSNLP, "File Name" ); - auto iBSLSP = nif->getBlock( i, "BSLightingShaderProperty" ); + auto iBSLSP = nif->getBlockIndex( i, "BSLightingShaderProperty" ); if ( iBSLSP.isValid() ) { checkPath( nif, iBSLSP, "Name", P_NO_EXT ); checkPath( nif, iBSLSP, "Root Material", P_NO_EXT ); } - auto iBSESP = nif->getBlock( i, "BSEffectShaderProperty" ); + auto iBSESP = nif->getBlockIndex( i, "BSEffectShaderProperty" ); if ( iBSESP.isValid() ) { checkPath( nif, iBSESP, "Source Texture" ); checkPath( nif, iBSESP, "Greyscale Texture" ); @@ -677,7 +676,7 @@ QModelIndex spErrorInvalidPaths::cast( NifModel * nif, const QModelIndex & ) checkPath( nif, iBSESP, "Env Mask Texture" ); } - auto iNiST = nif->getBlock( i, "NiSourceTexture" ); + auto iNiST = nif->getBlockIndex( i, "NiSourceTexture" ); if ( iNiST.isValid() ) checkPath( nif, iNiST, "File Name" ); } @@ -722,7 +721,7 @@ QModelIndex spWarningEnvironmentMapping::cast(NifModel * nif, const QModelIndex { for ( int i = 0; i < nif->getBlockCount(); i++ ) { if ( nif->getBSVersion() < 83 ) { - auto iBSSP = nif->getBlock(i, "BSShaderPPLightingProperty"); + auto iBSSP = nif->getBlockIndex(i, "BSShaderPPLightingProperty"); if ( iBSSP.isValid() ) { auto sf1 = nif->get(iBSSP, "Shader Flags"); auto sf2 = nif->get(iBSSP, "Shader Flags 2"); diff --git a/src/spells/skeleton.cpp b/src/spells/skeleton.cpp index 223bda251..b127330ac 100644 --- a/src/spells/skeleton.cpp +++ b/src/spells/skeleton.cpp @@ -67,12 +67,12 @@ class spFixSkeleton final : public Spell doBones( nif, index, Transform(), local, bones ); for ( const auto link : nif->getChildLinks( nif->getBlockNumber( index ) ) ) { - QModelIndex iChild = nif->getBlock( link ); + QModelIndex iChild = nif->getBlockIndex( link ); if ( iChild.isValid() ) { if ( nif->itemName( iChild ) == "NiNode" ) { doNodes( nif, iChild, Transform(), world, bones ); - } else if ( nif->inherits( iChild, "NiTriBasedGeom" ) ) { + } else if ( nif->blockInherits( iChild, "NiTriBasedGeom" ) ) { doShape( nif, iChild, Transform(), world, bones ); } } @@ -95,7 +95,7 @@ class spFixSkeleton final : public Spell local.value( name ).writeBack( nif, index ); for ( const auto link : nif->getChildLinks( nif->getBlockNumber( index ) ) ) { - QModelIndex iChild = nif->getBlock( link, "NiNode" ); + QModelIndex iChild = nif->getBlockIndex( link, "NiNode" ); if ( iChild.isValid() ) doBones( nif, iChild, tparent * tlocal, local, bones ); @@ -113,12 +113,12 @@ class spFixSkeleton final : public Spell Transform tlocal( nif, index ); for ( const auto link : nif->getChildLinks( nif->getBlockNumber( index ) ) ) { - QModelIndex iChild = nif->getBlock( link ); + QModelIndex iChild = nif->getBlockIndex( link ); if ( iChild.isValid() ) { if ( nif->itemName( iChild ) == "NiNode" ) { hasSkinnedChildren |= doNodes( nif, iChild, tparent * tlocal, world, bones ); - } else if ( nif->inherits( iChild, "NiTriBasedGeom" ) ) { + } else if ( nif->blockInherits( iChild, "NiTriBasedGeom" ) ) { hasSkinnedChildren |= doShape( nif, iChild, tparent * tlocal, world, bones ); } } @@ -133,8 +133,8 @@ class spFixSkeleton final : public Spell } bool doShape( NifModel * nif, const QModelIndex & index, const Transform & tparent, const TransMap & world, const TransMap & bones ) { - QModelIndex iShapeData = nif->getBlock( nif->getLink( index, "Data" ) ); - QModelIndex iSkinInstance = nif->getBlock( nif->getLink( index, "Skin Instance" ), "NiSkinInstance" ); + QModelIndex iShapeData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); + QModelIndex iSkinInstance = nif->getBlockIndex( nif->getLink( index, "Skin Instance" ), "NiSkinInstance" ); if ( !iSkinInstance.isValid() || !iShapeData.isValid() ) return false; @@ -147,7 +147,7 @@ class spFixSkeleton final : public Spell if ( iNames.isValid() ) for ( int n = 0; n < nif->rowCount( iNames ); n++ ) { - QModelIndex iBone = nif->getBlock( nif->getLink( iNames.child( n, 0 ) ), "NiNode" ); + QModelIndex iBone = nif->getBlockIndex( nif->getLink( iNames.child( n, 0 ) ), "NiNode" ); if ( iBone.isValid() ) names.append( nif->get( iBone, "Name" ) ); @@ -156,7 +156,7 @@ class spFixSkeleton final : public Spell } - QModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInstance, "Data" ), "NiSkinData" ); + QModelIndex iSkinData = nif->getBlockIndex( nif->getLink( iSkinInstance, "Data" ), "NiSkinData" ); if ( !iSkinData.isValid() ) return false; @@ -224,7 +224,7 @@ class spScanSkeleton final : public Spell stream << name << local << tparent * local; qDebug() << name; for ( const auto link : nif->getChildLinks( nif->getBlockNumber( index ) ) ) { - QModelIndex iChild = nif->getBlock( link, "NiNode" ); + QModelIndex iChild = nif->getBlockIndex( link, "NiNode" ); if ( iChild.isValid() ) scan( nif, iChild, tparent * local, stream ); @@ -267,11 +267,12 @@ class spSkinPartition final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & iShape ) override final { - if ( nif->isNiBlock( iShape, { "NiTriShape", "NiTriStrips" } ) ) { - QModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); + static QStringList testNames = { "NiTriShape", "NiTriStrips" }; + if ( nif->isNiBlock( iShape, testNames ) ) { + QModelIndex iSkinInst = nif->getBlockIndex( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); if ( iSkinInst.isValid() ) { - return nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ).isValid(); + return nif->getBlockIndex( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ).isValid(); } } @@ -332,17 +333,17 @@ class spSkinPartition final : public Spell QPersistentModelIndex iData; if ( iShapeType == "NiTriShape" ) { - iData = nif->getBlock( nif->getLink( iShape, "Data" ), "NiTriShapeData" ); + iData = nif->getBlockIndex( nif->getLink( iShape, "Data" ), "NiTriShapeData" ); } else if ( iShapeType == "NiTriStrips" ) { - iData = nif->getBlock( nif->getLink( iShape, "Data" ), "NiTriStripsData" ); + iData = nif->getBlockIndex( nif->getLink( iShape, "Data" ), "NiTriStripsData" ); } - QPersistentModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); - QPersistentModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ); - QModelIndex iSkinPart = nif->getBlock( nif->getLink( iSkinInst, "Skin Partition" ), "NiSkinPartition" ); + QPersistentModelIndex iSkinInst = nif->getBlockIndex( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); + QPersistentModelIndex iSkinData = nif->getBlockIndex( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ); + QModelIndex iSkinPart = nif->getBlockIndex( nif->getLink( iSkinInst, "Skin Partition" ), "NiSkinPartition" ); if ( !iSkinPart.isValid() ) - iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); + iSkinPart = nif->getBlockIndex( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); // read in the weights from NiSkinData @@ -459,7 +460,7 @@ class spSkinPartition final : public Spell QMap trimap; quint32 defaultPart = 0; - if ( nif->inherits( iSkinInst, "BSDismemberSkinInstance" ) ) { + if ( nif->blockInherits( iSkinInst, "BSDismemberSkinInstance" ) ) { // First find a partition to dump dangling faces. Torso is prefered if available. quint32 nparts = nif->get( iSkinInst, "Num Partitions" ); QModelIndex iPartData = nif->getIndex( iSkinInst, "Partitions" ); @@ -791,11 +792,11 @@ class spSkinPartition final : public Spell // start writing NiSkinPartition nif->set( iSkinPart, "Num Partitions", parts.count() ); - nif->updateArray( iSkinPart, "Partitions" ); + nif->updateArraySize( iSkinPart, "Partitions" ); QModelIndex iBSSkinInstPartData; - if ( nif->inherits( iSkinInst, "BSDismemberSkinInstance" ) ) { + if ( nif->blockInherits( iSkinInst, "BSDismemberSkinInstance" ) ) { quint32 nparts = nif->get( iSkinInst, "Num Partitions" ); iBSSkinInstPartData = nif->getIndex( iSkinInst, "Partitions" ); @@ -803,7 +804,7 @@ class spSkinPartition final : public Spell if ( nparts != (quint32)parts.count() ) { qCWarning( nsSpell ) << "BSDismemberSkinInstance partition count does not match Skin Partition count. Adjusting to fit."; nif->set( iSkinInst, "Num Partitions", parts.count() ); - nif->updateArray( iSkinInst, "Partitions" ); + nif->updateArraySize( iSkinInst, "Partitions" ); } } @@ -893,25 +894,25 @@ class spSkinPartition final : public Spell // fill in bone map QModelIndex iBoneMap = nif->getIndex( iPart, "Bones" ); - nif->updateArray( iBoneMap ); + nif->updateArraySize( iBoneMap ); nif->setArray( iBoneMap, bones.toVector() ); // fill in vertex map nif->set( iPart, "Has Vertex Map", 1 ); QModelIndex iVertexMap = nif->getIndex( iPart, "Vertex Map" ); - nif->updateArray( iVertexMap ); + nif->updateArraySize( iVertexMap ); nif->setArray( iVertexMap, vertices ); // fill in vertex weights nif->set( iPart, "Has Vertex Weights", 1 ); QModelIndex iVWeights = nif->getIndex( iPart, "Vertex Weights" ); - nif->updateArray( iVWeights ); + nif->updateArraySize( iVWeights ); for ( int v = 0; v < nif->rowCount( iVWeights ); v++ ) { QModelIndex iVertex = iVWeights.child( v, 0 ); - nif->updateArray( iVertex ); + nif->updateArraySize( iVertex ); QList list = weights.value( vertices[v] ); for ( int b = 0; b < maxBones; b++ ) @@ -923,31 +924,31 @@ class spSkinPartition final : public Spell if ( make_strips == true ) { //Clear out any existing triangle data that might be left over from an existing Skin Partition QModelIndex iTriangles = nif->getIndex( iPart, "Triangles" ); - nif->updateArray( iTriangles ); + nif->updateArraySize( iTriangles ); // write the strips QModelIndex iStripLengths = nif->getIndex( iPart, "Strip Lengths" ); - nif->updateArray( iStripLengths ); + nif->updateArraySize( iStripLengths ); for ( int s = 0; s < nif->rowCount( iStripLengths ); s++ ) nif->set( iStripLengths.child( s, 0 ), strips.value( s ).count() ); QModelIndex iStrips = nif->getIndex( iPart, "Strips" ); - nif->updateArray( iStrips ); + nif->updateArraySize( iStrips ); for ( int s = 0; s < nif->rowCount( iStrips ); s++ ) { - nif->updateArray( iStrips.child( s, 0 ) ); + nif->updateArraySize( iStrips.child( s, 0 ) ); nif->setArray( iStrips.child( s, 0 ), strips.value( s ) ); } } else { //Clear out any existing strip data that might be left over from an existing Skin Partition QModelIndex iStripLengths = nif->getIndex( iPart, "Strip Lengths" ); - nif->updateArray( iStripLengths ); + nif->updateArraySize( iStripLengths ); QModelIndex iStrips = nif->getIndex( iPart, "Strips" ); - nif->updateArray( iStrips ); + nif->updateArraySize( iStrips ); QModelIndex iTriangles = nif->getIndex( iPart, "Triangles" ); - nif->updateArray( iTriangles ); + nif->updateArraySize( iTriangles ); nif->setArray( iTriangles, triangles ); } @@ -955,11 +956,11 @@ class spSkinPartition final : public Spell nif->set( iPart, "Has Bone Indices", 1 ); QModelIndex iVBones = nif->getIndex( iPart, "Bone Indices" ); - nif->updateArray( iVBones ); + nif->updateArraySize( iVBones ); for ( int v = 0; v < nif->rowCount( iVBones ); v++ ) { QModelIndex iVertex = iVBones.child( v, 0 ); - nif->updateArray( iVertex ); + nif->updateArraySize( iVertex ); QList list = weights.value( vertices[v] ); for ( int b = 0; b < maxBones; b++ ) @@ -1022,7 +1023,7 @@ class spAllSkinPartitions final : public Spell spSkinPartition Partitioner; for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex idx = nif->getBlock( n ); + QModelIndex idx = nif->getBlockIndex( n ); if ( Partitioner.isApplicable( nif, idx ) ) indices.append( idx ); @@ -1155,12 +1156,12 @@ class spFixBoneBounds final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & iSkinData ) override final { - QModelIndex iSkinInstance = nif->getBlock( nif->getParent( nif->getBlockNumber( iSkinData ) ), "NiSkinInstance" ); - QModelIndex iMesh = nif->getBlock( nif->getParent( nif->getBlockNumber( iSkinInstance ) ) ); - QModelIndex iMeshData = nif->getBlock( nif->getLink( iMesh, "Data" ) ); + QModelIndex iSkinInstance = nif->getBlockIndex( nif->getParent( nif->getBlockNumber( iSkinData ) ), "NiSkinInstance" ); + QModelIndex iMesh = nif->getBlockIndex( nif->getParent( nif->getBlockNumber( iSkinInstance ) ) ); + QModelIndex iMeshData = nif->getBlockIndex( nif->getLink( iMesh, "Data" ) ); int skelRoot = nif->getLink( iSkinInstance, "Skeleton Root" ); - if ( !nif->inherits( iMeshData, "NiTriBasedGeomData" ) || skelRoot < 0 || skelRoot != nif->getParent( nif->getBlockNumber( iMesh ) ) ) + if ( !nif->blockInherits( iMeshData, "NiTriBasedGeomData" ) || skelRoot < 0 || skelRoot != nif->getParent( nif->getBlockNumber( iMesh ) ) ) return iSkinData; Transform meshTrans( nif, iMesh ); @@ -1169,7 +1170,7 @@ class spFixBoneBounds final : public Spell QModelIndex iBoneMap = nif->getIndex( iSkinInstance, "Bones" ); for ( int n = 0; n < nif->rowCount( iBoneMap ); n++ ) { - QModelIndex iBone = nif->getBlock( nif->getLink( iBoneMap.child( n, 0 ) ), "NiNode" ); + QModelIndex iBone = nif->getBlockIndex( nif->getLink( iBoneMap.child( n, 0 ) ), "NiNode" ); if ( skelRoot != nif->getParent( nif->getBlockNumber( iBone ) ) ) return iSkinData; @@ -1253,7 +1254,7 @@ class spMirrorSkeleton final : public Spell int n = 0; while ( n < nif->getBlockCount() ) { - QModelIndex iBlock = nif->getBlock( n ); + QModelIndex iBlock = nif->getBlockIndex( n ); if ( nif->itemName( iBlock ).indexOf( "NiKeyframe" ) >= 0 ) nif->removeNiBlock( n ); @@ -1303,7 +1304,7 @@ class spMirrorSkeleton final : public Spell // traverse for ( const auto link : nif->getChildLinks( nif->getBlockNumber( index ) ) ) { - QModelIndex iChild = nif->getBlock( link ); + QModelIndex iChild = nif->getBlockIndex( link ); QString childName = nif->get( iChild, "Name" ); // Might as well rename children now if we can - this is less case-critical than Bip01 L/R @@ -1320,11 +1321,11 @@ class spMirrorSkeleton final : public Spell if ( nif->itemName( iChild ) == "NiNode" ) { // repeat doBones( nif, iChild ); - } else if ( nif->inherits( iChild, "NiTriBasedGeom" ) ) { + } else if ( nif->blockInherits( iChild, "NiTriBasedGeom" ) ) { // Scale NiTriShape vertices, flip normals // Change SkinInstance bones doShapes( nif, iChild ); - } else if ( nif->inherits( iChild, "NiKeyframeController" ) ) { + } else if ( nif->blockInherits( iChild, "NiKeyframeController" ) ) { // Flip keyframe data, fun doKeyframes( nif, iChild ); } @@ -1335,8 +1336,8 @@ class spMirrorSkeleton final : public Spell void doShapes( NifModel * nif, const QModelIndex & index ) { //qDebug() << "Entering doShapes"; - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); - QModelIndex iSkinInstance = nif->getBlock( nif->getLink( index, "Skin Instance" ), "NiSkinInstance" ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); + QModelIndex iSkinInstance = nif->getBlockIndex( nif->getLink( index, "Skin Instance" ), "NiSkinInstance" ); if ( iData.isValid() && iSkinInstance.isValid() ) { // from spScaleVertices @@ -1377,7 +1378,7 @@ class spMirrorSkeleton final : public Spell // from spFixSkeleton - get the bones from the skin data // weirdness with rounding, sometimes...? probably "good enough" for 99% of cases - QModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInstance, "Data" ), "NiSkinData" ); + QModelIndex iSkinData = nif->getBlockIndex( nif->getLink( iSkinInstance, "Data" ), "NiSkinData" ); if ( !iSkinData.isValid() ) return; @@ -1416,7 +1417,7 @@ class spMirrorSkeleton final : public Spell void doKeyframes( NifModel * nif, QModelIndex & index ) { // do stuff - QModelIndex keyframeData = nif->getBlock( nif->getLink( index, "Data" ), "NiKeyframeData" ); + QModelIndex keyframeData = nif->getBlockIndex( nif->getLink( index, "Data" ), "NiKeyframeData" ); if ( !keyframeData.isValid() ) return; diff --git a/src/spells/stringpalette.cpp b/src/spells/stringpalette.cpp index 49d2f0cde..6f7c34932 100644 --- a/src/spells/stringpalette.cpp +++ b/src/spells/stringpalette.cpp @@ -136,7 +136,7 @@ class spEditStringOffset final : public Spell //! Gets the string palette referred to by this string offset static QModelIndex getStringPalette( const NifModel * nif, const QModelIndex & index ) { - QModelIndex iPalette = nif->getBlock( nif->getLink( index.parent(), "String Palette" ) ); + QModelIndex iPalette = nif->getBlockIndex( nif->getLink( index.parent(), "String Palette" ) ); if ( iPalette.isValid() ) return iPalette; @@ -295,8 +295,8 @@ class spEditStringEntries final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->inherits( index, "NiSequence" ) - && nif->getBlock( nif->getLink( index, "String Palette" ) ).isValid() + return nif->blockInherits( index, "NiSequence" ) + && nif->getBlockIndex( nif->getLink( index, "String Palette" ) ).isValid() && nif->checkVersion( 0x0A020000, 0x14000005 ); } @@ -305,11 +305,11 @@ class spEditStringEntries final : public Spell // string offset is used in ControllerLink which exists in NiSequence // a single palette could be share by multiple NiSequences - QPersistentModelIndex iPalette = nif->getBlock( nif->getLink( index, "String Palette" ) ); + QPersistentModelIndex iPalette = nif->getBlockIndex( nif->getLink( index, "String Palette" ) ); qDebug() << "This block uses " << iPalette; if ( !iPalette.isValid() ) { - iPalette = nif->getBlock( nif->getLink( index.parent(), "String Palette" ) ); + iPalette = nif->getBlockIndex( nif->getLink( index.parent(), "String Palette" ) ); if ( !iPalette.isValid() ) { qCWarning( nsSpell ) << Spell::tr( "Cannot find string palette" ); @@ -373,7 +373,7 @@ class spEditStringEntries final : public Spell QList sequenceList; for ( int i = 0; i < nif->getBlockCount(); i++ ) { - QPersistentModelIndex current = nif->getBlock( i, "NiSequence" ); + QPersistentModelIndex current = nif->getBlockIndex( i, "NiSequence" ); if ( current.isValid() ) { sequenceList.append( current ); @@ -388,7 +388,7 @@ class spEditStringEntries final : public Spell while ( sequenceListIterator.hasNext() ) { QPersistentModelIndex temp = sequenceListIterator.next(); - QPersistentModelIndex tempPalette = nif->getBlock( nif->getLink( temp, "String Palette" ) ); + QPersistentModelIndex tempPalette = nif->getBlockIndex( nif->getLink( temp, "String Palette" ) ); //qDebug() << "Sequence " << temp << " uses " << tempPalette; if ( iPalette == tempPalette ) { @@ -465,7 +465,7 @@ class spStringPaletteLister final : public Spell QList sequenceList; for ( int i = 0; i < nif->getBlockCount(); i++ ) { - QModelIndex current = nif->getBlock( i, "NiSequence" ); + QModelIndex current = nif->getBlockIndex( i, "NiSequence" ); if ( current.isValid() ) { sequenceList.append( current ); diff --git a/src/spells/strippify.cpp b/src/spells/strippify.cpp index 52245bd0c..ff544dff0 100644 --- a/src/spells/strippify.cpp +++ b/src/spells/strippify.cpp @@ -12,7 +12,7 @@ template void copyArray( NifModel * nif, const QModelIndex & iDst, const QModelIndex & iSrc ) { if ( iDst.isValid() && iSrc.isValid() ) { - nif->updateArray( iDst ); + nif->updateArraySize( iDst ); nif->setArray( iDst, nif->getArray( iSrc ) ); } } @@ -41,7 +41,7 @@ class spStrippify final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { QPersistentModelIndex idx = index; - QPersistentModelIndex iData = nif->getBlock( nif->getLink( idx, "Data" ), "NiTriShapeData" ); + QPersistentModelIndex iData = nif->getBlockIndex( nif->getLink( idx, "Data" ), "NiTriShapeData" ); if ( !iData.isValid() ) return idx; @@ -109,7 +109,7 @@ class spStrippify final : public Spell QModelIndex iSrcUV = nif->getIndex( iData, "UV Sets" ); if ( iDstUV.isValid() && iSrcUV.isValid() ) { - nif->updateArray( iDstUV ); + nif->updateArraySize( iDstUV ); for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) { copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); @@ -126,13 +126,13 @@ class spStrippify final : public Spell QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); if ( iLengths.isValid() && iPoints.isValid() ) { - nif->updateArray( iLengths ); - nif->updateArray( iPoints ); + nif->updateArraySize( iLengths ); + nif->updateArraySize( iPoints ); int x = 0; for ( const QVector& strip : strips ) { nif->set( iLengths.child( x, 0 ), strip.count() ); QModelIndex iStrip = iPoints.child( x, 0 ); - nif->updateArray( iStrip ); + nif->updateArraySize( iStrip ); nif->setArray( iStrip, strip ); x++; } @@ -152,7 +152,7 @@ class spStrippify final : public Spell // Copy the entire NiTriStrips branch auto iStrip2 = dupe.cast( nif, idx ); - auto iStrip2Data = nif->getBlock( nif->getLink( iStrip2, "Data" ), "NiTriStripsData" ); + auto iStrip2Data = nif->getBlockIndex( nif->getLink( iStrip2, "Data" ), "NiTriStripsData" ); if ( !iStrip2Data.isValid() || strips.count() != 2 ) return QModelIndex(); @@ -165,10 +165,10 @@ class spStrippify final : public Spell auto stripsA = strips.at(0); if ( iLengths.isValid() && iPoints.isValid() ) { - nif->updateArray( iLengths ); + nif->updateArraySize( iLengths ); nif->set( iLengths.child( 0, 0 ), stripsA.count() ); - nif->updateArray( iPoints ); - nif->updateArray( iPoints.child( 0, 0 ) ); + nif->updateArraySize( iPoints ); + nif->updateArraySize( iPoints.child( 0, 0 ) ); nif->setArray( iPoints.child( 0, 0 ), stripsA ); nif->set( iStripData, "Num Triangles", stripsA.count() - 2 ); } @@ -182,10 +182,10 @@ class spStrippify final : public Spell auto stripsB = strips.at(1); if ( iLengths.isValid() && iPoints.isValid() ) { - nif->updateArray( iLengths ); + nif->updateArraySize( iLengths ); nif->set( iLengths.child( 0, 0 ), stripsB.count() ); - nif->updateArray( iPoints ); - nif->updateArray( iPoints.child( 0, 0 ) ); + nif->updateArraySize( iPoints ); + nif->updateArraySize( iPoints.child( 0, 0 ) ); nif->setArray( iPoints.child( 0, 0 ), stripsB ); nif->set( iStrip2Data, "Num Triangles", stripsB.count() - 2 ); } @@ -214,7 +214,7 @@ class spStrippifyAll final : public Spell QList iTriShapes; for ( int l = 0; l < nif->getBlockCount(); l++ ) { - QModelIndex idx = nif->getBlock( l, "NiTriShape" ); + QModelIndex idx = nif->getBlockIndex( l, "NiTriShape" ); if ( idx.isValid() ) iTriShapes << idx; @@ -246,7 +246,7 @@ class spTriangulate final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { QPersistentModelIndex idx = index; - QPersistentModelIndex iStripData = nif->getBlock( nif->getLink( idx, "Data" ), "NiTriStripsData" ); + QPersistentModelIndex iStripData = nif->getBlockIndex( nif->getLink( idx, "Data" ), "NiTriStripsData" ); if ( !iStripData.isValid() ) return idx; @@ -271,7 +271,7 @@ class spTriangulate final : public Spell QVector triangles = triangulate( strips ); nif->insertNiBlock( "NiTriShapeData", nif->getBlockNumber( idx ) + 1 ); - QModelIndex iTriData = nif->getBlock( nif->getBlockNumber( idx ) + 1, "NiTriShapeData" ); + QModelIndex iTriData = nif->getBlockIndex( nif->getBlockNumber( idx ) + 1, "NiTriShapeData" ); if ( iTriData.isValid() ) { copyValue( nif, iTriData, iStripData, "Num Vertices" ); @@ -296,7 +296,7 @@ class spTriangulate final : public Spell QModelIndex iSrcUV = nif->getIndex( iStripData, "UV Sets" ); if ( iDstUV.isValid() && iSrcUV.isValid() ) { - nif->updateArray( iDstUV ); + nif->updateArraySize( iDstUV ); for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) { copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); @@ -313,7 +313,7 @@ class spTriangulate final : public Spell QModelIndex iTriangles = nif->getIndex( iTriData, "Triangles" ); if ( iTriangles.isValid() ) { - nif->updateArray( iTriangles ); + nif->updateArraySize( iTriangles ); nif->setArray( iTriangles, triangles ); } @@ -346,7 +346,7 @@ class spTriangulateAll final : public Spell QList triStrips; for ( int l = 0; l < nif->getBlockCount(); l++ ) { - QModelIndex idx = nif->getBlock( l, "NiTriStrips" ); + QModelIndex idx = nif->getBlockIndex( l, "NiTriStrips" ); if ( idx.isValid() ) triStrips << idx; } @@ -371,9 +371,9 @@ class spStichStrips final : public Spell static QModelIndex getStripsData( const NifModel * nif, const QModelIndex & index ) { if ( nif->isNiBlock( index, "NiTriStrips" ) ) - return nif->getBlock( nif->getLink( index, "Data" ), "NiTriStripsData" ); + return nif->getBlockIndex( nif->getLink( index, "Data" ), "NiTriStripsData" ); - return nif->getBlock( index, "NiTriStripsData" ); + return nif->getBlockIndex( index, "NiTriStripsData" ); } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final @@ -411,10 +411,10 @@ class spStichStrips final : public Spell } nif->set( iData, "Num Strips", 1 ); - nif->updateArray( iLength ); + nif->updateArraySize( iLength ); nif->set( iLength.child( 0, 0 ), strip.size() ); - nif->updateArray( iPoints ); - nif->updateArray( iPoints.child( 0, 0 ) ); + nif->updateArraySize( iPoints ); + nif->updateArraySize( iPoints.child( 0, 0 ) ); nif->setArray( iPoints.child( 0, 0 ), strip ); return index; @@ -483,12 +483,12 @@ class spUnstichStrips final : public Spell strips << scratch; nif->set( iData, "Num Strips", strips.size() ); - nif->updateArray( iLength ); - nif->updateArray( iPoints ); + nif->updateArraySize( iLength ); + nif->updateArraySize( iPoints ); for ( int r = 0; r < strips.count(); r++ ) { nif->set( iLength.child( r, 0 ), strips[r].size() ); - nif->updateArray( iPoints.child( r, 0 ) ); + nif->updateArraySize( iPoints.child( r, 0 ) ); nif->setArray( iPoints.child( r, 0 ), strips[r] ); } diff --git a/src/spells/tangentspace.cpp b/src/spells/tangentspace.cpp index 9723fb59b..510aecbd4 100644 --- a/src/spells/tangentspace.cpp +++ b/src/spells/tangentspace.cpp @@ -5,7 +5,7 @@ bool spTangentSpace::isApplicable( const NifModel * nif, const QModelIndex & index ) { - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( index, "Data" ) ); if ( nif->isNiBlock( index, "BSTriShape" ) || nif->isNiBlock( index, "BSSubIndexTriShape" ) || nif->isNiBlock( index, "BSMeshLODTriShape" ) ) { @@ -44,14 +44,14 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) QModelIndex iData; QModelIndex iPartBlock; if ( nif->getBSVersion() < 100 ) { - iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + iData = nif->getBlockIndex( nif->getLink( iShape, "Data" ) ); } else { auto vf = nif->get( iShape, "Vertex Desc" ); if ( (vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( iShape, "Skin" ) ); - auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); - iPartBlock = nif->getBlock( partID, "NiSkinPartition" ); + auto partID = nif->getLink( nif->getBlockIndex( skinID, "NiSkinInstance" ), "Skin Partition" ); + iPartBlock = nif->getBlockIndex( partID, "NiSkinPartition" ); if ( iPartBlock.isValid() ) iData = nif->getIndex( iPartBlock, "Vertex Data" ); } else { @@ -247,7 +247,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) if ( isOblivion ) { QModelIndex iTSpace; for ( const auto link : nif->getChildLinks( nif->getBlockNumber( iShape ) ) ) { - iTSpace = nif->getBlock( link, "NiBinaryExtraData" ); + iTSpace = nif->getBlockIndex( link, "NiBinaryExtraData" ); if ( iTSpace.isValid() && nif->get( iTSpace, "Name" ) == "Tangent space (binormal & tangent vectors)" ) break; @@ -264,7 +264,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) if ( iNumExtras.isValid() && iExtras.isValid() ) { int numlinks = nif->get( iNumExtras ); nif->set( iNumExtras, numlinks + 1 ); - nif->updateArray( iExtras ); + nif->updateArraySize( iExtras ); nif->setLink( iExtras.child( numlinks, 0 ), nif->getBlockNumber( iTSpace ) ); } } @@ -273,8 +273,8 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) } else if ( nif->getBSVersion() < 100 ) { QModelIndex iBinorms = nif->getIndex( iData, "Bitangents" ); QModelIndex iTangents = nif->getIndex( iData, "Tangents" ); - nif->updateArray( iBinorms ); - nif->updateArray( iTangents ); + nif->updateArraySize( iBinorms ); + nif->updateArraySize( iTangents ); nif->setArray( iBinorms, bin ); nif->setArray( iTangents, tan ); } else if ( nif->getBSVersion() >= 100 ) { @@ -335,7 +335,7 @@ class spAllTangentSpaces final : public Spell spTangentSpace TSpacer; for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex idx = nif->getBlock( n ); + QModelIndex idx = nif->getBlockIndex( n ); if ( TSpacer.isApplicable( nif, idx ) ) indices << idx; @@ -367,12 +367,12 @@ class spAddAllTangentSpaces final : public Spell { QVector blks; for ( int l = 0; l < nif->getBlockCount(); l++ ) { - QModelIndex idx = nif->getBlock( l, "NiTriShape" ); + QModelIndex idx = nif->getBlockIndex( l, "NiTriShape" ); if ( !idx.isValid() ) continue; // NiTriShapeData - auto iData = nif->getBlock( nif->getLink( idx, "Data" ) ); + auto iData = nif->getBlockIndex( nif->getLink( idx, "Data" ) ); // Do not do anything without proper UV/Vert/Tri data auto numVerts = nif->get( iData, "Num Vertices" ); @@ -382,8 +382,8 @@ class spAddAllTangentSpaces final : public Spell continue; nif->set( iData, "Data Flags", 4097 ); - nif->updateArray( iData, "Tangents" ); - nif->updateArray( iData, "Bitangents" ); + nif->updateArraySize( iData, "Tangents" ); + nif->updateArraySize( iData, "Bitangents" ); // Add NiTriShape for spTangentSpace blks << idx; diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index 8d29144fb..15a004122 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -132,7 +132,7 @@ static QIconPtr tex42_xpm_icon = nullptr; QModelIndex getData( const NifModel * nif, const QModelIndex & index ) { if ( nif->isNiBlock( index, { "NiTriShape", "NiTriStrips", "BSLODTriShape" } ) ) - return nif->getBlock( nif->getLink( index, "Data" ) ); + return nif->getBlockIndex( nif->getLink( index, "Data" ) ); if ( nif->isNiBlock( index, { "NiTriShapeData", "NiTriStripsData" } ) ) return index; @@ -171,7 +171,7 @@ class spChooseTexture final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & idx ) override final { - QModelIndex iBlock = nif->getBlock( idx ); + QModelIndex iBlock = nif->getBlockIndex( idx ); if ( nif->isNiBlock( iBlock, { "NiSourceTexture", "NiImage" } ) && ( iBlock == idx.sibling( idx.row(), 0 ) || nif->itemName( idx ) == "File Name" ) ) @@ -190,7 +190,7 @@ class spChooseTexture final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & idx ) override final { - QModelIndex iBlock = nif->getBlock( idx ); + QModelIndex iBlock = nif->getBlockIndex( idx ); QModelIndex iFile; bool setExternal = false; @@ -267,7 +267,7 @@ class spEditTexCoords final : public Spell auto iUVs = getUV( nif, index ); auto iTriData = nif->getIndex( index, "Num Triangles" ); return (iUVs.isValid() || iTriData.isValid()) - && (nif->inherits( index, "NiTriBasedGeom" ) || nif->inherits( index, "BSTriShape" )); + && (nif->blockInherits( index, "NiTriBasedGeom" ) || nif->blockInherits( index, "BSTriShape" )); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -287,7 +287,7 @@ REGISTER_SPELL( spEditTexCoords ) */ QModelIndex addTexture( NifModel * nif, const QModelIndex & index, const QString & name ) { - QModelIndex iTexProp = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex iTexProp = nif->getBlockIndex( index, "NiTexturingProperty" ); if ( !iTexProp.isValid() ) return index; @@ -329,7 +329,7 @@ class spAddBaseMap final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Base Texture" ) == 0 ); } @@ -350,7 +350,7 @@ class spAddDarkMap final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Dark Texture" ) == 0 ); } @@ -371,7 +371,7 @@ class spAddDetailMap final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Detail Texture" ) == 0 ); } @@ -392,7 +392,7 @@ class spAddGlowMap final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Glow Texture" ) == 0 ); } @@ -413,14 +413,14 @@ class spAddBumpMap final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Bump Map Texture" ) == 0 ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { QModelIndex iSrcTex = addTexture( nif, index, "Bump Map Texture" ); - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); nif->set( block, "Bump Map Luma Scale", 1.0 ); nif->set( block, "Bump Map Luma Offset", 0.0 ); QModelIndex iMatrix = nif->getIndex( block, "Bump Map Matrix" ); @@ -443,7 +443,7 @@ class spAddDecal0Map final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Decal 0 Texture" ) == 0 ); } @@ -464,7 +464,7 @@ class spAddDecal1Map final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Decal 1 Texture" ) == 0 ); } @@ -485,7 +485,7 @@ class spAddDecal2Map final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Decal 2 Texture" ) == 0 ); } @@ -506,7 +506,7 @@ class spAddDecal3Map final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return ( block.isValid() && nif->get( block, "Has Decal 3 Texture" ) == 0 ); } @@ -532,7 +532,7 @@ class spTextureTemplate final : public Spell { QModelIndex iUVs = getUV( nif, index ); auto iTriData = nif->getIndex( index, "Num Triangles" ); - bool bstri = nif->getBSVersion() >= 100 && nif->inherits( index, "BSTriShape" ) && iTriData.isValid(); + bool bstri = nif->getBSVersion() >= 100 && nif->blockInherits( index, "BSTriShape" ) && iTriData.isValid(); return (iUVs.isValid() && nif->rowCount( iUVs ) >= 1) || bstri; } @@ -634,8 +634,8 @@ class spTextureTemplate final : public Spell if ( (vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( index, "Skin" ) ); - auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); - auto iPartBlock = nif->getBlock( partID, "NiSkinPartition" ); + auto partID = nif->getLink( nif->getBlockIndex( skinID, "NiSkinInstance" ), "Skin Partition" ); + auto iPartBlock = nif->getBlockIndex( partID, "NiSkinPartition" ); if ( !iPartBlock.isValid() ) return index; @@ -790,7 +790,7 @@ class spMultiApplyMode final : public Spell QList indices; for ( int n = 0; n < nif->getBlockCount(); n++ ) { - QModelIndex idx = nif->getBlock( n ); + QModelIndex idx = nif->getBlockIndex( n ); indices << idx; } @@ -807,7 +807,7 @@ class spMultiApplyMode final : public Spell if ( !index.isValid() ) return; - if ( nif->inherits( index, "NiTexturingProperty" ) + if ( nif->blockInherits( index, "NiTexturingProperty" ) && nif->get( index, "Apply Mode" ) == rep ) nif->set( index, "Apply Mode", by ); @@ -819,7 +819,7 @@ class spMultiApplyMode final : public Spell qint32 link = nif->getLink( iChildren.child( c, 0 ) ); if ( lChildren.contains( link ) ) { - QModelIndex iChild = nif->getBlock( link ); + QModelIndex iChild = nif->getBlockIndex( link ); replaceApplyMode( nif, iChild, rep, by ); } } @@ -829,7 +829,7 @@ class spMultiApplyMode final : public Spell if ( iProperties.isValid() ) { for ( int p = 0; p < nif->rowCount( iProperties ); p++ ) { - QModelIndex iProp = nif->getBlock( nif->getLink( iProperties.child( p, 0 ) ) ); + QModelIndex iProp = nif->getBlockIndex( nif->getLink( iProperties.child( p, 0 ) ) ); replaceApplyMode( nif, iProp, rep, by ); } } @@ -847,7 +847,7 @@ class spTexInfo final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iBlock = nif->getBlock( index ); + QModelIndex iBlock = nif->getBlockIndex( index ); if ( nif->isNiBlock( iBlock, "NiSourceTexture" ) ) return true; @@ -887,17 +887,17 @@ class spExportTexture final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - QModelIndex iBlock = nif->getBlock( index ); + QModelIndex iBlock = nif->getBlockIndex( index ); if ( nif->isNiBlock( iBlock, "NiSourceTexture" ) && nif->get( iBlock, "Use External" ) == 0 ) { - QModelIndex iData = nif->getBlock( nif->getLink( index, "Pixel Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( index, "Pixel Data" ) ); if ( iData.isValid() ) { return true; } - } else if ( nif->inherits( iBlock, "NiPixelFormat" ) ) { + } else if ( nif->blockInherits( iBlock, "NiPixelFormat" ) ) { int thisBlockNumber = nif->getBlockNumber( index ); - QModelIndex iParent = nif->getBlock( nif->getParent( thisBlockNumber ) ); + QModelIndex iParent = nif->getBlockIndex( nif->getParent( thisBlockNumber ) ); if ( !iParent.isValid() ) { return true; @@ -911,9 +911,9 @@ class spExportTexture final : public Spell { TexCache * tex = new TexCache(); tex->setNifFolder( nif->getFolder() ); - QModelIndex iBlock = nif->getBlock( index ); + QModelIndex iBlock = nif->getBlockIndex( index ); - if ( nif->inherits( iBlock, "NiSourceTexture" ) ) { + if ( nif->blockInherits( iBlock, "NiSourceTexture" ) ) { tex->bind( index ); QString file = nif->getFolder(); @@ -922,7 +922,7 @@ class spExportTexture final : public Spell file.append( "/" + nif->get( index, "File Name" ) ); } - QModelIndex iData = nif->getBlock( nif->getLink( index, "Pixel Data" ) ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( index, "Pixel Data" ) ); QString filename = QFileDialog::getSaveFileName( qApp->activeWindow(), Spell::tr( "Export texture" ), file, "Textures (*.dds *.tga)" ); if ( !filename.isEmpty() ) { @@ -935,7 +935,7 @@ class spExportTexture final : public Spell } return index; - } else if ( nif->inherits( iBlock, "NiPixelFormat" ) ) { + } else if ( nif->blockInherits( iBlock, "NiPixelFormat" ) ) { TexCache * tex = new TexCache(); tex->setNifFolder( nif->getFolder() ); QString file = nif->getFolder(); @@ -965,7 +965,7 @@ class spEmbedTexture final : public Spell return false; } - QModelIndex iBlock = nif->getBlock( index ); + QModelIndex iBlock = nif->getBlockIndex( index ); if ( !( nif->isNiBlock( iBlock, "NiSourceTexture" ) && nif->get( iBlock, "Use External" ) == 1 ) ) { return false; @@ -991,8 +991,8 @@ class spEmbedTexture final : public Spell int blockNum = nif->getBlockNumber( index ); nif->insertNiBlock( "NiPixelData", blockNum + 1 ); - QPersistentModelIndex iSourceTexture = nif->getBlock( blockNum, "NiSourceTexture" ); - QModelIndex iPixelData = nif->getBlock( blockNum + 1, "NiPixelData" ); + QPersistentModelIndex iSourceTexture = nif->getBlockIndex( blockNum, "NiSourceTexture" ); + QModelIndex iPixelData = nif->getBlockIndex( blockNum + 1, "NiPixelData" ); //qDebug() << "spEmbedTexture: Block number" << blockNum << "holds source" << iSourceTexture << "Pixel data will be stored in" << iPixelData; @@ -1144,7 +1144,7 @@ void TexFlipDialog::listFromNif() QStringList sourceFiles; for ( int i = 0; i < numSources; i++ ) { - QModelIndex source = nif->getBlock( nif->getLink( sources.child( i, 0 ) ) ); + QModelIndex source = nif->getBlockIndex( nif->getLink( sources.child( i, 0 ) ) ); sourceFiles << nif->get( source, "File Name" ); } @@ -1163,7 +1163,7 @@ class spEditFlipper final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return ( nif->getBlockName( index ) == "NiFlipController" ); + return ( nif->itemName( index ) == "NiFlipController" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -1199,7 +1199,7 @@ class spEditFlipper final : public Spell } nif->set( flipController, "Num Sources", flipNames.size() ); - nif->updateArray( sources ); + nif->updateArraySize( sources ); for ( int i = 0; i < flipNames.size(); i++ ) { QString name = TexCache::stripPath( flipNames.at( i ), nif->getFolder() ); @@ -1209,7 +1209,7 @@ class spEditFlipper final : public Spell sourceTex = nif->insertNiBlock( "NiSourceTexture", nif->getBlockNumber( flipController ) + i + 1 ); nif->setLink( sources.child( i, 0 ), nif->getBlockNumber( sourceTex ) ); } else { - sourceTex = nif->getBlock( nif->getLink( sources.child( i, 0 ) ) ); + sourceTex = nif->getBlockIndex( nif->getLink( sources.child( i, 0 ) ) ); } nif->set( sourceTex, "File Name", name ); @@ -1235,7 +1235,7 @@ class spTextureFlipper final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { // also check NiTextureProperty? - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + QModelIndex block = nif->getBlockIndex( index, "NiTexturingProperty" ); return block.isValid(); } diff --git a/src/spells/transform.cpp b/src/spells/transform.cpp index 53d73185d..5d5cf583b 100644 --- a/src/spells/transform.cpp +++ b/src/spells/transform.cpp @@ -120,7 +120,7 @@ QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & ind Transform tp( nif, index ); bool ok = false; for ( const auto l : nif->getChildLinks( nif->getBlockNumber( index ) ) ) { - QModelIndex iChild = nif->getBlock( l ); + QModelIndex iChild = nif->getBlockIndex( l ); if ( iChild.isValid() && nif->inherits( nif->itemName( iChild ), "NiAVObject" ) ) { Transform tc( nif, iChild ); @@ -138,9 +138,9 @@ QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & ind QModelIndex iData; if ( nif->itemName( index ) == "NiTriShape" || nif->itemName( index ) == "BSLODTriShape" ) - iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTriShapeData" ); + iData = nif->getBlockIndex( nif->getLink( index, "Data" ), "NiTriShapeData" ); else if ( nif->itemName( index ) == "NiTriStrips" ) - iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTriStripsData" ); + iData = nif->getBlockIndex( nif->getLink( index, "Data" ), "NiTriStripsData" ); if ( iData.isValid() ) { Transform t( nif, index ); @@ -212,9 +212,9 @@ QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & ind nif->set( iVert, "Tangent", t.rotation * tangent ); // Unpack, Transform, Pack Bitangent - auto bitX = nif->getValue( nif->getIndex( iVert, "Bitangent X" ) ).toFloat(); - auto bitYi = nif->getValue( nif->getIndex( iVert, "Bitangent Y" ) ).toCount(); - auto bitZi = nif->getValue( nif->getIndex( iVert, "Bitangent Z" ) ).toCount(); + auto bitX = nif->get( iVert, "Bitangent X" ); + auto bitYi = nif->get( iVert, "Bitangent Y" ); + auto bitZi = nif->get( iVert, "Bitangent Z" ); auto bit = t.rotation * Vector3( bitX, (bitYi / 255.0) * 2.0 - 1.0, (bitZi / 255.0) * 2.0 - 1.0 ); @@ -365,7 +365,7 @@ class spEditTransformation final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - NifBlockEditor * edit = new NifBlockEditor( nif, nif->getBlock( index ) ); + NifBlockEditor * edit = new NifBlockEditor( nif, nif->getBlockIndex( index ) ); if ( Transform::canConstruct( nif, index ) ) { edit->add( new NifVectorEdit( nif, nif->getIndex( index, "Translation" ) ) ); @@ -396,7 +396,7 @@ class spScaleVertices final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->inherits( index, "NiGeometry" ); + return nif->blockInherits( index, "NiGeometry" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -434,7 +434,7 @@ class spScaleVertices final : public Spell settings.setValue( key, chkNormals->isChecked() ); - QModelIndex iData = nif->getBlock( nif->getLink( nif->getBlock( index ), "Data" ), "NiGeometryData" ); + QModelIndex iData = nif->getBlockIndex( nif->getLink( nif->getBlockIndex( index ), "Data" ), "NiGeometryData" ); QVector vertices = nif->getArray( iData, "Vertices" ); QMutableVectorIterator it( vertices ); diff --git a/src/ui/widgets/inspect.cpp b/src/ui/widgets/inspect.cpp index dfd655d0c..afcb34378 100644 --- a/src/ui/widgets/inspect.cpp +++ b/src/ui/widgets/inspect.cpp @@ -401,7 +401,7 @@ void InspectView::update() mat.toEuler( x, y, z ); impl->nameText->setText( nif->get( selection, "Name" ) ); - impl->typeText->setText( nif->getBlockName( selection ) ); + impl->typeText->setText( nif->itemName( selection ) ); impl->timeText->setText( QString( "%1" ).Farg( impl->time ) ); impl->posXText->setText( QString( "%1" ).Farg( tm.translation[0] ) ); diff --git a/src/ui/widgets/nifeditors.cpp b/src/ui/widgets/nifeditors.cpp index 2a9295479..5844eb647 100644 --- a/src/ui/widgets/nifeditors.cpp +++ b/src/ui/widgets/nifeditors.cpp @@ -137,7 +137,7 @@ void NifBlockEditor::nifDestroyed() void NifBlockEditor::nifDataChanged( const QModelIndex & begin, const QModelIndex & end ) { if ( nif && iBlock.isValid() ) { - if ( nif->getBlockOrHeader( begin ) == iBlock || nif->getBlockOrHeader( end ) == iBlock ) { + if ( nif->getTopIndex( begin ) == iBlock || nif->getTopIndex( end ) == iBlock ) { if ( !timer->isActive() ) timer->start(); } @@ -193,7 +193,7 @@ QLayout * NifEditBox::getLayout() void NifEditBox::sltReset() { if ( nif && index.isValid() ) - nif->setValue( index, originalValue ); + nif->setIndexValue( index, originalValue ); } void NifEditBox::sltApplyData() @@ -309,24 +309,26 @@ NifVectorEdit::NifVectorEdit( NifModel * n, const QModelIndex & index ) connect( vector, &VectorEdit::sigEdited, this, &NifVectorEdit::sltApplyData ); } -void NifVectorEdit::updateData( NifModel * n ) +void NifVectorEdit::updateData( NifModel * dstNif ) { - NifValue val = n->getValue( index ); - - if ( val.type() == NifValue::tVector3 ) - vector->setVector3( val.get() ); - else if ( val.type() == NifValue::tVector2 ) - vector->setVector2( val.get() ); + NifItem * item = dstNif->getItem( index ); + if ( item ) { + if ( item->valueIsVector3() ) + vector->setVector3( item->get() ); + else if ( item->valueIsVector2() ) + vector->setVector2( item->get() ); + } } -void NifVectorEdit::applyData( NifModel * n ) +void NifVectorEdit::applyData( NifModel * dstNif ) { - NifValue::Type type = n->getValue( index ).type(); - - if ( type == NifValue::tVector3 ) - n->set( index, vector->getVector3() ); - else if ( type == NifValue::tVector2 ) - n->set( index, vector->getVector2() ); + NifItem * item = dstNif->getItem( index ); + if ( item ) { + if ( item->valueIsVector3() ) + item->set( vector->getVector3() ); + else if ( item->valueIsVector2() ) + item->set( vector->getVector2() ); + } } NifRotationEdit::NifRotationEdit( NifModel * n, const QModelIndex & index ) @@ -336,24 +338,26 @@ NifRotationEdit::NifRotationEdit( NifModel * n, const QModelIndex & index ) connect( rotation, &RotationEdit::sigEdited, this, &NifRotationEdit::sltApplyData ); } -void NifRotationEdit::updateData( NifModel * n ) +void NifRotationEdit::updateData( NifModel * dstNif ) { - NifValue val = n->getValue( index ); - - if ( val.type() == NifValue::tMatrix ) - rotation->setMatrix( val.get() ); - else if ( val.type() == NifValue::tQuat || val.type() == NifValue::tQuatXYZW ) - rotation->setQuat( val.get() ); + const NifItem * item = dstNif->getItem( index ); + if ( item ) { + if ( item->valueIsMatrix() ) + rotation->setMatrix( item->get() ); + else if ( item->valueIsQuat() ) + rotation->setQuat( item->get() ); + } } -void NifRotationEdit::applyData( NifModel * n ) +void NifRotationEdit::applyData( NifModel * dstNif ) { - NifValue::Type type = n->getValue( index ).type(); - - if ( type == NifValue::tMatrix ) - n->set( index, rotation->getMatrix() ); - else if ( type == NifValue::tQuat || type == NifValue::tQuatXYZW ) - n->set( index, rotation->getQuat() ); + NifItem * item = dstNif->getItem( index ); + if ( item ) { + if ( item->valueIsMatrix() ) + item->set( rotation->getMatrix() ); + else if ( item->valueIsQuat() ) + item->set( rotation->getQuat() ); + } } NifMatrix4Edit::NifMatrix4Edit( NifModel * n, const QModelIndex & index ) diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 48fb2bf7a..066b4ade1 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -107,11 +107,23 @@ void NifTreeView::setRowHiding( bool show ) bool NifTreeView::isRowHidden( int r, const QModelIndex & index ) const { - NifItem * item = static_cast(index.internalPointer()); - if ( !item || !doRowHiding ) - return false; + const NifItem * item = static_cast( index.internalPointer() ); + return isRowHidden( item ); +} + +bool NifTreeView::isRowHidden( const NifItem * rowItem ) const +{ + if ( rowItem && nif ) { + if ( doRowHiding ) { + if ( !nif->evalCondition( rowItem )) + return true; + } /* else { + if ( !nif->evalVersion( rowItem ) ) + return true; + } */ + } - return !item->condition(); + return false; } void NifTreeView::setAllExpanded( const QModelIndex & index, bool e ) @@ -161,23 +173,21 @@ void NifTreeView::pasteTo( QModelIndex iDest, const NifValue & srcValue ) if ( iDest.column() != NifModel::ValueCol ) return; - NifItem * item = static_cast(iDest.internalPointer()); + NifItem * item = nif->getItem( iDest ); + if ( !item || item->valueType() != srcValue.type() ) + return; auto valueType = model()->sibling( iDest.row(), 0, iDest ).data().toString(); - NifValue destValue = item->value(); - if ( destValue.type() != srcValue.type() ) - return; - - switch ( destValue.type() ) { + switch ( item->valueType() ) { case NifValue::tByte: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tWord: case NifValue::tShort: case NifValue::tFlags: case NifValue::tBlockTypeIndex: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tStringOffset: case NifValue::tInt: @@ -186,40 +196,40 @@ void NifTreeView::pasteTo( QModelIndex iDest, const NifValue & srcValue ) case NifValue::tStringIndex: case NifValue::tUpLink: case NifValue::tLink: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tVector2: case NifValue::tHalfVector2: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tVector3: case NifValue::tByteVector3: case NifValue::tHalfVector3: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tVector4: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tFloat: case NifValue::tHfloat: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tColor3: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tColor4: case NifValue::tByteColor4: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tQuat: case NifValue::tQuatXYZW: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tMatrix: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tMatrix4: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tString: case NifValue::tSizedString: @@ -228,7 +238,7 @@ void NifTreeView::pasteTo( QModelIndex iDest, const NifValue & srcValue ) case NifValue::tHeaderString: case NifValue::tLineString: case NifValue::tChar8String: - nif->set( iDest, srcValue.get() ); + item->set( srcValue.get( nif, nullptr ) ); break; default: // Return and do not push to Undo Stack @@ -237,7 +247,7 @@ void NifTreeView::pasteTo( QModelIndex iDest, const NifValue & srcValue ) auto n = static_cast(nif); if ( n ) - n->undoStack->push( new ChangeValueCommand( iDest, destValue, srcValue, valueType, n ) ); + n->undoStack->push( new ChangeValueCommand( iDest, item->value(), srcValue, valueType, n ) ); } void NifTreeView::paste() @@ -306,7 +316,7 @@ void NifTreeView::updateConditionRecurse( const QModelIndex & index ) updateConditionRecurse( child ); } - setRowHidden( index.row(), index.parent(), doRowHiding && !item->condition() ); + setRowHidden( index.row(), index.parent(), isRowHidden(item) ); } auto splitMime = []( QString format ) { @@ -444,7 +454,7 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) nif->resetState(); // Refresh the header - nif->invalidateConditions( nif->getHeader(), true ); + nif->invalidateHeaderConditions(); nif->updateHeader(); if ( noSignals && nif->getProcessingResult() ) { @@ -488,16 +498,16 @@ void NifTreeView::currentChanged( const QModelIndex & current, const QModelIndex if ( mdl && mdl->isNiBlock( current ) ) { auto cnt = mdl->rowCount( current ); const int ARRAY_LIMIT = 100; - if ( mdl->inherits( current, "NiTransformInterpolator" ) - || mdl->inherits( current, "NiBSplineTransformInterpolator" ) ) { + if ( mdl->blockInherits( current, "NiTransformInterpolator" ) + || mdl->blockInherits( current, "NiBSplineTransformInterpolator" ) ) { // Auto-Expand NiQuatTransform autoExpand( current.child( 0, 0 ) ); - } else if ( mdl->inherits( current, "NiNode" ) ) { + } else if ( mdl->blockInherits( current, "NiNode" ) ) { // Auto-Expand Children array auto iChildren = mdl->getIndex( current, "Children" ); if ( mdl->rowCount( iChildren ) < ARRAY_LIMIT ) autoExpand( iChildren ); - } else if ( mdl->inherits( current, "NiSkinPartition" ) ) { + } else if ( mdl->blockInherits( current, "NiSkinPartition" ) ) { // Auto-Expand skin partitions array autoExpand( current.child( 1, 0 ) ); } else if ( mdl->getValue( current.child( cnt - 1, 0 ) ).type() == NifValue::tNone diff --git a/src/ui/widgets/nifview.h b/src/ui/widgets/nifview.h index 9e5e2abf0..f87b420e1 100644 --- a/src/ui/widgets/nifview.h +++ b/src/ui/widgets/nifview.h @@ -38,6 +38,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +class NifItem; //! Widget for showing a nif file as tree, list, or block details. class NifTreeView final : public QTreeView @@ -59,7 +60,10 @@ class NifTreeView final : public QTreeView bool evalConditions() const { return doRowHiding; } //! Is a row hidden? bool isRowHidden( int row, const QModelIndex & parent ) const; +protected: + bool isRowHidden( const NifItem * rowItem ) const; +public: //! Minimum size QSize minimumSizeHint() const override final { return { 50, 50 }; } //! Default size diff --git a/src/ui/widgets/refrbrowser.cpp b/src/ui/widgets/refrbrowser.cpp index ab7b2f4c3..5cc517bed 100644 --- a/src/ui/widgets/refrbrowser.cpp +++ b/src/ui/widgets/refrbrowser.cpp @@ -88,10 +88,10 @@ void ReferenceBrowser::browse( const QModelIndex & index ) return; } - QString blockType = nif->getBlockType( index ); + QString blockType = nif->itemType( index ); if ( blockType == "NiBlock" ) { - blockType = nif->getBlockName( index ); + blockType = nif->itemName( index ); } if ( !QFileInfo( docFolder.filePath( "%1.html" ).arg( blockType ) ).exists() ) { diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 5b6cacc5e..ec7b7ad02 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -839,7 +839,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) textures->setNifFolder( nif->getFolder() ); - iShapeData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + iShapeData = nif->getBlockIndex( nif->getLink( iShape, "Data" ) ); if ( nif->getVersionNumber() == 0x14020007 && nif->getBSVersion() >= 100 ) { iShapeData = nif->getIndex( iShape, "Vertex Data" ); @@ -847,8 +847,8 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) if ( (vf & VertexFlags::VF_SKINNED) && nif->getBSVersion() == 100 ) { // Skinned SSE auto skinID = nif->getLink( nif->getIndex( iShape, "Skin" ) ); - auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); - iPartBlock = nif->getBlock( partID, "NiSkinPartition" ); + auto partID = nif->getLink( nif->getBlockIndex( skinID, "NiSkinInstance" ), "Skin Partition" ); + iPartBlock = nif->getBlockIndex( partID, "NiSkinPartition" ); if ( !iPartBlock.isValid() ) return false; @@ -858,7 +858,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) } } - if ( nif->inherits( iShapeData, "NiTriBasedGeomData" ) ) { + if ( nif->blockInherits( iShapeData, "NiTriBasedGeomData" ) ) { iTexCoords = nif->getIndex( iShapeData, "UV Sets" ).child( 0, 0 ); if ( !iTexCoords.isValid() || !nif->rowCount( iTexCoords ) ) { @@ -868,7 +868,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) if ( !setTexCoords() ) { return false; } - } else if ( nif->inherits( iShape, "BSTriShape" ) ) { + } else if ( nif->blockInherits( iShape, "BSTriShape" ) ) { int numVerts = 0; if ( !isDataOnSkin ) numVerts = nif->get( iShape, "Num Vertices" ); @@ -890,14 +890,14 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) props << nif->getLink( iShape, "Shader Property" ); for ( const auto l : props ) { - QModelIndex iTexProp = nif->getBlock( l, "NiTexturingProperty" ); + QModelIndex iTexProp = nif->getBlockIndex( l, "NiTexturingProperty" ); if ( iTexProp.isValid() ) { while ( currentTexSlot < texnames.size() ) { iTex = nif->getIndex( iTexProp, texnames[currentTexSlot] ); if ( iTex.isValid() ) { - QModelIndex iTexSource = nif->getBlock( nif->getLink( iTex, "Source" ) ); + QModelIndex iTexSource = nif->getBlockIndex( nif->getLink( iTex, "Source" ) ); if ( iTexSource.isValid() ) { currentCoordSet = nif->get( iTex, "UV Set" ); @@ -912,10 +912,10 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) } } } else { - iTexProp = nif->getBlock( l, "NiTextureProperty" ); + iTexProp = nif->getBlockIndex( l, "NiTextureProperty" ); if ( iTexProp.isValid() ) { - QModelIndex iTexSource = nif->getBlock( nif->getLink( iTexProp, "Image" ) ); + QModelIndex iTexSource = nif->getBlockIndex( nif->getLink( iTexProp, "Image" ) ); if ( iTexSource.isValid() ) { //texfile = TexCache::find( nif->get( iTexSource, "File Name" ) , nif->getFolder() ); @@ -924,13 +924,13 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) } } else { // TODO: use the BSShaderTextureSet - iTexProp = nif->getBlock( l, "BSShaderPPLightingProperty" ); + iTexProp = nif->getBlockIndex( l, "BSShaderPPLightingProperty" ); if ( !iTexProp.isValid() ) - iTexProp = nif->getBlock( l, "BSLightingShaderProperty" ); + iTexProp = nif->getBlockIndex( l, "BSLightingShaderProperty" ); if ( iTexProp.isValid() ) { - QModelIndex iTexSource = nif->getBlock( nif->getLink( iTexProp, "Texture Set" ) ); + QModelIndex iTexSource = nif->getBlockIndex( nif->getLink( iTexProp, "Texture Set" ) ); if ( iTexSource.isValid() ) { // Assume that a FO3 mesh never has embedded textures... @@ -944,7 +944,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) } } } else { - iTexProp = nif->getBlock( l, "BSEffectShaderProperty" ); + iTexProp = nif->getBlockIndex( l, "BSEffectShaderProperty" ); if ( iTexProp.isValid() ) { QString texture = nif->get( iTexProp, "Source Texture" ); @@ -964,7 +964,7 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) bool UVWidget::setTexCoords() { - if ( nif->inherits( iShape, "NiTriBasedGeom" ) ) + if ( nif->blockInherits( iShape, "NiTriBasedGeom" ) ) texcoords = nif->getArray( iTexCoords ); QVector tris; @@ -980,7 +980,7 @@ bool UVWidget::setTexCoords() for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) { tris += triangulate( nif->getArray( iPoints.child( r, 0 ) ) ); } - } else if ( nif->inherits( iShape, "BSTriShape" ) ) { + } else if ( nif->blockInherits( iShape, "BSTriShape" ) ) { if ( !isDataOnSkin ) { tris = nif->getArray( iShape, "Triangles" ); } else { @@ -1017,9 +1017,9 @@ void UVWidget::updateNif() disconnect( nif, &NifModel::dataChanged, this, &UVWidget::nifDataChanged ); nif->setState( BaseModel::Processing ); - if ( nif->inherits( iShapeData, "NiTriBasedGeomData" ) ) { + if ( nif->blockInherits( iShapeData, "NiTriBasedGeomData" ) ) { nif->setArray( iTexCoords, texcoords ); - } else if ( nif->inherits( iShape, "BSTriShape" ) ) { + } else if ( nif->blockInherits( iShape, "BSTriShape" ) ) { int numVerts = 0; if ( !isDataOnSkin ) numVerts = nif->get( iShape, "Num Vertices" ); @@ -1045,7 +1045,7 @@ void UVWidget::nifDataChanged( const QModelIndex & idx ) return; } - if ( nif->getBlock( idx ) == iShapeData ) { + if ( nif->getBlockIndex( idx ) == iShapeData ) { //if ( ! setNifData( nif, iShape ) ) { close(); @@ -1523,7 +1523,7 @@ void UVWidget::getTexSlots() props << nif->getLink( iShape, "Shader Property" ); for ( const auto l : props ) { - QModelIndex iTexProp = nif->getBlock( l, "NiTexturingProperty" ); + QModelIndex iTexProp = nif->getBlockIndex( l, "NiTexturingProperty" ); if ( iTexProp.isValid() ) { for ( const QString& name : texnames ) { @@ -1554,13 +1554,13 @@ void UVWidget::selectTexSlot() props << nif->getLink( iShape, "Shader Property" ); for ( const auto l : props ) { - QModelIndex iTexProp = nif->getBlock( l, "NiTexturingProperty" ); + QModelIndex iTexProp = nif->getBlockIndex( l, "NiTexturingProperty" ); if ( iTexProp.isValid() ) { iTex = nif->getIndex( iTexProp, texnames[currentTexSlot] ); if ( iTex.isValid() ) { - QModelIndex iTexSource = nif->getBlock( nif->getLink( iTex, "Source" ) ); + QModelIndex iTexSource = nif->getBlockIndex( nif->getLink( iTex, "Source" ) ); if ( iTexSource.isValid() ) { currentCoordSet = nif->get( iTex, "UV Set" ); @@ -1637,7 +1637,7 @@ void UVWidget::duplicateCoordSet() nif->set( iShapeData, "Data Flags", numUvSets ); QModelIndex uvSets = nif->getIndex( iShapeData, "UV Sets" ); - nif->updateArray( uvSets ); + nif->updateArraySize( uvSets ); nif->setArray( uvSets.child( numUvSets, 0 ), nif->getArray( uvSets.child( currentCoordSet, 0 ) ) ); // switch to that coordinate set changeCoordSet( numUvSets ); diff --git a/src/ui/widgets/valueedit.cpp b/src/ui/widgets/valueedit.cpp index 2c8a260c6..b1de34d98 100644 --- a/src/ui/widgets/valueedit.cpp +++ b/src/ui/widgets/valueedit.cpp @@ -154,7 +154,7 @@ void ValueEdit::setValue( const NifValue & v ) QSpinBox * be = new QSpinBox( this ); be->setFrame( false ); be->setRange( 0, 0xff ); - be->setValue( v.toCount() ); + be->setValue( v.toCount( nullptr, nullptr ) ); edit = be; } break; @@ -164,7 +164,7 @@ void ValueEdit::setValue( const NifValue & v ) QSpinBox * we = new QSpinBox( this ); we->setFrame( false ); we->setRange( 0, 0xffff ); - we->setValue( v.toCount() ); + we->setValue( v.toCount( nullptr, nullptr ) ); edit = we; } break; @@ -173,7 +173,7 @@ void ValueEdit::setValue( const NifValue & v ) QSpinBox * we = new QSpinBox( this ); we->setFrame( false ); we->setRange( SHRT_MIN, SHRT_MAX ); - we->setValue( (short)v.toCount() ); + we->setValue( (short)v.toCount( nullptr, nullptr ) ); edit = we; } break; @@ -182,7 +182,7 @@ void ValueEdit::setValue( const NifValue & v ) QSpinBox * ie = new QSpinBox( this ); ie->setFrame( false ); ie->setRange( INT_MIN, INT_MAX ); - ie->setValue( (int)v.toCount() ); + ie->setValue( (int)v.toCount( nullptr, nullptr ) ); edit = ie; } break; @@ -191,7 +191,7 @@ void ValueEdit::setValue( const NifValue & v ) QSpinBox * ie = new QSpinBox( this ); ie->setFrame( false ); ie->setRange( -1, INT_MAX ); - ie->setValue( (int)v.toCount() ); + ie->setValue( (int)v.toCount( nullptr, nullptr ) ); edit = ie; } break; @@ -201,7 +201,7 @@ void ValueEdit::setValue( const NifValue & v ) UIntSpinBox * ie = new UIntSpinBox( this ); ie->setFrame( false ); ie->setRange( INT_MIN, INT_MAX ); - ie->setValue( v.toCount() ); + ie->setValue( v.toCount( nullptr, nullptr ) ); edit = ie; } break; @@ -209,7 +209,7 @@ void ValueEdit::setValue( const NifValue & v ) case NifValue::tUpLink: { QLineEdit * le = new QLineEdit( this ); - int tmp = v.toLink(); + int tmp = v.toLink( nullptr, nullptr ); if ( tmp > 0 ) { le->setText( QString::number( tmp ) ); @@ -227,7 +227,7 @@ void ValueEdit::setValue( const NifValue & v ) fe->setRange( -1e10, +1e10 ); fe->setDecimals( 4 ); */ - fe->setValue( v.toFloat() ); + fe->setValue( v.toFloat( nullptr, nullptr ) ); edit = fe; } break; @@ -259,70 +259,70 @@ void ValueEdit::setValue( const NifValue & v ) case NifValue::tByteColor4: { ColorEdit * ce = new ColorEdit( this ); - ce->setColor4( v.get() ); + ce->setColor4( v.get( nullptr, nullptr ) ); edit = ce; } break; case NifValue::tColor4: { ColorEdit * ce = new ColorEdit( this ); - ce->setColor4( v.get() ); + ce->setColor4( v.get( nullptr, nullptr ) ); edit = ce; } break; case NifValue::tColor3: { ColorEdit * ce = new ColorEdit( this ); - ce->setColor3( v.get() ); + ce->setColor3( v.get( nullptr, nullptr ) ); edit = ce; } break; case NifValue::tVector4: { VectorEdit * ve = new VectorEdit( this ); - ve->setVector4( v.get() ); + ve->setVector4( v.get( nullptr, nullptr ) ); edit = ve; } break; case NifValue::tByteVector3: { VectorEdit * ve = new VectorEdit( this ); - ve->setVector3( v.get() ); + ve->setVector3( v.get( nullptr, nullptr ) ); edit = ve; } break; case NifValue::tHalfVector3: { VectorEdit * ve = new VectorEdit( this ); - ve->setVector3( v.get() ); + ve->setVector3( v.get( nullptr, nullptr ) ); edit = ve; } break; case NifValue::tVector3: { VectorEdit * ve = new VectorEdit( this ); - ve->setVector3( v.get() ); + ve->setVector3( v.get( nullptr, nullptr ) ); edit = ve; } break; case NifValue::tHalfVector2: { VectorEdit * ve = new VectorEdit( this ); - ve->setVector2( v.get() ); + ve->setVector2( v.get( nullptr, nullptr ) ); edit = ve; } break; case NifValue::tVector2: { VectorEdit * ve = new VectorEdit( this ); - ve->setVector2( v.get() ); + ve->setVector2( v.get( nullptr, nullptr ) ); edit = ve; } break; case NifValue::tMatrix: { RotationEdit * re = new RotationEdit( this ); - re->setMatrix( v.get() ); + re->setMatrix( v.get( nullptr, nullptr ) ); edit = re; } break; @@ -330,14 +330,14 @@ void ValueEdit::setValue( const NifValue & v ) case NifValue::tQuatXYZW: { RotationEdit * re = new RotationEdit( this ); - re->setQuat( v.get() ); + re->setQuat( v.get( nullptr, nullptr ) ); edit = re; } break; case NifValue::tTriangle: { TriangleEdit * te = new TriangleEdit( this ); - te->setTriangle( v.get() ); + te->setTriangle( v.get( nullptr, nullptr ) ); edit = te; } break; @@ -346,7 +346,7 @@ void ValueEdit::setValue( const NifValue & v ) if ( /*???*/ false ) { QSpinBox * ie = new UIntSpinBox( this ); ie->setFrame( false ); - ie->setValue( v.toCount() ); + ie->setValue( v.toCount( nullptr, nullptr ) ); edit = ie; } else { QLineEdit * le = new QLineEdit( this ); @@ -360,7 +360,7 @@ void ValueEdit::setValue( const NifValue & v ) if ( /*???*/ false ) { QSpinBox * ie = new UIntSpinBox( this ); ie->setFrame( false ); - ie->setValue( v.toCount() ); + ie->setValue( v.toCount( nullptr, nullptr ) ); edit = ie; } else { QLineEdit * le = new QLineEdit( this ); @@ -393,7 +393,7 @@ NifValue ValueEdit::getValue() const case NifValue::tUInt: case NifValue::tULittle32: case NifValue::tStringIndex: - val.setCount( qobject_cast( edit )->value() ); + val.setCount( qobject_cast( edit )->value(), nullptr, nullptr ); break; case NifValue::tLink: case NifValue::tUpLink: @@ -402,74 +402,70 @@ NifValue ValueEdit::getValue() const bool ok = false; int tmp = str.toInt( &ok ); - if ( ok == false || tmp < 0 ) { - val.setLink( -1 ); - } else { - val.setLink( tmp ); - } + val.setLink( ( ok && tmp >= 0 ) ? tmp : -1, nullptr, nullptr ); } break; case NifValue::tFloat: case NifValue::tHfloat: - val.setFloat( qobject_cast( edit )->value() ); + val.setFloat( qobject_cast( edit )->value(), nullptr, nullptr ); break; case NifValue::tLineString: case NifValue::tShortString: case NifValue::tChar8String: - val.setFromString( qobject_cast( edit )->text() ); + val.setFromString( qobject_cast( edit )->text(), nullptr, nullptr ); break; case NifValue::tSizedString: case NifValue::tText: - val.setFromString( qobject_cast( edit )->toPlainText() ); + val.setFromString( qobject_cast( edit )->toPlainText(), nullptr, nullptr ); break; case NifValue::tByteColor4: { auto col = qobject_cast(edit)->getColor4(); - val.set( *static_cast(&col) ); + val.set( *static_cast(&col), nullptr, nullptr ); break; } case NifValue::tColor4: - val.set( qobject_cast( edit )->getColor4() ); + val.set( qobject_cast( edit )->getColor4(), nullptr, nullptr ); break; case NifValue::tColor3: - val.set( qobject_cast( edit )->getColor3() ); + val.set( qobject_cast( edit )->getColor3(), nullptr, nullptr ); break; case NifValue::tVector4: - val.set( qobject_cast( edit )->getVector4() ); + val.set( qobject_cast( edit )->getVector4(), nullptr, nullptr ); break; case NifValue::tByteVector3: { auto vec = qobject_cast(edit)->getVector3(); - val.set( *static_cast(&vec) ); + val.set( *static_cast(&vec), nullptr, nullptr ); break; } case NifValue::tHalfVector3: { auto vec = qobject_cast(edit)->getVector3(); - val.set( *static_cast(&vec) ); + val.set( *static_cast(&vec), nullptr, nullptr ); break; } case NifValue::tVector3: - val.set( qobject_cast( edit )->getVector3() ); + val.set( qobject_cast( edit )->getVector3(), nullptr, nullptr ); break; case NifValue::tHalfVector2: { auto vec = qobject_cast(edit)->getVector2(); - val.set( *static_cast(&vec) ); + val.set( *static_cast(&vec), nullptr, nullptr ); break; } case NifValue::tVector2: - val.set( qobject_cast( edit )->getVector2() ); + val.set( qobject_cast( edit )->getVector2(), nullptr, nullptr ); break; case NifValue::tMatrix: - val.set( qobject_cast( edit )->getMatrix() ); + val.set( qobject_cast( edit )->getMatrix(), nullptr, nullptr ); break; case NifValue::tQuat: case NifValue::tQuatXYZW: - val.set( qobject_cast( edit )->getQuat() ); + val.set( qobject_cast( edit )->getQuat(), nullptr, nullptr ); break; case NifValue::tTriangle: - val.set( qobject_cast( edit )->getTriangle() ); + val.set( qobject_cast( edit )->getTriangle(), nullptr, nullptr ); break; default: break; diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index 822cdb888..b637c5a16 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -448,8 +448,8 @@ void TestThread::run() if ( !headerOnly && loaded && model == &nif ) { for ( int b = 0; b < nif.getBlockCount(); b++ ) { - auto blk = nif.getBlock( b ); - bool current_match = !blockMatch.isEmpty() && nif.inherits(nif.getBlockName(blk), blockMatch); + auto blk = nif.getBlockIndex( b ); + bool current_match = !blockMatch.isEmpty() && nif.inherits(nif.itemName(blk), blockMatch); blk_match |= current_match; NifValue value; @@ -462,10 +462,8 @@ void TestThread::run() bool isInt = value.isCount() && !value.isFloat(); bool isStr = value.isString() || value.type() == NifValue::tStringIndex || value.isFloat(); - auto asInt = value.toCount(); - auto asStr = value.toString(); - if ( value.type() == NifValue::tStringIndex ) - asStr = nif.string(nameIdx); + auto asInt = value.toCount( nullptr, nullptr); + auto asStr = ( value.type() == NifValue::tStringIndex ) ? nif.resolveString(nameIdx) : value.toString(); bool current_match = false; @@ -590,14 +588,13 @@ QList TestThread::checkLinks( const NifModel * nif, const QModelInd for ( int r = 0; r < nif->rowCount( iParent ); r++ ) { QModelIndex idx = iParent.child( r, 0 ); - bool child; - if ( nif->isLink( idx, &child ) ) { + if ( nif->isLink( idx ) ) { qint32 l = nif->getLink( idx ); if ( l < 0 ) { // This is not really an error - // if ( ! child && ! kf ) + // if ( ! isChildLink && ! kf ) // messages.append( Message() << tr("unassigned parent link") << linkId( nif, idx ) ); } else if ( l >= nif->getBlockCount() ) { messages.append( TestMessage() << tr( "invalid link" ) << linkId( nif, idx ) ); @@ -605,9 +602,9 @@ QList TestThread::checkLinks( const NifModel * nif, const QModelInd QString tmplt = nif->itemTmplt( idx ); if ( !tmplt.isEmpty() ) { - QModelIndex iBlock = nif->getBlock( l ); + QModelIndex iBlock = nif->getBlockIndex( l ); - if ( !nif->inherits( iBlock, tmplt ) ) + if ( !nif->blockInherits( iBlock, tmplt ) ) messages.append( TestMessage() << tr( "link" ) << linkId( nif, idx ) << tr( "points to wrong block type" ) << nif->itemName( iBlock ) ); } } diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 00233f9c8..c4f1ee900 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -418,9 +418,9 @@ class NifXmlHandler final : public QXmlDefaultHandler quint32 enumVal = NifValue::enumOptionValue( type, defval, &ok ); if ( ok ) { - data.value.setCount( enumVal ); + data.value.setCount( enumVal, nullptr, nullptr ); } else { - data.value.setFromString( defval ); + data.value.setFromString( defval, nullptr, nullptr ); } } From 5abf173ddf89806950ab15b85707b5552426de46 Mon Sep 17 00:00:00 2001 From: gavrant Date: Sun, 30 Jul 2023 11:07:29 +0300 Subject: [PATCH 065/118] Python script for generating standard overload declarations and definitions for NifItem methods in BaseModel/NifModel/KfmModel classes. See get in BaseModel as an example. --- src/model/model_func_gen.py | 324 ++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/model/model_func_gen.py diff --git a/src/model/model_func_gen.py b/src/model/model_func_gen.py new file mode 100644 index 000000000..cd31ad795 --- /dev/null +++ b/src/model/model_func_gen.py @@ -0,0 +1,324 @@ +# Usage: +# Uncomment/write the generate block for a specific function at the bottom of the script, then in command line console (on Windows): +# python model_func_gen.py > output.txt + +FLAG_CONST = 1 +FLAG_TEMPLATE = 2 +FLAG_ARRAY_ARGS = 4 +FLAG_ARRAY_COMMENTS = 8 +FLAG_GETITEM_REPORT = 16 + +class ExtraArg: + def __init__(self, _type, _name, func_code = None, default_val = None): + self.type = _type + self.name = _name + self.func_code = func_code if func_code else _name + self.default_val = '' if default_val is None else f' = {default_val}' + +class ExtraTemplateType: + def __init__(self, _type, _func_call): + self.type = _type + self.func_call = _func_call + + def replace_type(self, type_str): + twords = [] + cur_word = '' + for ch in type_str: + if ch.isalnum(): + cur_word += ch + else: + if cur_word: + twords.append(cur_word) + cur_word = '' + twords.append(ch) + if cur_word: + twords.append(cur_word) + + result = '' + for w in twords: + result += self.type if w == 'T' else w + return result + +def generate(class_name, func_name, return_type, comment, flags = 0, func_call = None, extra_args = None, extra_template_types = None): + + constMod = ' const' if (flags & FLAG_CONST) else '' + constNifItemPtrType = 'const NifItem *' + returnOp = '' if return_type == 'void' else 'return ' + funcCallName = func_call if func_call else func_name + templatePrefix = 'template ' if (flags & FLAG_TEMPLATE) else '' + + extraArgDeclare1 = '' + extraArgDeclare2 = '' + extraArgList = '' + if extra_args: + if type(extra_args) is ExtraArg: + extra_args = [extra_args, ] + for xarg in extra_args: + extraArgDeclare1 += f', {xarg.type} {xarg.name}{xarg.default_val}' + extraArgDeclare2 += f', {xarg.type} {xarg.name}' + extraArgList += f', {xarg.func_code}' + else: + extra_args = [] + + if (flags & FLAG_TEMPLATE) and extra_template_types: + if type(extra_template_types) is ExtraTemplateType: + extra_template_types = [extra_template_types, ] + for xt in extra_template_types: + xt.return_type = xt.replace_type(return_type) + xt.arg_declare = '' + for xarg in extra_args: + local_xarg_type = xt.replace_type(xarg.type) + xt.arg_declare += f', {local_xarg_type} {xarg.name}' + else: + extra_template_types = [] + + + openBracket = '{' + closeBracket = '}' + + if not comment.endswith('.'): + comment += '.' + + if (flags & FLAG_ARRAY_COMMENTS): + comment_child = comment.replace(' an array', ' a child array') + comment_index = comment.replace(' an array', ' a model index array') + else: + comment_child = comment.replace(' an item', ' a child item') + comment_index = comment.replace(' an item', ' a model index') + + if (flags & FLAG_ARRAY_ARGS): + itemArgName = 'arrayRootItem' + indexArgName = 'iArray' + itemParentArgName = 'arrayParent' + indexParentArgName = itemParentArgName + itemIndexArgName = 'arrayIndex' + itemNameArgName = 'arrayName' + else: + itemArgName = 'item' + indexArgName = 'index' + itemParentArgName = 'itemParent' + indexParentArgName = itemParentArgName + itemIndexArgName = 'itemIndex' + itemNameArgName = 'itemName' + + if (flags & FLAG_GETITEM_REPORT): + getItemReportArg = ', true' + else: + getItemReportArg = '' + + indent = '' + func_bodies = [] + + def func_declare(base_args, get_item_code, func_comment): + if func_comment: + comment_prefix = '!' + for comment_ln in func_comment.split('\n'): + print(f'{indent}//{comment_prefix} {comment_ln}') + comment_prefix = '' + print(f'{indent}{templatePrefix}{return_type} {func_name}( {base_args}{extraArgDeclare1} ){constMod};') + if get_item_code: + func_bodies.append((base_args, get_item_code)) + + indent = '\t' + localPtrType = constNifItemPtrType if (flags & FLAG_CONST) else 'NifItem *' + func_declare(f'{localPtrType} {itemArgName}', f'{itemArgName}' if funcCallName != func_name else None, comment) + # print(f'{indent}') + func_declare(f'{constNifItemPtrType} {itemParentArgName}, int {itemIndexArgName}', f'getItem({itemParentArgName}, {itemIndexArgName}{getItemReportArg})', comment_child) + func_declare(f'{constNifItemPtrType} {itemParentArgName}, const QString & {itemNameArgName}', f'getItem({itemParentArgName}, {itemNameArgName}{getItemReportArg})', comment_child) + func_declare(f'{constNifItemPtrType} {itemParentArgName}, const QLatin1String & {itemNameArgName}', f'getItem({itemParentArgName}, {itemNameArgName}{getItemReportArg})', comment_child) + func_declare(f'{constNifItemPtrType} {itemParentArgName}, const char * {itemNameArgName}', f'getItem({itemParentArgName}, QLatin1String({itemNameArgName}){getItemReportArg})', comment_child) + # print(f'{indent}') + func_declare(f'const QModelIndex & {indexArgName}', f'getItem({indexArgName})', comment_index) + func_declare(f'const QModelIndex & {indexParentArgName}, int {itemIndexArgName}', f'getItem({indexParentArgName}, {itemIndexArgName}{getItemReportArg})', comment_child) + func_declare(f'const QModelIndex & {indexParentArgName}, const QString & {itemNameArgName}', f'getItem({indexParentArgName}, {itemNameArgName}{getItemReportArg})', comment_child) + func_declare(f'const QModelIndex & {indexParentArgName}, const QLatin1String & {itemNameArgName}', f'getItem({indexParentArgName}, {itemNameArgName}{getItemReportArg})', comment_child) + func_declare(f'const QModelIndex & {indexParentArgName}, const char * {itemNameArgName}', f'getItem({indexParentArgName}, QLatin1String({itemNameArgName}){getItemReportArg})', comment_child) + + if (flags & FLAG_TEMPLATE) and funcCallName == func_name: + funcCallName += '' + + indent = '' + print(f'{indent}') + for base_args, get_item_code in func_bodies: + # print(f'{indent}') + print(f'{indent}{templatePrefix}inline {return_type} {class_name}::{func_name}( {base_args}{extraArgDeclare2} ){constMod}') + print(f'{indent}{openBracket}') + print(f'{indent}\t{returnOp}{funcCallName}( {get_item_code}{extraArgList} );') + print(f'{indent}{closeBracket}') + for xt in extra_template_types: + print(f'{indent}template <> inline {xt.return_type} {class_name}::{func_name}( {base_args}{xt.arg_declare} ){constMod}') + print(f'{indent}{openBracket}') + print(f'{indent}\t{returnOp}{xt.func_call}( {get_item_code}{extraArgList} );') + print(f'{indent}{closeBracket}') + +""" +generate( + class_name = 'BaseModel', + func_name = 'getIndex', + return_type = 'QModelIndex', + comment = 'Get the model index of an item', + flags = FLAG_CONST, + func_call = 'itemToIndex', + extra_args = ExtraArg('int', 'column', default_val = '0') +) +""" + +""" +generate( + class_name = 'BaseModel', + func_name = 'updateArraySize', + return_type = 'bool', + comment = 'Update the size of an array from its conditions (append missing or remove excess items)', + flags = FLAG_ARRAY_ARGS | FLAG_ARRAY_COMMENTS, + func_call = 'updateArraySizeImpl' +) +""" + +""" +generate( + class_name = 'BaseModel', + func_name = 'get', + return_type = 'T', + comment = 'Get the value of an item', + flags = FLAG_CONST | FLAG_TEMPLATE, + func_call = 'NifItem::get' +) +""" + +""" +generate( + class_name = 'BaseModel', + func_name = 'set', + return_type = 'bool', + comment = 'Set the value of an item', + flags = FLAG_TEMPLATE | FLAG_GETITEM_REPORT, + extra_args = ExtraArg('const T &', 'val') +) +""" + +""" +generate( + class_name = 'BaseModel', + func_name = 'getArray', + return_type = 'QVector', + comment = 'Get an array as a QVector', + flags = FLAG_CONST | FLAG_TEMPLATE | FLAG_ARRAY_ARGS | FLAG_ARRAY_COMMENTS, + func_call = 'NifItem::getArray' +) +""" + +""" +generate( + class_name = 'BaseModel', + func_name = 'setArray', + return_type = 'void', + comment = 'Write a QVector to an array', + flags = FLAG_TEMPLATE | FLAG_ARRAY_ARGS | FLAG_ARRAY_COMMENTS | FLAG_GETITEM_REPORT, + extra_args = ExtraArg('const QVector &', 'array') +) +""" + +""" +generate( + class_name = 'BaseModel', + func_name = 'fillArray', + return_type = 'void', + comment = 'Fill an array with a value', + flags = FLAG_TEMPLATE | FLAG_ARRAY_ARGS | FLAG_ARRAY_COMMENTS | FLAG_GETITEM_REPORT, + extra_args = ExtraArg('const T &', 'val') +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'get', + return_type = 'T', + comment = 'Get the value of an item', + flags = FLAG_CONST | FLAG_TEMPLATE, +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'set', + return_type = 'bool', + comment = 'Set the value of an item', + flags = FLAG_TEMPLATE | FLAG_GETITEM_REPORT, + extra_args = ExtraArg('const T &', 'val'), +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'resolveString', + return_type = 'QString', + comment = 'Get the string value of an item, expanding string indices or subitems if necessary', + flags = FLAG_CONST +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'assignString', + return_type = 'bool', + comment = 'Set the string value of an item, updating string indices or subitems if necessary', + flags = FLAG_GETITEM_REPORT, + extra_args = [ExtraArg('const QString &', 'string'), ExtraArg('bool', 'replace', default_val = 'false')] +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'getLink', + return_type = 'qint32', + comment = 'Return the link value (block number) of an item if it\'s a valid link, otherwise -1', + flags = FLAG_CONST +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'getLinkItem', + return_type = 'const NifItem *', + comment = 'Return the link block item stored in an item if it\'s a valid link', + flags = FLAG_CONST +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'setLink', + return_type = 'bool', + comment = 'Set the link value (block number) of an item if it\'s a valid link', + flags = 0, + extra_args = ExtraArg('qint32', 'link') +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'getLinkArray', + return_type = 'QVector', + comment = 'Return a QVector of link values (block numbers) of an item if it\'s a valid link array', + flags = FLAG_CONST | FLAG_ARRAY_ARGS +) +""" + +""" +generate( + class_name = 'NifModel', + func_name = 'setLinkArray', + return_type = 'bool', + comment = 'Write a QVector of link values (block numbers) to an item if it\'s a valid link array.\nThe size of QVector must match the current size of the array.', + flags = FLAG_ARRAY_ARGS, + extra_args = ExtraArg('const QVector &', 'links'), +) +""" \ No newline at end of file From 698dd0de0b3c421111c50fcb458e744da6f10ccd Mon Sep 17 00:00:00 2001 From: gavrant Date: Wed, 2 Aug 2023 15:03:49 +0300 Subject: [PATCH 066/118] Fixed wrong type of Backward and Forward value getters in glcontroller::interpolate. This fixes animations of some effects in NifSkope. --- src/gl/glcontroller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 48558d2f0..713c1370e 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -395,9 +395,9 @@ template bool interpolate( T & value, const QModelIndex & array, fl */ // 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; From 6e73dc7a2bcd98193a3c90ca51b8e6524776df8f Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:50:30 -0400 Subject: [PATCH 067/118] Restore save/import for FO76 and later --- src/gl/glscene.cpp | 2 -- src/lib/importex/importex.cpp | 5 ++- src/lib/importex/obj.cpp | 4 ++- src/model/nifmodel.cpp | 59 +++++++++++++++++++++++++++-------- src/model/nifmodel.h | 5 +++ src/nifskope_ui.cpp | 7 ++--- 6 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index fa8c64864..6628c4d23 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -209,8 +209,6 @@ void Scene::make( NifModel * nif, bool flushTextures ) return; game = Game::GameManager::get_game(nif->getVersionNumber(), nif->getUserVersion(), nif->getBSVersion()); - if ( game == Game::FALLOUT_76 ) - emit disableSave(); update( nif, QModelIndex() ); diff --git a/src/lib/importex/importex.cpp b/src/lib/importex/importex.cpp index e95c2edf2..47f242617 100644 --- a/src/lib/importex/importex.cpp +++ b/src/lib/importex/importex.cpp @@ -85,9 +85,8 @@ void NifSkope::sltImportExport( QAction * a ) mImport->setDisabled( false ); mExport->setDisabled( false ); - if ( nif->getBSVersion() >= 100 ) - mImport->actions().at(0)->setDisabled( true ); - else if ( nif->getBSVersion() == 0 ) + // Import OBJ as Collision disabled for non-Bethesda + if ( nif->getBSVersion() == 0 ) mImport->actions().at(1)->setDisabled( true ); if ( a->text() == tr( "Export .OBJ" ) ) diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index a83205947..e851e2ae6 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -1089,6 +1089,8 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) // For some reason need to update all the fixed arrays... nif->updateArraySize( iStripsShape, "Unused" ); + nif->set( iStripsShape, "Num Strips Data", 0 ); + nif->updateArraySize( iStripsShape, "Strips Data" ); QPersistentModelIndex iBody = nif->insertNiBlock( "bhkRigidBody" ); nif->setLink( iBody, "Shape", nif->getBlockNumber( iStripsShape ) ); @@ -1101,7 +1103,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) QPersistentModelIndex iObject = nif->insertNiBlock( "bhkCollisionObject" ); QPersistentModelIndex iParent = (iShape.isValid()) ? iShape : iNode; - nif->setLink( iObject, "Parent", nif->getBlockNumber( iParent ) ); + nif->setLink( iObject, "Target", nif->getBlockNumber( iParent ) ); nif->setLink( iObject, "Body", nif->getBlockNumber( iBody ) ); nif->setLink( iParent, "Collision Object", nif->getBlockNumber( iObject ) ); diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 1114b9173..06e06dd96 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -2043,6 +2043,9 @@ int NifModel::blockSize( const NifItem * item, NifSStream & stream ) const if ( !item ) return 0; + auto testSkip = testSkipIO(item); + QString name; + int size = 0; for ( auto child : item->childIter() ) { if ( child->isAbstract() ) { @@ -2069,6 +2072,16 @@ int NifModel::blockSize( const NifItem * item, NifSStream & stream ) const size += stream.size( child->value() ); } } + + // Get material path if current item is the Name field of a shader property + if ( testSkip && child->hasName("Name") ) { + auto iStr = child->get(); + if ( iStr >= 0 ) + name = get(getItem(getHeaderItem(), "Strings"), iStr); + } + // Short circuit I/O after Controller if shader property Name is a material path + if ( testSkip && child->hasName("Controller") && !name.isEmpty() ) + break; } return size; @@ -2079,14 +2092,7 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) if ( !parent ) return false; - bool stopRead = false; - // Be advised, getBSVersion returns 0 if it's the file's header that is being loaded. - // Though for shader properties loadItem happens after the header is fully processed, so the check below should work w/o issues. - if ( getBSVersion() == 155 && parent->parent() == root ) { - if ( parent->hasName("BSLightingShaderProperty") || parent->hasName("BSEffectShaderProperty") ) - stopRead = true; - } - + bool testSkip = testSkipIO(parent); QString name; for ( auto child : parent->childIter() ) { @@ -2112,13 +2118,14 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) } } - if ( stopRead && child->hasName("Name") ) { + // Get material path if current item is the Name field of a shader property + if ( testSkip && child->hasName("Name") ) { auto iStr = child->get(); if ( iStr >= 0 ) - name = get( getItem( getHeaderItem(), "Strings" ), iStr ); + name = get(getItem(getHeaderItem(), "Strings"), iStr); } - - if ( stopRead && child->hasName("Controller") && !name.isEmpty() ) + // Short circuit I/O after Controller if shader property Name is a material path + if ( testSkip && child->hasName("Controller") && !name.isEmpty() ) break; } @@ -2152,6 +2159,9 @@ bool NifModel::saveItem( const NifItem * parent, NifOStream & stream ) const if ( !parent ) return false; + auto testSkip = testSkipIO(parent); + QString name; + for ( auto child : parent->childIter() ) { if ( child->isAbstract() ) { qDebug() << "Not saving abstract item " << child->name(); @@ -2175,6 +2185,16 @@ bool NifModel::saveItem( const NifItem * parent, NifOStream & stream ) const return false; } } + + // Get material path if current item is the Name field of a shader property + if ( testSkip && child->hasName("Name") ) { + auto iStr = child->get(); + if ( iStr >= 0 ) + name = get(getItem(getHeaderItem(), "Strings"), iStr); + } + // Short circuit I/O after Controller if shader property Name is a material path + if ( testSkip && child->hasName("Controller") && !name.isEmpty() ) + break; } return true; @@ -2589,7 +2609,8 @@ bool NifModel::assignString( NifItem * item, const QString & string, bool replac itemIndex = item; iOldStrIndex = -1; break; - } // fall through + } + [[fallthrough]]; default: return BaseModel::set( item, string ); } @@ -2766,6 +2787,18 @@ void NifModel::updateModel( UpdateType value ) emit linksChanged(); } +bool NifModel::testSkipIO( const NifItem * item ) const +{ + bool testSkip = false; + // Be advised, getBSVersion returns 0 if it's the file's header that is being loaded. + // Though for shader properties loadItem happens after the header is fully processed, so the check below should work w/o issues. + if ( getBSVersion() >= 151 && item->parent() == root ) { + if (item->hasName("BSLightingShaderProperty") || item->hasName("BSEffectShaderProperty") ) + testSkip = true; + } + return testSkip; +} + void NifModel::cacheBSVersion( const NifItem * headerItem ) { bsVersion = get( headerItem, "BS Header\\BS Version" ); diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 4b620fbfb..dfa89b9b1 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -160,6 +160,11 @@ class NifModel final : public BaseModel //! Set delayed updating of model links bool holdUpdates( bool value ); + /*! Item may stop I/O depending on certain children values + * @return Whether or not to test for early I/O skip + */ + bool testSkipIO( const NifItem * parent ) const; + QList getRootLinks() const; QList getChildLinks( int block ) const; QList getParentLinks( int block ) const; diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index f778bd543..d4b651d18 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -780,12 +780,11 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) if ( nif && nif->getVersionNumber() >= 0x14050000 ) { mExport->setDisabled( true ); mImport->setDisabled( true ); - } else { + } else if ( nif ) { mExport->setDisabled( false ); mImport->setDisabled( false ); - if ( nif->getBSVersion() >= 100 ) - mImport->actions().at(0)->setDisabled(true); - else if ( nif->getBSVersion() == 0 ) + // Import OBJ as Collision disabled for non-Bethesda + if ( nif->getBSVersion() == 0 ) mImport->actions().at(1)->setDisabled(true); } From 2cd32612b9a32300b3b862a2002bb9436cb6bd42 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:59:23 -0400 Subject: [PATCH 068/118] Improve bsver checks --- src/gl/bsshape.cpp | 4 ++-- src/gl/glproperty.cpp | 8 ++++---- src/gl/renderer.cpp | 8 ++++---- src/model/nifmodel.cpp | 3 ++- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 7a44f273c..9ba387a78 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -313,14 +313,14 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) } if ( !Node::SELECTING ) { - if ( nif->getBSVersion() == 155 ) + if ( nif->getBSVersion() >= 151 ) glEnable( GL_FRAMEBUFFER_SRGB ); else glDisable( GL_FRAMEBUFFER_SRGB ); shader = scene->renderer->setupProgram( this, shader ); } else { - if ( nif->getBSVersion() == 155 ) + if ( nif->getBSVersion() >= 151 ) glDisable( GL_FRAMEBUFFER_SRGB ); } diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 305686232..cbded8401 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -1128,7 +1128,7 @@ int BSShaderLightingProperty::getId( const QString & id ) void BSShaderLightingProperty::setFlags1( const NifModel * nif ) { - if ( nif->getBSVersion() == 155 ) { + if ( nif->getBSVersion() >= 151 ) { auto sf1 = nif->getArray( iBlock, "SF1" ); auto sf2 = nif->getArray( iBlock, "SF2" ); sf1.append( sf2 ); @@ -1145,7 +1145,7 @@ void BSShaderLightingProperty::setFlags1( const NifModel * nif ) void BSShaderLightingProperty::setFlags2( const NifModel * nif ) { - if ( nif->getBSVersion() == 155 ) { + if ( nif->getBSVersion() >= 151 ) { auto sf1 = nif->getArray( iBlock, "SF1" ); auto sf2 = nif->getArray( iBlock, "SF2" ); sf1.append( sf2 ); @@ -1232,7 +1232,7 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif ) setFlags1( nif ); setFlags2( nif ); - if ( nif->getBSVersion() == 155 ) { + if ( nif->getBSVersion() >= 151 ) { shaderType = ShaderFlags::ShaderType::ST_EnvironmentMap; hasVertexAlpha = true; hasVertexColors = true; @@ -1271,7 +1271,7 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif ) paletteScale = m->fGrayscaleToPaletteScale; hasSpecularMap = m->bSpecularEnabled && (!m->textureList[2].isEmpty() - || (nif->getBSVersion() == 155 && !m->textureList[7].isEmpty())); + || (nif->getBSVersion() >= 151 && !m->textureList[7].isEmpty())); hasGlowMap = m->bGlowmap; hasEmittance = m->bEmitEnabled; hasBacklight = m->bBackLighting; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index eaf2e684c..cd7f088dc 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -633,7 +633,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & else if ( esp ) mat = esp->getMaterial(); - QString default_n = (nifVersion == 155) ? ::default_ns : ::default_n; + QString default_n = (nifVersion >= 151) ? ::default_ns : ::default_n; // texturing @@ -767,7 +767,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // Glow params - if ( scene->hasOption(Scene::DoGlow) && scene->hasOption(Scene::DoLighting) && (lsp->hasEmittance || nifVersion == 155) ) + if ( scene->hasOption(Scene::DoGlow) && scene->hasOption(Scene::DoLighting) && (lsp->hasEmittance || nifVersion >= 151) ) prog->uni1f( GLOW_MULT, lsp->emissiveMult ); else prog->uni1f( GLOW_MULT, 0 ); @@ -838,7 +838,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & // Always bind mask regardless of shader settings prog->uniSampler( bsprop, SAMP_ENV_MASK, 5, texunit, white, clamp ); - if ( nifVersion == 155 ) { + if ( nifVersion >= 151 ) { prog->uniSampler( bsprop, SAMP_REFLECTIVITY, 8, texunit, black, clamp ); prog->uniSampler( bsprop, SAMP_LIGHTING, 9, texunit, lighting, clamp ); } @@ -921,7 +921,7 @@ bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & fn->glUniform1i( uniCubeMap, texunit++ ); } prog->uniSampler( bsprop, SAMP_SPECULAR, 4, texunit, white, clamp ); - if ( nifVersion == 155 ) { + if ( nifVersion >= 151 ) { prog->uniSampler( bsprop, SAMP_REFLECTIVITY, 6, texunit, black, clamp ); prog->uniSampler( bsprop, SAMP_LIGHTING, 7, texunit, lighting, clamp ); } diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 06e06dd96..e8a59e31a 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -568,7 +568,8 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at ) branch->prepareInsert( block->types.count() ); - if ( getBSVersion() == 155 && identifier.startsWith( "BSLighting" ) ) { + if ( getBSVersion() >= 151 && identifier.startsWith( "BSLighting" ) ) { + // TODO: This appears to be incomplete for ( const NifData& data : block->types ) { insertType( branch, data ); } From 7d56da53bfca9fad333931aa614105a6e625c080 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Wed, 30 Aug 2023 22:56:37 -0400 Subject: [PATCH 069/118] Fix Skyrim shading issues Manual merge for niftools/nifskope#230 --- res/shaders/sk_default.frag | 21 ++++++++--------- res/shaders/sk_default.vert | 45 +++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/res/shaders/sk_default.frag b/res/shaders/sk_default.frag index 0f69d94bc..0ee994f95 100644 --- a/res/shaders/sk_default.frag +++ b/res/shaders/sk_default.frag @@ -1,4 +1,4 @@ -#version 120 +#version 130 uniform sampler2D BaseMap; uniform sampler2D NormalMap; @@ -40,16 +40,14 @@ uniform float envReflection; uniform mat4 worldMatrix; -varying vec3 LightDir; -varying vec3 ViewDir; +in vec3 LightDir; +in vec3 ViewDir; -varying vec4 A; -varying vec4 C; -varying vec4 D; +in vec4 A; +in vec4 C; +in vec4 D; -varying vec3 N; -varying vec3 t; -varying vec3 b; +in mat3 tbnMatrix; vec3 tonemap(vec3 x) @@ -84,7 +82,7 @@ void main( void ) vec4 normalMap = texture2D( NormalMap, offset ); vec4 glowMap = texture2D( GlowMap, offset ); - vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + vec3 normal = normalize(tbnMatrix * (normalMap.rgb * 2.0 - 1.0)); vec3 L = normalize(LightDir); vec3 R = reflect(-L, normal); @@ -96,8 +94,7 @@ void main( void ) float NdotNegL = max( dot(normal, -L), 0.0 ); vec3 reflected = reflect( -E, normal ); - vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; - vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); + vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflected, 0.0 )) ); vec4 color; diff --git a/res/shaders/sk_default.vert b/res/shaders/sk_default.vert index 54c4fef12..423cd24c2 100644 --- a/res/shaders/sk_default.vert +++ b/res/shaders/sk_default.vert @@ -1,16 +1,14 @@ -#version 120 +#version 130 -varying vec3 LightDir; -varying vec3 ViewDir; +out vec3 LightDir; +out vec3 ViewDir; -varying vec3 N; -varying vec3 t; -varying vec3 b; -varying vec3 v; +out mat3 tbnMatrix; +out vec3 v; -varying vec4 A; -varying vec4 C; -varying vec4 D; +out vec4 A; +out vec4 C; +out vec4 D; uniform bool isGPUSkinned; uniform mat4 boneTransforms[100]; @@ -20,10 +18,13 @@ void main( void ) gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord[0] = gl_MultiTexCoord0; + vec3 n; + vec3 t; + vec3 b; if ( !isGPUSkinned ) { - N = normalize(gl_NormalMatrix * gl_Normal); - t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + n = gl_NormalMatrix * gl_Normal; + t = gl_NormalMatrix * gl_MultiTexCoord1.xyz; + b = gl_NormalMatrix * gl_MultiTexCoord2.xyz; v = vec3(gl_ModelViewMatrix * gl_Vertex); } else { mat4 bt = boneTransforms[int(gl_MultiTexCoord3[0])] * gl_MultiTexCoord4[0]; @@ -37,18 +38,18 @@ void main( void ) vec3 bit = vec3(bt * vec4(gl_MultiTexCoord2.xyz, 0.0)); gl_Position = gl_ModelViewProjectionMatrix * V; - N = normalize(gl_NormalMatrix * normal); - t = normalize(gl_NormalMatrix * tan); - b = normalize(gl_NormalMatrix * bit); + n = gl_NormalMatrix * normal; + t = gl_NormalMatrix * tan; + b = gl_NormalMatrix * bit; v = vec3(gl_ModelViewMatrix * V); } + + tbnMatrix = mat3(b.x, b.y, b.z, + t.x, t.y, t.z, + n.x, n.y, n.z); - mat3 tbnMatrix = mat3(b.x, t.x, N.x, - b.y, t.y, N.y, - b.z, t.z, N.z); - - ViewDir = tbnMatrix * -v.xyz; - LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + ViewDir = -v.xyz; + LightDir = gl_LightSource[0].position.xyz; A = gl_LightSource[0].ambient; C = gl_Color; From 6411cfce85c28b1a910a4de68190bcc6aada9b2c Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 4 Sep 2023 04:03:22 -0400 Subject: [PATCH 070/118] Fix archives for FO3/NV --- src/gamemanager.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gamemanager.cpp b/src/gamemanager.cpp index 9095ac335..ba6a42796 100644 --- a/src/gamemanager.cpp +++ b/src/gamemanager.cpp @@ -256,8 +256,15 @@ QList GameManager::opened_archives( const GameMode game ) return {}; QList archives; - for ( const auto an : get()->handles.value(game) ) { - archives.append(an->getArchive()); + if ( game == FALLOUT_3NV ) { + for ( const auto& an : get()->handles.value(FALLOUT_3) ) + archives.append(an->getArchive()); + for ( const auto& an : get()->handles.value(FALLOUT_NV) ) + archives.append(an->getArchive()); + } + else { + for ( const auto& an : get()->handles.value(game) ) + archives.append(an->getArchive()); } return archives; } From 9b970b3d37424eda1bb419b69f1a2eab7ae0189f Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 4 Sep 2023 04:05:58 -0400 Subject: [PATCH 071/118] Fix compilation error on Linux/Mac --- src/ui/widgets/lightingwidget.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/widgets/lightingwidget.h b/src/ui/widgets/lightingwidget.h index 0524bcfbf..b7dadce04 100644 --- a/src/ui/widgets/lightingwidget.h +++ b/src/ui/widgets/lightingwidget.h @@ -2,11 +2,11 @@ #define LIGHTINGWIDGET_H #include +#include #include class GLView; -class QAction; namespace Ui { class LightingWidget; @@ -14,11 +14,11 @@ class LightingWidget; class LightingWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - LightingWidget( GLView * ogl, QWidget * parent = nullptr); - ~LightingWidget(); + LightingWidget( GLView * ogl, QWidget * parent = nullptr); + ~LightingWidget(); void setDefaults(); void setActions( QVector actions ); From 1365ca1d6d68a6b3d18efe1c5c43a7c605313939 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 4 Sep 2023 05:42:25 -0400 Subject: [PATCH 072/118] nifxml updates --- src/data/niftypes.h | 19 ++++++++++++++ src/data/nifvalue.cpp | 12 +++++++++ src/data/nifvalue.h | 13 +++++++++- src/gl/gltools.cpp | 25 +++++++++--------- src/io/nifstream.cpp | 49 ++++++++++++++++++++++++++++++++++++ src/model/basemodel.cpp | 3 +++ src/model/nifmodel.cpp | 3 +++ src/nifskope.cpp | 3 ++- src/ui/widgets/nifview.cpp | 1 + src/ui/widgets/valueedit.cpp | 4 ++- src/ui/widgets/xmlcheck.cpp | 2 +- src/xml/nifxml.cpp | 24 +++++++++++------- 12 files changed, 133 insertions(+), 25 deletions(-) diff --git a/src/data/niftypes.h b/src/data/niftypes.h index 3b1d8d005..f992176a7 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -466,6 +466,24 @@ class HalfVector3 : public Vector3 } }; +class UshortVector3 : public Vector3 +{ +public: + //! Default constructor + UshortVector3() + { + xyz[0] = xyz[1] = xyz[2] = 0.0; + } + //! Constructor + UshortVector3( float x, float y, float z ) : Vector3( x, y, z ) + { + } + + UshortVector3( Vector3 v ) : Vector3( v ) + { + } +}; + class ByteVector3 : public Vector3 { public: @@ -1899,6 +1917,7 @@ inline QDataStream & operator>>( QDataStream & ds, BSVertexDesc & d ) Q_DECLARE_TYPEINFO( Vector2, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Vector3, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( HalfVector3, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( UshortVector3, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( ByteVector3, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Vector4, Q_MOVABLE_TYPE ); Q_DECLARE_TYPEINFO( Color3, Q_MOVABLE_TYPE ); diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index fd188a466..188881c6e 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -74,6 +74,8 @@ void NifValue::initialize() typeMap.insert( "bool", NifValue::tBool ); typeMap.insert( "byte", NifValue::tByte ); + typeMap.insert( "sbyte", NifValue::tByte ); + typeMap.insert( "normbyte", NifValue::tNormbyte ); typeMap.insert( "char", NifValue::tByte ); typeMap.insert( "word", NifValue::tWord ); typeMap.insert( "short", NifValue::tShort ); @@ -119,6 +121,7 @@ void NifValue::initialize() typeMap.insert( "blob", NifValue::tBlob ); typeMap.insert( "hfloat", NifValue::tHfloat ); typeMap.insert( "HalfVector3", NifValue::tHalfVector3 ); + typeMap.insert( "UshortVector3", NifValue::tUshortVector3 ); typeMap.insert( "ByteVector3", NifValue::tByteVector3 ); typeMap.insert( "HalfVector2", NifValue::tHalfVector2 ); typeMap.insert( "HalfTexCoord", NifValue::tHalfVector2 ); @@ -361,6 +364,7 @@ void NifValue::clear() break; case tVector3: case tHalfVector3: + case tUshortVector3: case tByteVector3: delete static_cast( val.data ); break; @@ -433,6 +437,7 @@ void NifValue::changeType( Type t ) return; case tVector3: case tHalfVector3: + case tUshortVector3: case tByteVector3: val.data = new Vector3(); break; @@ -503,6 +508,7 @@ void NifValue::operator=( const NifValue & other ) switch ( typ ) { case tVector3: case tHalfVector3: + case tUshortVector3: case tByteVector3: *static_cast( val.data ) = *static_cast( other.val.data ); return; @@ -591,6 +597,7 @@ bool NifValue::operator==( const NifValue & other ) const case tUInt64: return val.u64 == other.val.u64; + case tNormbyte: case tFloat: case tHfloat: return val.f32 == other.val.f32; @@ -649,6 +656,7 @@ bool NifValue::operator==( const NifValue & other ) const case tVector3: case tHalfVector3: + case tUshortVector3: case tByteVector3: { Vector3 * vec1 = static_cast(val.data); @@ -831,6 +839,7 @@ bool NifValue::setFromString( const QString & s, const BaseModel * model, const val.f32 = s.toDouble( &ok ); break; case tHfloat: + case tNormbyte: val.u64 = 0; val.f32 = s.toDouble( &ok ); break; @@ -913,6 +922,7 @@ QString NifValue::toString() const return QString("0.0"); return NumOrMinMax( val.f32, 'G', 6 ); case tHfloat: + case tNormbyte: return QString::number( val.f32, 'f', 4 ); case tString: case tSizedString: @@ -966,6 +976,7 @@ QString NifValue::toString() const } case tVector3: case tHalfVector3: + case tUshortVector3: case tByteVector3: { Vector3 * v = static_cast( val.data ); @@ -1143,6 +1154,7 @@ QString NifValue::getTypeDebugStr( NifValue::Type t ) case tBlob: typeStr = "Blob"; break; case tHfloat: typeStr = "Hfloat"; break; case tHalfVector3: typeStr = "HalfVector3"; break; + case tUshortVector3: typeStr = "UshortVector3"; break; case tByteVector3: typeStr = "ByteVector3"; break; case tHalfVector2: typeStr = "HalfVector2"; break; case tByteColor4: typeStr = "ByteColor4"; break; diff --git a/src/data/nifvalue.h b/src/data/nifvalue.h index 2ef72477e..7286d19d4 100644 --- a/src/data/nifvalue.h +++ b/src/data/nifvalue.h @@ -126,10 +126,12 @@ class NifValue tBlob, tHfloat, tHalfVector3, + tUshortVector3, tByteVector3, tHalfVector2, tByteColor4, tBSVertexDesc, + tNormbyte, tNone= 0xff }; @@ -253,7 +255,7 @@ class NifValue //! Check if the type of the data is a flag type (Flags in xml). bool isFlags() const { return typ == tFlags; } //! Check if the type of the data is a float type (Float in xml). - bool isFloat() const { return (typ == tFloat) || (typ == tHfloat); } + bool isFloat() const { return (typ == tFloat) || (typ == tHfloat) || (typ == tNormbyte); } //! Check if the type of the data is of a link type (Ref or Ptr in xml). bool isLink() const { return typ == tLink || typ == tUpLink; } //! Check if the type of the data is a 3x3 matrix type (Matrix33 in xml). @@ -592,6 +594,10 @@ template <> inline HalfVector3 NifValue::get( const BaseModel * model, const Nif { return getType( tHalfVector3, model, item ); } +template <> inline UshortVector3 NifValue::get( const BaseModel * model, const NifItem * item ) const +{ + return getType( tUshortVector3, model, item ); +} template <> inline ByteVector3 NifValue::get( const BaseModel * model, const NifItem * item ) const { return getType( tByteVector3, model, item ); @@ -740,6 +746,11 @@ template <> inline bool NifValue::set( const HalfVector3 & x, const BaseModel * { return setType( tHalfVector3, x, model, item ); } +//! Set the data from a UshortVector3. Return true if successful. +template <> inline bool NifValue::set( const UshortVector3 & x, const BaseModel * model, const NifItem * item ) +{ + return setType( tUshortVector3, x, model, item ); +} //! Set the data from a ByteVector3. Return true if successful. template <> inline bool NifValue::set( const ByteVector3 & x, const BaseModel * model, const NifItem * item ) { diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 290ca7212..441b94ccc 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -1012,23 +1012,24 @@ void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid ) glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_LINE : GL_FILL ); glEnable( GL_CULL_FACE ); - QModelIndex iChunks = nif->getIndex( iData, "Chunks" ); - for ( int r = 0; r < nif->rowCount( iChunks ); r++ ) { - Vector4 chunkOrigin = nif->get( iChunks.child( r, 0 ), "Translation" ); + QModelIndex iChunkArr = nif->getIndex( iData, "Chunks" ); + for ( int r = 0; r < nif->rowCount( iChunkArr ); r++ ) { + auto iChunk = nif->index(r, 0, iChunkArr); + Vector4 chunkOrigin = nif->get( iChunk, "Translation" ); - quint32 transformIndex = nif->get( iChunks.child( r, 0 ), "Transform Index" ); + quint32 transformIndex = nif->get( iChunk, "Transform Index" ); QModelIndex chunkTransform = iChunkTrans.child( transformIndex, 0 ); Vector4 chunkTranslation = nif->get( chunkTransform.child( 0, 0 ) ); Quat chunkRotation = nif->get( chunkTransform.child( 1, 0 ) ); - quint32 numOffsets = nif->get( iChunks.child( r, 0 ), "Num Vertices" ); - quint32 numIndices = nif->get( iChunks.child( r, 0 ), "Num Indices" ); - quint32 numStrips = nif->get( iChunks.child( r, 0 ), "Num Strips" ); - QVector offsets = nif->getArray( iChunks.child( r, 0 ), "Vertices" ); - QVector indices = nif->getArray( iChunks.child( r, 0 ), "Indices" ); - QVector strips = nif->getArray( iChunks.child( r, 0 ), "Strips" ); + quint32 numOffsets = nif->get( iChunk, "Num Vertices" ) / 3; + quint32 numIndices = nif->get( iChunk, "Num Indices" ); + quint32 numStrips = nif->get( iChunk, "Num Strips" ); + QVector offsets = nif->getArray( iChunk, "Vertices" ); + QVector indices = nif->getArray( iChunk, "Indices" ); + QVector strips = nif->getArray( iChunk, "Strips" ); - QVector vertices( numOffsets / 3 ); + QVector vertices( numOffsets ); int numStripVerts = 0; int offset = 0; @@ -1038,7 +1039,7 @@ void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid ) } for ( int n = 0; n < ((int)numOffsets / 3); n++ ) { - vertices[n] = chunkOrigin + chunkTranslation + Vector4( offsets[3 * n], offsets[3 * n + 1], offsets[3 * n + 2], 0 ) / 1000.0f; + vertices[n] = chunkOrigin + chunkTranslation + Vector4( offsets[n], 0.0f ) / 1000.0f; } glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_FILL : GL_LINE ); diff --git a/src/io/nifstream.cpp b/src/io/nifstream.cpp index 7bdb26309..70636dbea 100644 --- a/src/io/nifstream.cpp +++ b/src/io/nifstream.cpp @@ -143,6 +143,17 @@ bool NifIStream::read( NifValue & val ) val.val.u32 = half_to_float( half ); return (dataStream->status() == QDataStream::Ok); } + case NifValue::tNormbyte: + { + quint8 v; + float fv; + *dataStream >> v; + fv = (double(v) / 255.0) * 2.0 - 1.0; + val.val.u64 = 0; + val.val.f32 = fv; + + return (dataStream->status() == QDataStream::Ok); + } case NifValue::tByteVector3: { quint8 x, y, z; @@ -159,6 +170,24 @@ bool NifIStream::read( NifValue & val ) Vector3 * v = static_cast(val.val.data); v->xyz[0] = xf; v->xyz[1] = yf; v->xyz[2] = zf; + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tUshortVector3: + { + uint16_t x, y, z; + float xf, yf, zf; + + *dataStream >> x; + *dataStream >> y; + *dataStream >> z; + + xf = (float) x; + yf = (float) y; + zf = (float) z; + + Vector3 * v = static_cast(val.val.data); + v->xyz[0] = xf; v->xyz[1] = yf; v->xyz[2] = zf; + return (dataStream->status() == QDataStream::Ok); } case NifValue::tHalfVector3: @@ -578,6 +607,12 @@ bool NifOStream::write( const NifValue & val ) uint16_t half = half_from_float( val.val.u32 ); return device->write( (char *)&half, 2 ) == 2; } + case NifValue::tNormbyte: + { + uint8_t v = round( ((val.val.f32 + 1.0) / 2.0) * 255.0 ); + + return device->write( (char*)&v, 1 ) == 1; + } case NifValue::tByteVector3: { Vector3 * vec = static_cast(val.val.data); @@ -591,6 +626,19 @@ bool NifOStream::write( const NifValue & val ) return device->write( (char*)v, 3 ) == 3; } + case NifValue::tUshortVector3: + { + Vector3 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + + uint16_t v[3]; + v[0] = (uint16_t) round(vec->xyz[0]); + v[1] = (uint16_t) round(vec->xyz[1]); + v[2] = (uint16_t) round(vec->xyz[2]); + + return device->write( (char*)v, 6 ) == 3; + } case NifValue::tHalfVector3: { Vector3 * vec = static_cast(val.val.data); @@ -840,6 +888,7 @@ int NifSStream::size( const NifValue & val ) return 1; case NifValue::tByte: + case NifValue::tNormbyte: return 1; case NifValue::tWord: case NifValue::tShort: diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 88e533200..482e4664b 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -389,6 +389,7 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const } case NifValue::tFloat: case NifValue::tHfloat: + case NifValue::tNormbyte: { float f = item->valueToFloat(); quint32 i = item->valueToCount(); @@ -401,6 +402,8 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const } case NifValue::tVector3: return item->get().toHtml(); + case NifValue::tUshortVector3: + return item->get().toHtml(); case NifValue::tHalfVector3: return item->get().toHtml(); case NifValue::tByteVector3: diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index e8a59e31a..5fd65210e 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -1268,6 +1268,7 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const } case NifValue::tFloat: case NifValue::tHfloat: + case NifValue::tNormbyte: { return tr( "float: %1\nhex: 0x%2" ) .arg( NumOrMinMax( item->valueToFloat(), 'g', 8 ) ) @@ -1291,6 +1292,8 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const return item->get().toHtml(); case NifValue::tHalfVector3: return item->get().toHtml(); + case NifValue::tUshortVector3: + return item->get().toHtml(); case NifValue::tByteVector3: return item->get().toHtml(); case NifValue::tMatrix: diff --git a/src/nifskope.cpp b/src/nifskope.cpp index b326c8bc4..0b130dca5 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -88,7 +88,8 @@ const QList> NifSkope::filetypes = { // KF types { "Keyframe", "kf" }, { "Keyframe Animation", "kfa" }, { "Keyframe Motion", "kfm" }, // Miscellaneous NIF types - { "NIFCache", "nifcache" }, { "TEXCache", "texcache" }, { "PCPatch", "pcpatch" }, { "JMI", "jmi" } + { "NIFCache", "nifcache" }, { "TEXCache", "texcache" }, { "PCPatch", "pcpatch" }, { "JMI", "jmi" }, + { "Divinity 2 Character Template", "cat" } }; QStringList NifSkope::fileExtensions() diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 066b4ade1..9c5bf3b06 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -212,6 +212,7 @@ void NifTreeView::pasteTo( QModelIndex iDest, const NifValue & srcValue ) break; case NifValue::tFloat: case NifValue::tHfloat: + case NifValue::tNormbyte: item->set( srcValue.get( nif, nullptr ) ); break; case NifValue::tColor3: diff --git a/src/ui/widgets/valueedit.cpp b/src/ui/widgets/valueedit.cpp index b1de34d98..de7880c98 100644 --- a/src/ui/widgets/valueedit.cpp +++ b/src/ui/widgets/valueedit.cpp @@ -65,7 +65,7 @@ bool ValueEdit::canEdit( NifValue::Type t ) || t == NifValue::tMatrix || t == NifValue::tQuat || t == NifValue::tQuatXYZW || t == NifValue::tTriangle || t == NifValue::tShort || t == NifValue::tUInt || t == NifValue::tULittle32 || t == NifValue::tHfloat || t == NifValue::tHalfVector3 || t == NifValue::tByteVector3 - || t == NifValue::tHalfVector2; + || t == NifValue::tHalfVector2 || t == NifValue::tNormbyte; } class CenterLabel final : public QLabel @@ -220,6 +220,7 @@ void ValueEdit::setValue( const NifValue & v ) break; case NifValue::tFloat: case NifValue::tHfloat: + case NifValue::tNormbyte: { FloatEdit * fe = new FloatEdit( this ); /* @@ -407,6 +408,7 @@ NifValue ValueEdit::getValue() const break; case NifValue::tFloat: case NifValue::tHfloat: + case NifValue::tNormbyte: val.setFloat( qobject_cast( edit )->value(), nullptr, nullptr ); break; case NifValue::tLineString: diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index b637c5a16..76c484a0f 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -244,7 +244,7 @@ void TestShredder::run() QStringList extensions; if ( chkNif->isChecked() ) - extensions << "*.nif" << "*.nifcache" << "*.texcache" << "*.pcpatch" << "*.bto" << "*.btr" << "*.item" << "*.nif_wii"; + extensions << "*.nif" << "*.nifcache" << "*.texcache" << "*.pcpatch" << "*.bto" << "*.btr" << "*.item" << "*.nif_wii" << "*.cat"; if ( chkKf->isChecked() ) extensions << "*.kf" << "*.kfa"; if ( chkKfm->isChecked() ) diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index c4f1ee900..a9607fa73 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -82,7 +82,8 @@ class NifXmlHandler final : public QXmlDefaultHandler tagMember, tagToken, tagTokenTag, - tagModule + tagModule, + tagVerAttr }; //! i18n wrapper for various strings @@ -110,6 +111,7 @@ class NifXmlHandler final : public QXmlDefaultHandler tags.insert( "token", tagToken ); tags.insert( "bitfield", tagBitfield ); tags.insert( "member", tagMember ); + tags.insert( "verattr", tagVerAttr ); tokens.clear(); } @@ -308,6 +310,7 @@ class NifXmlHandler final : public QXmlDefaultHandler } break; case tagModule: + case tagVerAttr: // Unused Metadata break; default: err( tr( "expected basic, enum, struct, niobject or version got %1 instead" ).arg( tagid ) ); @@ -507,6 +510,9 @@ class NifXmlHandler final : public QXmlDefaultHandler err( tr( "only token tags allowed in token declaration" ) );; } break; + case tagVerAttr: + // Unused Metadata + break; default: err( tr( "error unhandled tag %1" ).arg( tagid ) ); break; @@ -625,8 +631,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() == XMLTMPL + || NifValue::type( d.type() ) != NifValue::tNone + || d.type() == XMLTMPL ); } @@ -634,10 +640,10 @@ class NifXmlHandler final : public QXmlDefaultHandler bool checkTemp( const NifData & d ) { return ( d.temp().isEmpty() - || NifValue::type( d.temp() ) != NifValue::tNone - || d.temp() == XMLTMPL - || NifModel::blocks.contains( d.temp() ) - || NifModel::compounds.contains( d.temp() ) + || NifValue::type( d.temp() ) != NifValue::tNone + || d.temp() == XMLTMPL + || NifModel::blocks.contains( d.temp() ) + || NifModel::compounds.contains( d.temp() ) ); } @@ -702,9 +708,9 @@ 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" From bf0a264d3c8bc640c1f0897a94a1913d809320ac Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 4 Sep 2023 09:19:03 -0400 Subject: [PATCH 073/118] nifxml fix INFINITY token --- src/xml/nifxml.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index a9607fa73..53d376cf7 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -164,8 +164,11 @@ class NifXmlHandler final : public QXmlDefaultHandler { QString str = list.value(attr); if ( tokens.contains( attr ) ) { - for ( const auto & p : tokens[attr] ) - str.replace( p.first, p.second ); + for ( const auto& p : tokens[attr] ) + if ( p.second == "INFINITY" ) + str.replace(p.first, "0x7F800000"); + else + str.replace( p.first, p.second ); } return str; } From dcac1435e94ee27f3203010c94d33bf14a374327 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Tue, 5 Sep 2023 07:17:18 -0400 Subject: [PATCH 074/118] Fix reportError for batch operations --- src/model/basemodel.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 482e4664b..d56607e6c 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -819,7 +819,10 @@ QString BaseModel::topItemRepr( const NifItem * item ) const void BaseModel::reportError( const QString & err ) const { - Message::append( getWindow(), "Parsing warnings.", err ); + if ( msgMode == MSG_USER ) + Message::append(getWindow(), "Parsing warnings.", err); + else + testMsg(err); } void BaseModel::reportError( const NifItem * item, const QString & err ) const From a5e6b20fa16d739894c6faaa07ec47d14705831f Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Tue, 5 Sep 2023 07:26:50 -0400 Subject: [PATCH 075/118] File Checker output error count Mainly meant for when there are 0 errors, so that it is clear there were no errors. --- src/ui/widgets/xmlcheck.cpp | 19 ++++++++++++++++++- src/ui/widgets/xmlcheck.h | 6 ++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index 76c484a0f..dc8336323 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -87,7 +87,7 @@ TestShredder::TestShredder() hdrOnly = new QCheckBox( tr( "Header Only" ), this ); hdrOnly->setChecked( settings.value( "Header Only", false ).toBool() ); - repErr = new QCheckBox( tr( "List Matches Only" ), this ); + repErr = new QCheckBox( tr( "Show only Matches/Errors" ), this ); repErr->setChecked( settings.value( "List Matches Only", true ).toBool() ); count = new QSpinBox(); @@ -203,6 +203,7 @@ void TestShredder::renumberThreads( int num ) connect( thread, &TestThread::sigStart, this, &TestShredder::threadStarted ); connect( thread, &TestThread::sigReady, text, &QTextBrowser::append ); connect( thread, &TestThread::finished, this, &TestShredder::threadFinished ); + connect( thread, &TestThread::incrementError, this, &TestShredder::onIncrementError ); threads.append( thread ); thread->blockMatch = blockMatch->text(); @@ -228,6 +229,7 @@ void TestShredder::renumberThreads( int num ) void TestShredder::run() { + errorCount = 0; progress->setMaximum( progress->maximum() - queue.count() ); queue.clear(); @@ -278,6 +280,7 @@ void TestShredder::threadStarted() void TestShredder::threadFinished() { + uint32_t finishedThreads = 0; if ( queue.isEmpty() ) { for ( TestThread * thread : threads ) { if ( thread->isRunning() ) @@ -288,9 +291,22 @@ void TestShredder::threadFinished() label->setText( tr( "%1 files in %2 seconds" ).arg( progress->maximum() ).arg( time.secsTo( QDateTime::currentDateTime() ) ) ); label->setVisible( true ); + + for ( TestThread* thread : threads ) { + if ( thread->isFinished() ) + finishedThreads++; + } + if ( finishedThreads == threads.count() ) + text->append(tr("Completed with %1 errors.").arg(errorCount)); } } +void TestShredder::onIncrementError() +{ + QMutexLocker lock(&mutex); + errorCount += 1; +} + void TestShredder::chooseBlock() { QStringList ids = NifModel::allNiBlocks(); @@ -548,6 +564,7 @@ void TestThread::run() if ( msg.type() != QtDebugMsg ) { result += "
" + msg; rep |= true; + emit incrementError(); } } diff --git a/src/ui/widgets/xmlcheck.h b/src/ui/widgets/xmlcheck.h index 554af3cfa..8dcb5704d 100644 --- a/src/ui/widgets/xmlcheck.h +++ b/src/ui/widgets/xmlcheck.h @@ -99,6 +99,7 @@ class TestThread final : public QThread signals: void sigStart( const QString & file ); void sigReady( const QString & result ); + void incrementError(); protected: void run() override final; @@ -129,6 +130,8 @@ protected slots: void threadStarted(); void threadFinished(); + void onIncrementError(); + void renumberThreads( int ); protected: @@ -154,6 +157,9 @@ protected slots: QList threads; QDateTime time; + + QMutex mutex; + uint32_t errorCount = 0; }; #endif From 52f1e64debee0524f7d0d16f005ed723e3536f8c Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 7 Sep 2023 00:29:30 +0300 Subject: [PATCH 076/118] NifSkope.pro updates - Changed C++ standard version to 20 (from 14 and "latest"). - Changed output file template to vcapp (Visual Studio Project). --- NifSkope.pro | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/NifSkope.pro b/NifSkope.pro index 7e3076037..12b7da045 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -2,7 +2,7 @@ ## BUILD OPTIONS ############################### -TEMPLATE = app +TEMPLATE = vcapp TARGET = NifSkope QT += xml opengl network widgets @@ -13,8 +13,8 @@ contains(QT_VERSION, ^5\\.[0-6]\\..*) { error("Minimum required version is Qt 5.7") } -# C++11/14 Support -CONFIG += c++14 +# C++ Standard Support +CONFIG += c++20 # Dependencies CONFIG += nvtristrip qhull zlib lz4 fsengine gli @@ -413,7 +413,7 @@ win32 { # Standards conformance to match GCC and clang !isEmpty(_MSC_VER):greaterThan(_MSC_VER, 1900) { - QMAKE_CXXFLAGS += /permissive- /std:c++latest + QMAKE_CXXFLAGS += /permissive- /std:c++20 } # LINKER FLAGS @@ -437,8 +437,8 @@ win32 { QMAKE_CXXFLAGS_DEBUG *= -Og -g3 QMAKE_CXXFLAGS_RELEASE *= -O3 -mfpmath=sse - # C++11 Support - QMAKE_CXXFLAGS_RELEASE *= -std=c++14 + # C++ Standard Support + QMAKE_CXXFLAGS_RELEASE *= -std=c++20 # Extension flags QMAKE_CXXFLAGS_RELEASE *= -msse2 -msse From bb67de9be5484c05dfed7638cb64855d0756ee0e Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 7 Sep 2023 08:59:36 +0300 Subject: [PATCH 077/118] NifSkope.pro: added copying of styles\qwindowsvistastyle.dll on build This is required since Qt 5.15 for the "Windows" UI themes to work properly. --- NifSkope.pro | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NifSkope.pro b/NifSkope.pro index 12b7da045..bbcde2b32 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -523,8 +523,12 @@ win32:contains(QT_ARCH, i386) { $$[QT_INSTALL_PLUGINS]/imageformats/qtga$${DLLEXT} \ $$[QT_INSTALL_PLUGINS]/imageformats/qwebp$${DLLEXT} + styles += \ + $$[QT_INSTALL_PLUGINS]/styles/qwindowsvistastyle$${DLLEXT} \ + copyFiles( $$platforms, platforms, true ) copyFiles( $$imageformats, imageformats, true ) + copyFiles( $$styles, styles, true ) } } # end build_pass From 48697cdae909619dfa2c78740b038519902ff02d Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 7 Sep 2023 21:28:10 +0300 Subject: [PATCH 078/118] Trigonometry fixes and refactoring - Fixed undefined M_PI and other C++ math constants once and for all. Apparently, including math.h is not enough, _USE_MATH_DEFINES macros also must be defined. Solved by adding _USE_MATH_DEFINES to NifSkope.pro. - Added functions for converting radians to degrees and back. - Minor refactoring of Vector2, Vector3, Vector4 classes (improved precision of normalize() by removing a redundant multiply operation, added toString() method, minor optimizations and fixes). Note: if you don't update the project file by (re)generating it from NifSkope.pro, you must add _USE_MATH_DEFINES macros by hand for it to compile. In Visual Studio 2022 it's done in the project's properties -> Configuration Properties -> C/C++ -> Preprocessor -> Preprocessor Definitions. --- NifSkope.pro | 1 + src/data/niftypes.cpp | 4 +- src/data/niftypes.h | 139 +++++++++++++++++++--------------- src/data/nifvalue.cpp | 15 ++-- src/gl/glnode.cpp | 9 +-- src/gl/glproperty.cpp | 2 +- src/gl/gltools.cpp | 4 +- src/glview.cpp | 12 +-- src/spells/normals.cpp | 2 +- src/ui/widgets/colorwheel.cpp | 6 +- src/ui/widgets/inspect.cpp | 6 +- src/ui/widgets/uvedit.cpp | 4 +- src/ui/widgets/valueedit.cpp | 16 ++-- 13 files changed, 109 insertions(+), 111 deletions(-) diff --git a/NifSkope.pro b/NifSkope.pro index bbcde2b32..c4d5b3025 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -40,6 +40,7 @@ CONFIG(debug, debug|release) { # Require explicit DEFINES += \ + _USE_MATH_DEFINES \ # Define M_PI, etc. in cmath QT_NO_CAST_FROM_BYTEARRAY \ # QByteArray deprecations QT_NO_URL_CAST_FROM_STRING \ # QUrl deprecations QT_DISABLE_DEPRECATED_BEFORE=0x050300 #\ # Disable all functions deprecated as of 5.3 diff --git a/src/data/niftypes.cpp b/src/data/niftypes.cpp index 61f7e59ce..001f627aa 100644 --- a/src/data/niftypes.cpp +++ b/src/data/niftypes.cpp @@ -244,13 +244,13 @@ bool Matrix::toEuler( float & x, float & y, float & z ) const return true; } else { x = -atan2( -m[1][0], m[1][1] ); - y = -PI / 2; + y = float(-HALF_PI); z = 0.0; return false; } } else { x = atan2( m[1][0], m[1][1] ); - y = PI / 2; + y = float(HALF_PI); z = 0.0; return false; } diff --git a/src/data/niftypes.h b/src/data/niftypes.h index f992176a7..7ddcc0a6a 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -43,17 +43,34 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include + +//! @file niftypes.h Matrix, Matrix4, Triangle, Vector2, Vector3, Vector4, Color3, Color4, Quat + #ifndef PI -#ifdef M_PI #define PI M_PI -#else -//! This is only defined if we can't find M_PI, which should be in stdlib's %math.h -#define PI 3.1416f #endif + +#ifndef HALF_PI +#define HALF_PI M_PI_2 #endif +//! Convert radians to degrees (float). +constexpr inline float rad2degf( float rad ) { return double(rad) * 180.0 / M_PI; } +//! Convert radians to degrees (double). +constexpr inline double rad2degd( double rad ) { return rad * 180.0 / M_PI; } +//! Convert radians to degrees (float). +constexpr inline float rad2deg( float rad ) { return rad2degf( rad ); } +//! Convert radians to degrees (double). +constexpr inline double rad2deg( double rad ) { return rad2degd( rad ); } +//! Convert degrees to radians (float). +constexpr inline float deg2radf( float deg ) { return double(deg) * M_PI / 180.0; } +//! Convert degrees to radians (double). +constexpr inline double deg2radd( double deg ) { return deg * M_PI / 180.0; } +//! Convert degrees to radians (float). +constexpr inline float deg2rad( float deg ) { return deg2radf( deg ); } +//! Convert degrees to radians (double). +constexpr inline double deg2rad( double deg ) { return deg2radd( deg ); } -//! @file niftypes.h Matrix, Matrix4, Triangle, Vector2, Vector3, Vector4, Color3, Color4, Quat class NifModel; class QModelIndex; @@ -89,8 +106,7 @@ class Vector2 //! Add operator Vector2 operator+( const Vector2 & v ) const { - Vector2 w( *this ); - return ( w += v ); + return Vector2( xy[0] + v.xy[0], xy[1] + v.xy[1] ); } //! Minus-equals operator Vector2 & operator-=( const Vector2 & v ) @@ -102,13 +118,12 @@ class Vector2 //! Minus operator Vector2 operator-( const Vector2 & v ) const { - Vector2 w( *this ); - return ( w -= v ); + return Vector2( xy[0] - v.xy[0], xy[1] - v.xy[1] ); } //! Negation operator Vector2 operator-() const { - return Vector2() - *this; + return Vector2( -xy[0], -xy[1] ); } //! Times-equals operator Vector2 & operator*=( float s ) @@ -120,8 +135,7 @@ class Vector2 //! Times operator Vector2 operator*( float s ) const { - Vector2 w( *this ); - return ( w *= s ); + return Vector2( xy[0] * s, xy[1] * s ); } //! Divide equals operator Vector2 & operator/=( float s ) @@ -133,8 +147,7 @@ class Vector2 //! Divide operator Vector2 operator/( float s ) const { - Vector2 w( *this ); - return ( w /= s ); + return Vector2( xy[0] / s, xy[1] / s ); } //! Equality operator @@ -175,6 +188,12 @@ class Vector2 //! Set from string void fromString( QString str ); + //! Format as string + QString toString() const + { + return QString("(%1, %2)").arg( NumOrMinMax(xy[0]), NumOrMinMax(xy[1]) ); + } + protected: float xy[2]; @@ -206,7 +225,7 @@ class HalfVector2 : public Vector2 //! QDebug stream operator for Vector2 inline QDebug & operator<<( QDebug dbg, Vector2 v ) { - dbg.nospace() << "(" << v[0] << ", " << v[1] << ")"; + dbg.nospace() << v.toString(); return dbg.space(); } @@ -284,40 +303,33 @@ class Vector3 //! Add operator Vector3 operator+( const Vector3 & v ) const { - Vector3 w( *this ); - return w += v; + return Vector3( xyz[0] + v.xyz[0], xyz[1] + v.xyz[1], xyz[2] + v.xyz[2] ); } - - Vector3 & operator+( float s ) + //! Add operator + Vector3 operator+( float s ) const { - xyz[0] += s; - xyz[1] += s; - xyz[2] += s; - return *this; + return Vector3( xyz[0] + s, xyz[1] + s, xyz[2] + s ); } //! Minus operator Vector3 operator-( const Vector3 & v ) const { - Vector3 w( *this ); - return w -= v; + return Vector3( xyz[0] - v.xyz[0], xyz[1] - v.xyz[1], xyz[2] - v.xyz[2] ); } //! Negation operator Vector3 operator-() const { - return Vector3() - *this; + return Vector3( -xyz[0], -xyz[1], -xyz[2] ); } //! Times operator Vector3 operator*( float s ) const { - Vector3 v( *this ); - return v *= s; + return Vector3( xyz[0] * s, xyz[1] * s, xyz[2] * s ); } //! Divide operator Vector3 operator/( float s ) const { - Vector3 v( *this ); - return v /= s; + return Vector3( xyz[0] / s, xyz[1] / s, xyz[2] / s ); } //! Equality operator @@ -356,14 +368,11 @@ class Vector3 { float m = length(); - if ( m > 0.0 ) - m = 1.0 / m; - else - m = 0.0F; - - xyz[0] *= m; - xyz[1] *= m; - xyz[2] *= m; + if ( m > 0.0f ) { + (*this) /= m; + } else { + xyz[0] = xyz[1] = xyz[2] = 0.0f; + } return *this; } @@ -386,9 +395,9 @@ class Vector3 if ( dot > 1.0 ) return 0.0; else if ( dot < -1.0 ) - return (float)PI; + return float(PI); else if ( dot == 0.0 ) - return (float)(PI / 2); + return float(HALF_PI); return acos( dot ); } @@ -428,6 +437,12 @@ class Vector3 //! Set from string void fromString( QString str ); + //! Format as string + QString toString() const + { + return tr("(%1, %2, %3)").arg( NumOrMinMax(xyz[0]), NumOrMinMax(xyz[1]), NumOrMinMax(xyz[2]) ); + } + //! Format as HTML QString toHtml() const { @@ -505,7 +520,7 @@ class ByteVector3 : public Vector3 //! QDebug stream operator for Vector3 inline QDebug & operator<<( QDebug dbg, const Vector3 & v ) { - dbg.nospace() << "(" << v[0] << ", " << v[1] << ", " << v[2] << ")"; + dbg.nospace() << v.toString(); return dbg.space(); } @@ -582,31 +597,27 @@ class Vector4 //! Add operator Vector4 operator+( const Vector4 & v ) const { - Vector4 w( *this ); - return w += v; + return Vector4( xyzw[0] + v.xyzw[0], xyzw[1] + v.xyzw[1], xyzw[2] + v.xyzw[2], xyzw[3] + v.xyzw[3] ); } //! Minus operator Vector4 operator-( const Vector4 & v ) const { - Vector4 w( *this ); - return w -= v; + return Vector4( xyzw[0] - v.xyzw[0], xyzw[1] - v.xyzw[1], xyzw[2] - v.xyzw[2], xyzw[3] - v.xyzw[3] ); } //! Negation operator Vector4 operator-() const { - return Vector4() - *this; + return Vector4( -xyzw[0], -xyzw[1], -xyzw[2], -xyzw[3] ); } //! Times operator Vector4 operator*( float s ) const { - Vector4 v( *this ); - return v *= s; + return Vector4( xyzw[0] * s, xyzw[1] * s, xyzw[2] * s, xyzw[3] * s ); } //! Divide operator Vector4 operator/( float s ) const { - Vector4 v( *this ); - return v /= s; + return Vector4( xyzw[0] / s, xyzw[1] / s, xyzw[2] / s, xyzw[3] / s ); } //! Equality operator @@ -645,15 +656,11 @@ class Vector4 { float m = length(); - if ( m > 0.0 ) - m = 1.0 / m; - else - m = 0.0F; - - xyzw[0] *= m; - xyzw[1] *= m; - xyzw[2] *= m; - xyzw[3] *= m; + if ( m > 0.0f ) { + (*this) /= m; + } else { + xyzw[0] = xyzw[1] = xyzw[2] = xyzw[3] = 0.0f; + } } //! Find the dot product of two vectors @@ -670,11 +677,11 @@ class Vector4 if ( dot > 1.0 ) return 0.0; else if ( dot < -1.0 ) - return (float)PI; + return float(PI); else if ( dot == 0.0 ) - return (float)PI / 2; + return float(HALF_PI); - return (float)acos( dot ); + return acos( dot ); } //! Comparison function for lexicographic sorting @@ -701,6 +708,12 @@ class Vector4 //! Set from string void fromString( QString str ); + //! Format as string + QString toString() const + { + return tr("(%1, %2, %3, %4)").arg( NumOrMinMax(xyzw[0]), NumOrMinMax(xyzw[1]), NumOrMinMax(xyzw[2]), NumOrMinMax(xyzw[3]) ); + } + //! Format as HTML QString toHtml() const { @@ -725,7 +738,7 @@ class Vector4 //! QDebug stream operator for Vector2 inline QDebug & operator<<( QDebug dbg, const Vector4 & v ) { - dbg.nospace() << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")"; + dbg.nospace() << v.toString(); return dbg.space(); } diff --git a/src/data/nifvalue.cpp b/src/data/nifvalue.cpp index 188881c6e..1c2301c8c 100644 --- a/src/data/nifvalue.cpp +++ b/src/data/nifvalue.cpp @@ -1016,9 +1016,9 @@ QString NifValue::toString() const } return ( pre + QString( "Y %1 P %2 R %3" ) + suf ) - .arg( NumOrMinMax( x / PI * 180, 'f', ROTATION_COARSE ) ) - .arg( NumOrMinMax( y / PI * 180, 'f', ROTATION_COARSE ) ) - .arg( NumOrMinMax( z / PI * 180, 'f', ROTATION_COARSE ) ); + .arg( NumOrMinMax( rad2deg(x), 'f', ROTATION_COARSE ) ) + .arg( NumOrMinMax( rad2deg(y), 'f', ROTATION_COARSE ) ) + .arg( NumOrMinMax( rad2deg(z), 'f', ROTATION_COARSE ) ); } case tMatrix4: { @@ -1027,16 +1027,13 @@ QString NifValue::toString() const m->decompose( t, r, s ); float xr, yr, zr; r.toEuler( xr, yr, zr ); - xr *= 180 / PI; - yr *= 180 / PI; - zr *= 180 / PI; return QString( "Trans( X %1 Y %2 Z %3 ) Rot( Y %4 P %5 R %6 ) Scale( X %7 Y %8 Z %9 )" ) .arg( t[0], 0, 'f', 3 ) .arg( t[1], 0, 'f', 3 ) .arg( t[2], 0, 'f', 3 ) - .arg( xr, 0, 'f', 3 ) - .arg( yr, 0, 'f', 3 ) - .arg( zr, 0, 'f', 3 ) + .arg( rad2deg(xr), 0, 'f', 3 ) + .arg( rad2deg(yr), 0, 'f', 3 ) + .arg( rad2deg(zr), 0, 'f', 3 ) .arg( s[0], 0, 'f', 3 ) .arg( s[1], 0, 'f', 3 ) .arg( s[2], 0, 'f', 3 ); diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index b585fbb53..c63c714ac 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -51,11 +51,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! @file glnode.cpp Scene management for visible NiNodes and their children. -#ifndef M_PI -#define M_PI 3.1415926535897932385 -#endif - - int Node::SELECTING = 0; static QColor highlightColor; @@ -1310,7 +1305,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c float angle = atan2f( slidingAxis[1], slidingAxis[0] ); if ( slidingAxis[0] < 0.0001f && slidingAxis[1] < 0.0001f ) { - angle = (float)PI / 2.0f; + angle = float(HALF_PI); } t.translation = d1; @@ -1840,7 +1835,7 @@ 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( "rotation Y %1, P %2, R %3 " ).Farg( rad2deg(xr) ).Farg( rad2deg(yr) ).Farg( rad2deg(zr) ) + 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 ) ) diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index cbded8401..a5637d0a5 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -441,7 +441,7 @@ bool TexturingProperty::bind( int id, const QString & fname ) glTranslatef( textures[id].center[0], textures[id].center[1], 0 ); // rotation appears to be in radians - glRotatef( (textures[id].rotation * 180.0 / PI ), 0, 0, 1 ); + glRotatef( rad2deg( textures[id].rotation ), 0, 0, 1 ); // It appears that the scaling here is relative to center glScalef( textures[id].tiling[0], textures[id].tiling[1], 1 ); glTranslatef( textures[id].translation[0], textures[id].translation[1], 0 ); diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 441b94ccc..9a124bafc 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -596,7 +596,7 @@ void drawRagdollCone( const Vector3 & pivot, const Vector3 & twist, const Vector Vector3 xy = x * sin( f ) + y * sin( f <= PI / 2 || f >= 3 * PI / 2 ? maxPlaneAngle : -minPlaneAngle ) * cos( f ); - glVertex( pivot + z * sqrt( 1 - xy.length() * xy.length() ) + xy ); + glVertex( pivot + z * sqrt( 1 - xy.squaredLength() ) + xy ); } glEnd(); @@ -611,7 +611,7 @@ void drawRagdollCone( const Vector3 & pivot, const Vector3 & twist, const Vector Vector3 xy = x * sin( -f ) + y * sin( -f <= PI / 2 || -f >= 3 * PI / 2 ? maxPlaneAngle : -minPlaneAngle ) * cos( -f ); - glVertex( pivot + z * sqrt( 1 - xy.length() * xy.length() ) + xy ); + glVertex( pivot + z * sqrt( 1 - xy.squaredLength() ) + xy ); } glEnd(); diff --git a/src/glview.cpp b/src/glview.cpp index 1f7bf63d9..8734b6642 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -92,10 +92,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define ZOOM_QE_KEY_MULT 1.025 #define ZOOM_MOUSE_WHEEL_MULT 0.95 -#ifndef M_PI -#define M_PI 3.1415926535897932385 -#endif - //! @file glview.cpp GLView implementation @@ -460,7 +456,7 @@ void GLView::paintGL() 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.rotation.fromEuler( deg2rad(Rot[0]), deg2rad(Rot[1]), deg2rad(Rot[2]) ); viewTrans.translation = viewTrans.rotation * Pos; viewTrans.rotation = viewTrans.rotation * ap; @@ -523,9 +519,9 @@ void GLView::paintGL() Vector4 lightDir( 0.0, 0.0, 1.0, 0.0 ); if ( !frontalLight ) { - float decl = declination / 180.0 * PI; + float decl = deg2rad( declination ); Vector3 v( sin( decl ), 0, cos( decl ) ); - Matrix m; m.fromEuler( 0, 0, planarAngle / 180.0 * PI ); + Matrix m; m.fromEuler( 0, 0, deg2rad( planarAngle ) ); v = m * v; lightDir = Vector4( viewTrans.rotation * v, 0.0 ); @@ -931,7 +927,7 @@ void GLView::center() 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 ); + Pos += Matrix::euler( deg2rad(Rot[0]), deg2rad(Rot[1]), deg2rad(Rot[2]) ).inverted() * Vector3( x, y, z ); updateViewpoint(); update(); } diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index 40abfebc9..b7d0aac59 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -278,7 +278,7 @@ class spSmoothNormals final : public Spell return index; - float maxa = angle->value() / 180 * PI; + float maxa = deg2rad( angle->value() ); float maxd = dist->value(); QVector snorms( norms ); diff --git a/src/ui/widgets/colorwheel.cpp b/src/ui/widgets/colorwheel.cpp index 2282313aa..183ffa7c6 100644 --- a/src/ui/widgets/colorwheel.cpp +++ b/src/ui/widgets/colorwheel.cpp @@ -199,10 +199,6 @@ int ColorWheel::heightForWidth( int width ) const return width; } -#ifndef M_PI -#define M_PI 3.1415926535897932385 -#endif - void ColorWheel::paintEvent( QPaintEvent * e ) { Q_UNUSED( e ); @@ -364,7 +360,7 @@ void ColorWheel::setColor( int x, int y ) if ( V > 1.0 ) V = 1.0; if ( V > 0 ) { - double h = V * ( c + c / 2 ) / ( 2.0 * sin( 60.0 / 180.0 * M_PI ) ); + double h = V * ( c + c / 2 ) / ( 2.0 * sin( deg2radd(60.0) ) ); S = ( p.x() + h ) / ( h * 2 ); if ( S < 0.0 ) S = 0.0; diff --git a/src/ui/widgets/inspect.cpp b/src/ui/widgets/inspect.cpp index afcb34378..a793bf8a7 100644 --- a/src/ui/widgets/inspect.cpp +++ b/src/ui/widgets/inspect.cpp @@ -413,9 +413,9 @@ void InspectView::update() impl->rotYText->setText( QString( "%1" ).Farg( q[2] ) ); impl->rotZText->setText( QString( "%1" ).Farg( q[3] ) ); - impl->eulXText->setText( QString( "%1" ).Farg( x * 180.0f / PI ) ); - impl->eulYText->setText( QString( "%1" ).Farg( y * 180.0f / PI ) ); - impl->eulZText->setText( QString( "%1" ).Farg( z * 180.0f / PI ) ); + impl->eulXText->setText( QString( "%1" ).Farg( rad2deg(x) ) ); + impl->eulYText->setText( QString( "%1" ).Farg( rad2deg(y) ) ); + impl->eulZText->setText( QString( "%1" ).Farg( rad2deg(z) ) ); impl->matText->setText( QString( "[%1, %2, %3]\n[%4, %5, %6]\n[%7, %8, %9]\n" ) diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index ec7b7ad02..bd906352e 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -1454,7 +1454,7 @@ class UVWRotateCommand final : public QUndoCommand } Matrix rotMatrix; - rotMatrix.fromEuler( 0, 0, ( rotation * PI / 180.0 ) ); + rotMatrix.fromEuler( 0, 0, deg2rad(rotation) ); for ( const auto i : uvw->selection ) { Vector3 temp( uvw->texcoords[i], 0 ); @@ -1483,7 +1483,7 @@ class UVWRotateCommand final : public QUndoCommand } Matrix rotMatrix; - rotMatrix.fromEuler( 0, 0, -( rotation * PI / 180.0 ) ); + rotMatrix.fromEuler( 0, 0, -deg2rad(rotation) ); for ( const auto i : uvw->selection ) { Vector3 temp( uvw->texcoords[i], 0 ); diff --git a/src/ui/widgets/valueedit.cpp b/src/ui/widgets/valueedit.cpp index de7880c98..bbc3604c2 100644 --- a/src/ui/widgets/valueedit.cpp +++ b/src/ui/widgets/valueedit.cpp @@ -796,16 +796,16 @@ void RotationEdit::setMatrix( const Matrix & m ) { float Y, P, R; m.toEuler( Y, P, R ); - v[0]->setValue( Y / PI * 180 ); - v[1]->setValue( P / PI * 180 ); - v[2]->setValue( R / PI * 180 ); + v[0]->setValue( rad2deg(Y) ); + v[1]->setValue( rad2deg(P) ); + v[2]->setValue( rad2deg(R) ); } break; case mAxis: { Vector3 axis; float angle; m.toQuat().toAxisAngle( axis, angle ); - v[0]->setValue( angle / PI * 180 ); + v[0]->setValue( rad2deg(angle) ); for ( int x = 0; x < 3; x++ ) v[x + 1]->setValue( axis[x] ); @@ -837,13 +837,13 @@ Matrix RotationEdit::getMatrix() const case mAuto: case mEuler: { - Matrix m; m.fromEuler( v[0]->value() / 180 * PI, v[1]->value() / 180 * PI, v[2]->value() / 180 * PI ); + Matrix m; m.fromEuler( deg2rad(v[0]->value()), deg2rad(v[1]->value()), deg2rad(v[2]->value()) ); return m; }; case mAxis: { Quat q; - q.fromAxisAngle( Vector3( v[1]->value(), v[2]->value(), v[3]->value() ), v[0]->value() / 180 * PI ); + q.fromAxisAngle( Vector3( v[1]->value(), v[2]->value(), v[3]->value() ), deg2rad(v[0]->value()) ); Matrix m; m.fromQuat( q ); return m; @@ -859,13 +859,13 @@ Quat RotationEdit::getQuat() const case mAuto: case mEuler: { - Matrix m; m.fromEuler( v[0]->value() / 180 * PI, v[1]->value() / 180 * PI, v[2]->value() / 180 * PI ); + Matrix m; m.fromEuler( deg2rad(v[0]->value()), deg2rad(v[1]->value()), deg2rad(v[2]->value()) ); return m.toQuat(); } case mAxis: { Quat q; - q.fromAxisAngle( Vector3( v[1]->value(), v[2]->value(), v[3]->value() ), v[0]->value() / 180 * PI ); + q.fromAxisAngle( Vector3( v[1]->value(), v[2]->value(), v[3]->value() ), deg2rad(v[0]->value()) ); return q; } } From 1b8cfa6aeb3915ee8fc865640e2c55026a298f97 Mon Sep 17 00:00:00 2001 From: gavrant Date: Fri, 8 Sep 2023 09:04:47 +0300 Subject: [PATCH 079/118] Fixed a massive spam of Message::append calls freezing NifSkope by limiting the max. number of detailed text lines (error lines) in a message box to 50. --- src/message.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/message.cpp b/src/message.cpp index 58a56f303..97ed3e07d 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -141,16 +141,20 @@ class DetailsMessageBox : public QMessageBox { public: explicit DetailsMessageBox( QWidget * parent, const QString & txt ) - : QMessageBox( parent ), m_key( txt ) { } + : QMessageBox( parent ), m_key( txt ) { } ~DetailsMessageBox(); const QString & key() const { return m_key; } + int detailsCount() const { return m_nDetails; } + void updateDetailsCount() { m_nDetails++; } + protected: void closeEvent( QCloseEvent * event ) override; QString m_key; + int m_nDetails = 0; }; static QVector messageBoxes; @@ -175,11 +179,18 @@ void Message::append( QWidget * parent, const QString & str, const QString & err } if ( msgBox ) { - // Append strings to existing message box's Detailed Text - // Show box if it has been closed before - msgBox->show(); - if ( !err.isEmpty() ) - msgBox->setDetailedText( msgBox->detailedText().append( err + "\n" ) ); + // Limit the number of detail lines to MAX_DETAILS_COUNTER + // because when errors are spammed at hundreds or thousands in a row, this makes NifSkope unresponsive. + const int MAX_DETAILS_COUNTER = 50; + + if ( !err.isEmpty() && msgBox->detailsCount() <= MAX_DETAILS_COUNTER ) { + // Append strings to existing message box's Detailed Text + // Show box if it has been closed before + msgBox->show(); + QString newLine = ( msgBox->detailsCount() < MAX_DETAILS_COUNTER ) ? (err + "\n") : QString("...\n"); + msgBox->setDetailedText( msgBox->detailedText().append( newLine ) ); + msgBox->updateDetailsCount(); + } } else { // Create new message box @@ -205,11 +216,12 @@ void Message::append( QWidget * parent, const QString & str, const QString & err if ( !err.isEmpty() ) { msgBox->setDetailedText( err + "\n" ); + msgBox->updateDetailsCount(); // Auto-show detailed text on first show. // https://stackoverflow.com/questions/36083551/qmessagebox-show-details for ( auto btn : msgBox->buttons() ) { - if ( msgBox->buttonRole( btn ) == QMessageBox::ActionRole) { + if ( msgBox->buttonRole( btn ) == QMessageBox::ActionRole ) { btn->click(); // "Click" it to expand the detailed text break; } @@ -237,9 +249,9 @@ DetailsMessageBox::~DetailsMessageBox() unregisterMessageBox( this ); // Just in case if buttonClicked or closeEvent fail } -void DetailsMessageBox::closeEvent( QCloseEvent * event ) { - - QMessageBox::closeEvent(event); +void DetailsMessageBox::closeEvent( QCloseEvent * event ) +{ + QMessageBox::closeEvent( event ); if ( event->isAccepted() ) unregisterMessageBox( this ); } From 8c63b2437e01678162cdb1884abcf7fd1a02fc7c Mon Sep 17 00:00:00 2001 From: gavrant Date: Fri, 8 Sep 2023 11:01:25 +0300 Subject: [PATCH 080/118] Fixed "QWindowsWindow::setGeometry: Unable to set geometry" warning from Qt on Message::append by moving msgBox->setDetailedText(...) after msgBox->show(). Also registration for "OK button pressed" event was moved before msgBox->show(), just to be safe. --- src/message.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/message.cpp b/src/message.cpp index 97ed3e07d..7859ed490 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -214,6 +214,15 @@ void Message::append( QWidget * parent, const QString & str, const QString & err msgBox->setText( str ); msgBox->setIcon( icon ); + connect( msgBox, &QMessageBox::buttonClicked, [msgBox]( QAbstractButton * button ) { + Q_UNUSED( button ); + unregisterMessageBox( msgBox ); + } ); + + msgBox->show(); + + // setDetailedText(...) has to be after show(), + // otherwise a "QWindowsWindow::setGeometry: Unable to set geometry ..." warning from Qt appears in Debug build. if ( !err.isEmpty() ) { msgBox->setDetailedText( err + "\n" ); msgBox->updateDetailsCount(); @@ -228,14 +237,7 @@ void Message::append( QWidget * parent, const QString & str, const QString & err } } - msgBox->show(); msgBox->activateWindow(); - - // Clear Detailed Text with each confirmation - connect( msgBox, &QMessageBox::buttonClicked, [msgBox]( QAbstractButton * button ) { - Q_UNUSED( button ); - unregisterMessageBox( msgBox ); - } ); } } From 7aa8c4a73ad927f7ca5e3a7efd6077a99d64a811 Mon Sep 17 00:00:00 2001 From: gavrant Date: Fri, 8 Sep 2023 23:34:09 +0300 Subject: [PATCH 081/118] Fixed NifSkope failing to get the Rotation of the currently selected NiNode in "Show Nodes" + "Show Axes" mode because it's a Matrix not a Quat. --- src/gl/glnode.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index c63c714ac..2bf73a916 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -655,9 +655,7 @@ void Node::drawSelection() const glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); Transform t; - Matrix m; - m.fromQuat( nif->get( scene->currentIndex, "Rotation" ) ); - t.rotation = m; + t.rotation = nif->get( scene->currentIndex, "Rotation" ); glPushMatrix(); glMultMatrix( t ); From 84fe93838977102eb0130677da5f7284455afaf5 Mon Sep 17 00:00:00 2001 From: gavrant Date: Sun, 10 Sep 2023 21:49:50 +0300 Subject: [PATCH 082/118] Model refactoring, part 3: cleanup NifItem class: - Replaced "type" in the name of the string type functions (type(), hasType, setType) with "strType" (strType(), hasStrType, ...) to make it clearer that this is not about the numerical type of the item's value. - Replaced "temp" in the template functions (temp(), setTemp) with "templ" (templ(), setTempl). "Temp" made me think it has something to do with "temporary". - "Value" functions got shorter or clearer names. For example, valueIsCount became isCount, valueToCount - getCountValue, valueFromCount - setCountValue. - Deleted most of the static functions that checked for item == nullptr. No point to bloat the code for just 0 to 3 uses for those static "mirrors". - Added isArrayEx() which checks if the item is an array or its parent is a multi-array (same check as in the old BaseModel::isArray(const NifItem *)). - Added repr() function that returns string representation of the item ("NiTriShape [0]\Vertex Data [3]\Vertex colors"). BaseModel class: - Renamed isArray(const NifItem *) method to isArrayEx to show that it and isArray(const QModelIndex &) make different checks. - "type" and "temp" methods followed suit of their cousins from NifItem. - Fixed "look by child name" overloads of getItem ignoring reportErrors arg if the child name is a path with "\\". - If updateArraySize methods fail to find the array by its name, they will report it. - Added a string representation function and a couple overloads of reportError for a QModelIndex. NifModel class: - Fixed loadHeader failing to reset "BS Header\BS Version" field because of the preceding reset of "User Version" field making its condition invalid. - If setLink or setLinkArray methods fail to find the item by its name, they will report it. - Added some new stuff to the map of array singular pseudonyms. - Added a couple of quality of life function for working block numbers/links. --- src/data/nifitem.cpp | 7 +- src/data/nifitem.h | 190 ++++++++++------------ src/gl/glnode.cpp | 4 +- src/gl/renderer.cpp | 16 +- src/lib/importex/3ds.cpp | 8 +- src/lib/importex/obj.cpp | 8 +- src/model/basemodel.cpp | 197 ++++++++++++----------- src/model/basemodel.h | 57 ++++--- src/model/kfmmodel.cpp | 18 +-- src/model/nifmodel.cpp | 278 ++++++++++++++++++--------------- src/model/nifmodel.h | 54 ++++--- src/spells/blocks.cpp | 8 +- src/spells/flags.cpp | 4 +- src/spells/mesh.cpp | 4 +- src/spells/misc.cpp | 4 +- src/spells/sanitize.cpp | 2 +- src/spells/skeleton.cpp | 6 +- src/spells/transform.cpp | 2 +- src/ui/widgets/nifeditors.cpp | 16 +- src/ui/widgets/refrbrowser.cpp | 2 +- src/ui/widgets/xmlcheck.cpp | 4 +- src/xml/kfmxml.cpp | 4 +- src/xml/nifxml.cpp | 14 +- 23 files changed, 488 insertions(+), 419 deletions(-) diff --git a/src/data/nifitem.cpp b/src/data/nifitem.cpp index f5f16120b..0c7ab370f 100644 --- a/src/data/nifitem.cpp +++ b/src/data/nifitem.cpp @@ -113,7 +113,7 @@ void NifItem::updateLinkCache( int iStartChild, bool bDoCleanup ) // Add new links for ( int i = iStartChild; i < childItems.count(); i++ ) { const NifItem * c = childItems.at( i ); - if ( c->valueIsLink() ) + if ( c->isLink() ) linkRows.append( i ); if ( c->hasChildLinks() ) linkAncestorRows.append( i ); @@ -139,6 +139,11 @@ void NifItem::onParentItemChange() c->onParentItemChange(); } +QString NifItem::repr() const +{ + return parentModel->itemRepr( this ); +} + void NifItem::reportError( const QString & msg ) const { parentModel->reportError( this, msg ); diff --git a/src/data/nifitem.h b/src/data/nifitem.h index cf0d51b5f..3b1de699a 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -72,7 +72,7 @@ class NifSharedData final : public QSharedData NifSharedData( const QString & n, const QString & t, const QString & tt, const QString & a, const QString & a1, const QString & a2, const QString & c, quint32 v1, quint32 v2, NifSharedData::DataFlags f ) - : QSharedData(), name( n ), type( t ), temp( tt ), arg( a ), argexpr( a ), arr1( a1 ), arr2( a2 ), + : QSharedData(), name( n ), type( t ), templ( tt ), arg( a ), argexpr( a ), arr1( a1 ), arr2( a2 ), cond( c ), ver1( v1 ), ver2( v2 ), condexpr( c ), arr1expr( a1 ), flags( f ) { } @@ -91,7 +91,7 @@ class NifSharedData final : public QSharedData //! Type. QString type; //! Template type. - QString temp; + QString templ; //! Argument. QString arg; //! Arg as an expression. @@ -143,7 +143,7 @@ class NifData //! Get the type of the data. inline const QString & type() const { return d->type; } //! Get the template type of the data. - inline const QString & temp() const { return d->temp; } + inline const QString & templ() const { return d->templ; } //! Get the argument of the data. inline const QString & arg() const { return d->arg; } //! Get the first array length of the data. @@ -190,7 +190,7 @@ class NifData //! Sets the type of the data. void setType( const QString & type ) { d->type = type; } //! Sets the template type of the data. - void setTemp( const QString & temp ) { d->temp = temp; } + void setTempl( const QString & templ ) { d->templ = templ; } //! Sets the argument of the data. void setArg( const QString & arg ) { @@ -314,6 +314,8 @@ struct NifBlock class NifItem { public: + NifItem() = delete; + NifItem( BaseModel * model, NifItem * parent ) : parentModel( model ), parentItem( parent ) {} @@ -388,11 +390,10 @@ class NifItem ChildIterator childIter() const { return ChildIterator(childItems); } - - //! Get child items + //! Get QVector of child items. const QVector & children() { return childItems; } - //! Return a count of the number of child items + //! Return the number of child items. int childCount() const { return childItems.count(); } private: @@ -424,7 +425,7 @@ class NifItem NifItem * insertChild( const NifData & data, NifValue::Type forceVType, int at = -1 ) { NifItem * item = new NifItem( parentModel, data, this ); - item->valueChangeType( forceVType ); + item->changeValueType( forceVType ); registerChild( item, at ); return item; } @@ -586,10 +587,10 @@ class NifItem //! Return the name of the data inline const QString & name() const { return itemData.name(); } - //! Return the type of the data - inline const QString & type() const { return itemData.type(); } + //! Return the type of the data (the "type" attribute in the XML file). + inline const QString & strType() const { return itemData.type(); } //! Return the template type of the data - inline const QString & temp() const { return itemData.temp(); } + inline const QString & templ() const { return itemData.templ(); } //! Return the argument attribute of the data inline const QString & arg() const { return itemData.arg(); } //! Return the first array length of the data @@ -612,10 +613,11 @@ class NifItem //! Return the arr1 attribute of the data, as an expression inline const NifExpr & arr1expr() const { return itemData.arr1expr(); } //! Return the version condition attribute of the data - inline QString vercond() const { return itemData.vercond(); } + inline const QString & vercond() const { return itemData.vercond(); } //! Return the version condition attribute of the data, as an expression inline const NifExpr & verexpr() const { return itemData.verexpr(); } - //! Return the abstract attribute of the data + + //! Return the abstract attribute of the data. inline bool isAbstract() const { return itemData.isAbstract(); } //! Is the item data binary. Binary means the data is being treated as one blob. inline bool isBinary() const { return itemData.isBinary(); } @@ -627,31 +629,33 @@ class NifItem inline bool isArray() const { return itemData.isArray(); } //! Is the item data a multi-array. Multi-array means the item's children are also arrays. inline bool isMultiArray() const { return itemData.isMultiArray(); } + //! Is the item data an array or is its parent a multi-array. + inline bool isArrayEx() const { return isArray() || ( parentItem && parentItem->isMultiArray() ); } //! Is the item data conditionless. Conditionless means no expression evaluation is necessary. inline bool isConditionless() const { return itemData.isConditionless(); } - //! Does the item's name matches testName? + //! Does the item's name match testName? inline bool hasName( const QString & testName ) const { return itemData.name() == testName; } - //! Does the item's name matches testName? + //! Does the item's name match testName? inline bool hasName( const QLatin1String & testName ) const { return itemData.name() == testName; } - //! Does the item's name matches testName? + //! Does the item's name match testName? // item->hasName("Foo") is much faster than item->name() == "Foo" inline bool hasName( const char * testName ) const { return itemData.name() == QLatin1String(testName); } - //! Does the item's name matches testName? - inline bool hasType( const QString & testName ) const { return itemData.type() == testName; } - //! Does the item's name matches testName? - inline bool hasType( const QLatin1String & testName ) const { return itemData.type() == testName; } - //! Does the item's name matches testName? + //! Does the item's string type match testType? + inline bool hasStrType( const QString & testType ) const { return itemData.type() == testType; } + //! Does the item's string type match testType? + inline bool hasStrType( const QLatin1String & testType) const { return itemData.type() == testType; } + //! Does the item's string type match testType? // item->hasType("Foo") is much faster than item->type() == "Foo" - inline bool hasType( const char * testName ) const { return itemData.type() == QLatin1String(testName); } + inline bool hasStrType( const char * testType ) const { return itemData.type() == QLatin1String(testType); } //! Set the name inline void setName( const QString & name ) { itemData.setName( name ); } - //! Set the type - inline void setType( const QString & type ) { itemData.setType( type ); } + //! Set the string type + inline void setStrType( const QString & type ) { itemData.setType( type ); } //! Set the template type - inline void setTemp( const QString & temp ) { itemData.setTemp( temp ); } + inline void setTempl( const QString & temp ) { itemData.setTempl( temp ); } //! Set the argument attribute inline void setArg( const QString & arg ) { itemData.setArg( arg ); } //! Set the first array length @@ -681,86 +685,69 @@ class NifItem //! Gets the item's value type (NifValue::Type). inline NifValue::Type valueType() const { return itemData.valueType(); } + //! Check if the item's value is of testType. + inline bool hasValueType( NifValue::Type testType ) const { return valueType() == testType; } //! Check if the type of the item's value is a color type (Color3 or Color4 in xml). - inline bool valueIsColor() const { return itemData.valueIsColor(); } + inline bool isColor() const { return itemData.valueIsColor(); } //! Check if the type of the item's value is a count. - inline bool valueIsCount() const { return itemData.valueIsCount(); } + inline bool isCount() const { return itemData.valueIsCount(); } //! Check if the type of the item's value is a flag type (Flags in xml). - inline bool valueIsFlags() const { return itemData.valueIsFlags(); } + inline bool isFlags() const { return itemData.valueIsFlags(); } //! Check if the type of the item's value is a float type (Float in xml). - inline bool valueIsFloat() const { return itemData.valueIsFloat(); } + inline bool isFloat() const { return itemData.valueIsFloat(); } //! Check if the type of the item's value is of a link type (Ref or Ptr in xml). - inline bool valueIsLink() const { return itemData.valueIsLink(); } + inline bool isLink() const { return itemData.valueIsLink(); } //! Check if the type of the item's value is a 3x3 matrix type (Matrix33 in xml). - inline bool valueIsMatrix() const { return itemData.valueIsMatrix(); } + inline bool isMatrix() const { return itemData.valueIsMatrix(); } //! Check if the type of the item's value is a 4x4 matrix type (Matrix44 in xml). - inline bool valueIsMatrix4() const { return itemData.valueIsMatrix4(); } + inline bool isMatrix4() const { return itemData.valueIsMatrix4(); } + //! Check if the type of the item's value is a byte matrix. + inline bool isByteMatrix() const { return itemData.valueIsByteMatrix(); } //! Check if the type of the item's value is a quaternion type. - inline bool valueIsQuat() const { return itemData.valueIsQuat(); } + inline bool isQuat() const { return itemData.valueIsQuat(); } //! Check if the type of the item's value is a string type. - inline bool valueIsString() const { return itemData.valueIsString(); } - //! Check if the type of the item's value is a Vector 4. - inline bool valueIsVector4() const { return itemData.valueIsVector4(); } + inline bool isString() const { return itemData.valueIsString(); } + //! Check if the type of the item's value is a Vector 2. + inline bool isVector2() const { return itemData.valueIsVector2(); } + //! Check if the type of the item's value is a HalfVector2. + inline bool isHalfVector2() const { return itemData.valueIsHalfVector2(); } //! Check if the type of the item's value is a Vector 3. - inline bool valueIsVector3() const { return itemData.valueIsVector3(); } + inline bool isVector3() const { return itemData.valueIsVector3(); } //! Check if the type of the item's value is a Half Vector3. - inline bool valueIsHalfVector3() const { return itemData.valueIsHalfVector3(); } + inline bool isHalfVector3() const { return itemData.valueIsHalfVector3(); } //! Check if the type of the item's value is a Byte Vector3. - inline bool valueIsByteVector3() const { return itemData.valueIsByteVector3(); } - //! Check if the type of the item's value is a HalfVector2. - inline bool valueIsHalfVector2() const { return itemData.valueIsHalfVector2(); } - //! Check if the type of the item's value is a Vector 2. - inline bool valueIsVector2() const { return itemData.valueIsVector2(); } + inline bool isByteVector3() const { return itemData.valueIsByteVector3(); } + //! Check if the type of the item's value is a Vector 4. + inline bool isVector4() const { return itemData.valueIsVector4(); } //! Check if the type of the item's value is a triangle type. - inline bool valueIsTriangle() const { return itemData.valueIsTriangle(); } + inline bool isTriangle() const { return itemData.valueIsTriangle(); } //! Check if the type of the item's value is a byte array. - inline bool valueIsByteArray() const { return itemData.valueIsByteArray(); } + inline bool isByteArray() const { return itemData.valueIsByteArray(); } //! Check if the type of the item's value is a File Version. - inline bool valueIsFileVersion() const { return itemData.valueIsFileVersion(); } - //! Check if the type of the item's value is a byte matrix. - inline bool valueIsByteMatrix() const { return itemData.valueIsByteMatrix(); } - - //! Return the item's value as a count, if applicable. - inline quint64 valueToCount() const { return itemData.value.toCount( parentModel, this ); } - //! Return the value of an item as a count if the item is not null and if it's applicable. - static inline quint64 valueToCount( const NifItem * item ) { return item ? item->valueToCount() : 0; } - - //! Return the item's value as a float, if applicable. - inline float valueToFloat() const { return itemData.value.toFloat( parentModel, this ); } - //! Return the value of an item as a float if the item is not null and if it's applicable. - static inline float valueToFloat( const NifItem * item ) { return item ? item->valueToFloat() : 0.0f; } - - //! Return the item's value as a link, if applicable. - inline qint32 valueToLink() const { return itemData.value.toLink( parentModel, this ); } - //! Return the value of an item as a link if the item is not null and if it's applicable. - static inline qint32 valueToLink( const NifItem * item ) { return item ? item->valueToLink() : -1; } - - //! Return the item's value as a QColor, if applicable. - inline QColor valueToColor() const { return itemData.value.toColor( parentModel, this ); } - //! Return the value of an item as a QColor if the item is not null and if it's applicable. - static inline QColor valueToColor( const NifItem * item ) { return item ? item->valueToColor() : QColor(); } - - //! Return the item's value as a file version, if applicable. - inline quint32 valueToFileVersion() const { return itemData.value.toFileVersion( parentModel, this ); } - //! Return the value of an item as a file version if the item is not null and if it's applicable. - static inline quint32 valueToFileVersion( const NifItem * item ) { return item ? item->valueToFileVersion() : 0; } + inline bool isFileVersion() const { return itemData.valueIsFileVersion(); } + + //! Return the item's value as a count if applicable. + inline quint64 getCountValue() const { return itemData.value.toCount( parentModel, this ); } + //! Return the item's value as a float if applicable. + inline float getFloatValue() const { return itemData.value.toFloat( parentModel, this ); } + //! Return the item's value as a link if applicable. + inline qint32 getLinkValue() const { return itemData.value.toLink( parentModel, this ); } + //! Return the item's value as a QColor if applicable. + inline QColor getColorValue() const { return itemData.value.toColor( parentModel, this ); } + //! Return the item's value as a file version if applicable. + inline quint32 getFileVersionValue() const { return itemData.value.toFileVersion( parentModel, this ); } //! Return a string which represents the item's value. - inline QString valueToString() const { return itemData.value.toString(); } - //! Return a string which represents the value of an item if the the is the item is not null. - static inline QString valueToString( const NifItem * item ) { return item ? item->valueToString() : QString(); } - + inline QString getValueAsString() const { return itemData.value.toString(); } //! Return the item's value as a QVariant. - inline QVariant valueToVariant() const { return itemData.value.toVariant(); } - //! Return the value of an item as a QVariant if the item is not null. - static inline QVariant valueToVariant( const NifItem * item ) { return item ? item->valueToVariant() : QVariant(); } + inline QVariant getValueAsVariant() const { return itemData.value.toVariant(); } //! Get the value of the item template inline T get() const { return itemData.value.get( parentModel, this ); } //! Get the value of an item if it's not nullptr template static inline T get( const NifItem * item ) { return item ? item->get() : T(); } - //! Get the child items as an array + //! Get the child items' values as an array. template QVector getArray() const { QVector array; @@ -772,7 +759,7 @@ class NifItem } return array; } - //! Get the child items of arrayRoot as an array if arrayRoot is not nullptr + //! Get the values of the child items of arrayRoot as an array if arrayRoot is not nullptr. template static inline QVector getArray( const NifItem * arrayRoot ) { return arrayRoot ? arrayRoot->getArray() : QVector(); @@ -783,13 +770,13 @@ class NifItem //! Set the value of an item if it's not nullptr. template static inline bool set( NifItem * item, const T & v ) { return item ? item->set(v) : false; } - //! Set the child items from an array + //! Set the child items' values from an array. template bool setArray( const QVector & array ) { int nSize = childItems.count(); if ( nSize != array.count() ) { reportError( - "setArray", + __func__, QString( "The input QVector's size (%1) does not match the array's size (%2)." ).arg( array.count() ).arg( nSize ) ); return false; @@ -802,7 +789,7 @@ class NifItem return true; } - //! Set the child items from a single value + //! Set the child items' values from a single value. template bool fillArray( const T & val ) { for ( NifItem * child : childItems ) { @@ -814,37 +801,24 @@ class NifItem } //! Set the item's value to a count. - inline bool valueFromCount( quint64 c ) { return itemData.value.setCount( c, parentModel, this ); } - //! Set the value of an item to a count if the item is not null. - static inline bool valueFromCount( NifItem * item, quint64 c ) { return item ? item->valueFromCount( c ) : false; } - + inline bool setCountValue( quint64 c ) { return itemData.value.setCount( c, parentModel, this ); } //! Set the item's value to a float. - inline bool valueFromFloat( float f ) { return itemData.value.setFloat( f, parentModel, this ); } - //! Set the value of an item to a float if the item is not null. - static inline bool valueFromFloat( NifItem * item, float f ) { return item ? item->valueFromFloat( f ) : false; } - + inline bool setFloatValue( float f ) { return itemData.value.setFloat( f, parentModel, this ); } //! Set the item's value to a link (block number). - inline bool valueFromLink( qint32 link ) { return itemData.value.setLink( link, parentModel, this ); } - //! Set the value of an item to a link (block number) if the item is not null. - static inline bool valueFromLink( NifItem * item, qint32 link ) { return item ? item->valueFromLink( link ) : false; } - + inline bool setLinkValue( qint32 link ) { return itemData.value.setLink( link, parentModel, this ); } //! Set the item's value to a file version. - inline bool valueFromFileVersion( quint32 v ) { return itemData.value.setFileVersion( v, parentModel, this ); } - //! Set the value of an item to a file version if the item is not null. - static inline bool valueFromFileVersion( NifItem * item, quint32 v ) { return item ? item->valueFromFileVersion( v ) : false; } + inline bool setFileVersionValue( quint32 v ) { return itemData.value.setFileVersion( v, parentModel, this ); } //! Set the item's value from a string. - inline bool valueFromString( const QString & str ) { return itemData.value.setFromString( str, parentModel, this ); } - //! Set the value of an item from a string if the item is not null. - static inline bool valueFromString( NifItem * item, const QString & str ) { return item ? item->valueFromString( str ) : false; } - + inline bool setValueFromString( const QString & str ) { return itemData.value.setFromString( str, parentModel, this ); } //! Set the item's value from a QVariant. - inline bool valueFromVariant( const QVariant & v ) { return itemData.value.setFromVariant( v ); } - //! Set the value of an item from a QVariant if the item is not null. - static inline bool valueFromVariant( NifItem * item, const QVariant & v ) { return item ? item->valueFromVariant( v ) : false; } + inline bool setValueFromVariant( const QVariant & v ) { return itemData.value.setFromVariant( v ); } //! Change the type of value stored. - inline void valueChangeType( NifValue::Type t ) { itemData.value.changeType( t ); } + inline void changeValueType( NifValue::Type t ) { itemData.value.changeType( t ); } + + //! Return string representation ("path") of an item within its model (e.g., "NiTriShape [0]\Vertex Data [3]\Vertex colors"). + QString repr() const; void reportError( const QString & msg ) const; void reportError( const QString & funcName, const QString & msg ) const; diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index 2bf73a916..caa54caba 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -916,7 +916,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackrowCount( iTris ); t++ ) // DrawTriangleIndex( verts, nif->get( iTris.child( t, 0 ), "Triangle" ), t ); - } else if ( nif->isCompound( nif->itemType( scene->currentIndex ) ) ) { + } else if ( nif->isCompound( nif->itemStrType( scene->currentIndex ) ) ) { Triangle tri = nif->get( iTris.child( i, 0 ), "Triangle" ); DrawTriangleSelection( verts, tri ); //DrawTriangleIndex( verts, tri, i ); @@ -1866,7 +1866,7 @@ BoundSphere Node::bounds() const boundsphere |= BoundSphere( trans, rad.length() ); } - if ( nif->itemType( iBlock ) == "NiMesh" ) + if ( nif->itemStrType( iBlock ) == "NiMesh" ) boundsphere |= BoundSphere( nif, iBlock ); // BSBound collision bounding box diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index cd7f088dc..37b4b18f9 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -164,14 +164,14 @@ bool Renderer::ConditionSingle::eval( const NifModel * nif, const QVectorvalueIsString() ) - return compare( item->valueToString(), right ) ^ invert; - else if ( item->valueIsCount() ) - return compare( item->valueToCount(), right.toULongLong( nullptr, 0 ) ) ^ invert; - else if ( item->valueIsFloat() ) - return compare( item->valueToFloat(), (float)right.toDouble() ) ^ invert; - else if ( item->valueIsFileVersion() ) - return compare( item->valueToFileVersion(), right.toUInt( nullptr, 0 ) ) ^ invert; + if ( item->isString() ) + return compare( item->getValueAsString(), right ) ^ invert; + else if ( item->isCount() ) + return compare( item->getCountValue(), right.toULongLong( nullptr, 0 ) ) ^ invert; + else if ( item->isFloat() ) + return compare( item->getFloatValue(), (float)right.toDouble() ) ^ invert; + else if ( item->isFileVersion() ) + return compare( item->getFileVersionValue(), right.toUInt( nullptr, 0 ) ) ^ invert; else if ( item->valueType() == NifValue::tBSVertexDesc ) return compare( (uint) item->get().GetFlags(), right.toUInt( nullptr, 0 ) ) ^ invert; diff --git a/src/lib/importex/3ds.cpp b/src/lib/importex/3ds.cpp index 7ad330aff..5d8aaa0f3 100644 --- a/src/lib/importex/3ds.cpp +++ b/src/lib/importex/3ds.cpp @@ -638,7 +638,7 @@ void import3ds( NifModel * nif, const QModelIndex & index ) // Skyrim, nothing here yet } else if ( nif->getVersionNumber() >= 0x0303000D ) { //Newer versions use NiTexturingProperty and NiSourceTexture - if ( iTexProp.isValid() == false || objIndex != 0 || nif->itemType( iTexProp ) != "NiTexturingProperty" ) { + if ( iTexProp.isValid() == false || objIndex != 0 || nif->itemStrType( iTexProp ) != "NiTexturingProperty" ) { iTexProp = nif->insertNiBlock( "NiTexturingProperty" ); } @@ -649,7 +649,7 @@ void import3ds( NifModel * nif, const QModelIndex & index ) nif->set( iBaseMap, "Clamp Mode", 3 ); nif->set( iBaseMap, "Filter Mode", 2 ); - if ( iTexSource.isValid() == false || objIndex != 0 || nif->itemType( iTexSource ) != "NiSourceTexture" ) { + if ( iTexSource.isValid() == false || objIndex != 0 || nif->itemStrType( iTexSource ) != "NiSourceTexture" ) { iTexSource = nif->insertNiBlock( "NiSourceTexture" ); } @@ -665,13 +665,13 @@ void import3ds( NifModel * nif, const QModelIndex & index ) nif->set( iTexSource, "File Name", mat->map_Kd ); } else { //Older versions use NiTextureProperty and NiImage - if ( iTexProp.isValid() == false || objIndex != 0 || nif->itemType( iTexProp ) != "NiTextureProperty" ) { + if ( iTexProp.isValid() == false || objIndex != 0 || nif->itemStrType( iTexProp ) != "NiTextureProperty" ) { iTexProp = nif->insertNiBlock( "NiTextureProperty" ); } addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); - if ( iTexSource.isValid() == false || objIndex != 0 || nif->itemType( iTexSource ) != "NiImage" ) { + if ( iTexSource.isValid() == false || objIndex != 0 || nif->itemStrType( iTexSource ) != "NiImage" ) { iTexSource = nif->insertNiBlock( "NiImage" ); } diff --git a/src/lib/importex/obj.cpp b/src/lib/importex/obj.cpp index e851e2ae6..34a6ae4ef 100644 --- a/src/lib/importex/obj.cpp +++ b/src/lib/importex/obj.cpp @@ -838,7 +838,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) nif->updateArraySize( iTextures ); } else if ( nif->getVersionNumber() >= 0x0303000D ) { //Newer versions use NiTexturingProperty and NiSourceTexture - if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemType( iTexProp ) != "NiTexturingProperty" ) { + if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemStrType( iTexProp ) != "NiTexturingProperty" ) { if ( !cBSShaderPPLightingProperty ) // no need of NiTexturingProperty when BSShaderPPLightingProperty is present iTexProp = nif->insertNiBlock( "NiTexturingProperty" ); } @@ -855,7 +855,7 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) nif->set( iBaseMap, "Filter Mode", 2 ); } - if ( iTexSource.isValid() == false || first_tri_shape == false || nif->itemType( iTexSource ) != "NiSourceTexture" ) { + if ( iTexSource.isValid() == false || first_tri_shape == false || nif->itemStrType( iTexSource ) != "NiSourceTexture" ) { if ( !cBSShaderPPLightingProperty ) iTexSource = nif->insertNiBlock( "NiSourceTexture" ); } @@ -874,13 +874,13 @@ void importObj( NifModel * nif, const QModelIndex & index, bool collision ) } } else { //Older versions use NiTextureProperty and NiImage - if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemType( iTexProp ) != "NiTextureProperty" ) { + if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemStrType( iTexProp ) != "NiTextureProperty" ) { iTexProp = nif->insertNiBlock( "NiTextureProperty" ); } addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); - if ( iTexSource.isValid() == false || first_tri_shape == false || nif->itemType( iTexSource ) != "NiImage" ) { + if ( iTexSource.isValid() == false || first_tri_shape == false || nif->itemStrType( iTexSource ) != "NiImage" ) { iTexSource = nif->insertNiBlock( "NiImage" ); } diff --git a/src/model/basemodel.cpp b/src/model/basemodel.cpp index 482e4664b..53f96968a 100644 --- a/src/model/basemodel.cpp +++ b/src/model/basemodel.cpp @@ -134,36 +134,17 @@ QList BaseModel::getMessages() const * array functions */ -bool BaseModel::isArray( const QModelIndex & index ) const -{ - // TODO (Gavrant): I don't like that isArray(const QModelIndex &) and isArray( const NifItem *) check for different conditions. - const NifItem * item = getItem( index ); - return item ? item->isArray() : false; -} - -bool BaseModel::isArray( const NifItem * item ) const -{ - if ( !item ) - return false; - if ( item->isArray() ) - return true; - const NifItem * p = item->parent(); - if ( p && p->isMultiArray() ) - return true; - return false; -} - int BaseModel::evalArraySize( const NifItem * array ) const { // shortcut for speed - if ( !isArray( array ) ) { + if ( !isArrayEx(array) ) { if ( array ) - reportError( array, "evalArraySize", "The input item is not an array." ); + reportError( array, __func__, "The input item is not an array." ); return 0; } if ( array == root ) { - reportError( array, "evalArraySize", "The input item is the root."); + reportError( array, __func__, "The input item is the root." ); return 0; } @@ -183,19 +164,19 @@ QString BaseModel::itemName( const QModelIndex & index ) const return QString(); } -QString BaseModel::itemType( const QModelIndex & index ) const +QString BaseModel::itemStrType( const QModelIndex & index ) const { const NifItem * item = getItem( index ); if ( item ) - return item->type(); + return item->strType(); return QString(); } -QString BaseModel::itemTmplt( const QModelIndex & index ) const +QString BaseModel::itemTempl( const QModelIndex & index ) const { const NifItem * item = getItem( index ); if ( item ) - return item->temp(); + return item->templ(); return QString(); } @@ -319,9 +300,9 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const case NameCol: return item->name(); case TypeCol: - return item->type(); + return item->strType(); case ValueCol: - return item->valueToString(); + return item->getValueAsString(); case ArgCol: return item->arg(); case Arr1Col: @@ -346,9 +327,9 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const case NameCol: return item->name(); case TypeCol: - return item->type(); + return item->strType(); case ValueCol: - return item->valueToVariant(); + return item->getValueAsVariant(); case ArgCol: return item->arg(); case Arr1Col: @@ -376,7 +357,7 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const case NifValue::tWord: case NifValue::tShort: { - quint16 s = item->valueToCount(); + quint16 s = item->getCountValue(); return QString( "dec: %1
hex: 0x%2" ).arg( s ).arg( s, 4, 16, QChar( '0' ) ); } case NifValue::tBool: @@ -384,20 +365,20 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const case NifValue::tUInt: case NifValue::tULittle32: { - quint32 i = item->valueToCount(); + quint32 i = item->getCountValue(); return QString( "dec: %1
hex: 0x%2" ).arg( i ).arg( i, 8, 16, QChar( '0' ) ); } case NifValue::tFloat: case NifValue::tHfloat: case NifValue::tNormbyte: { - float f = item->valueToFloat(); - quint32 i = item->valueToCount(); + float f = item->getFloatValue(); + quint32 i = item->getCountValue(); return QString( "float: %1
data: 0x%2" ).arg( f ).arg( i, 8, 16, QChar( '0' ) ); } case NifValue::tFlags: { - quint16 f = item->valueToCount(); + quint16 f = item->getCountValue(); return QString( "dec: %1
hex: 0x%2
bin: 0b%3" ).arg( f ).arg( f, 4, 16, QChar( '0' ) ).arg( f, 16, 2, QChar( '0' ) ); } case NifValue::tVector3: @@ -440,8 +421,8 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const return QVariant(); case Qt::BackgroundColorRole: { - if ( column == ValueCol && item->valueIsColor() ) { - return item->valueToColor(); + if ( column == ValueCol && item->isColor() ) { + return item->getColorValue(); } } return QVariant(); @@ -464,10 +445,10 @@ bool BaseModel::setData( const QModelIndex & index, const QVariant & value, int item->setName( value.toString() ); break; case BaseModel::TypeCol: - item->setType( value.toString() ); + item->setStrType( value.toString() ); break; case BaseModel::ValueCol: - item->valueFromVariant( value ); + item->setValueFromVariant( value ); break; case BaseModel::ArgCol: item->setArg( value.toString() ); @@ -603,50 +584,65 @@ void BaseModel::refreshFileInfo( const QString & f ) * searching */ -const NifItem * BaseModel::getItem( const NifItem * parent, const QString & name, bool reportErrors ) const +const NifItem * BaseModel::getItemInternal( const NifItem * parent, const QString & name, bool reportErrors ) const { - if ( !parent ) - return nullptr; - - int slash = name.indexOf( QLatin1String("\\") ); - if ( slash > 0 ) { - QString left = name.left( slash ); - QString right = name.right( name.length() - slash - 1 ); + for ( auto item : parent->childIter() ) + if ( item->hasName(name) && evalCondition(item) ) + return item; - const NifItem * pp = ( left == QLatin1String("..") ) ? parent->parent() : getItem( parent, left ); - return getItem( pp, right ); - } + if ( reportErrors ) + reportError( parent, tr( "Could not find \"%1\" subitem." ).arg( name ) ); + return nullptr; +} +const NifItem * BaseModel::getItemInternal( const NifItem * parent, const QLatin1String & name, bool reportErrors ) const +{ for ( auto item : parent->childIter() ) if ( item->hasName(name) && evalCondition(item) ) return item; if ( reportErrors ) - reportError( parent, tr( "Could not find \"%1\" subitem." ).arg( name ) ); + reportError( parent, tr( "Could not find \"%1\" subitem." ).arg( QString(name) ) ); return nullptr; } -const NifItem * BaseModel::getItem( const NifItem * parent, const QLatin1String & name, bool reportErrors ) const +const QString SLASH_QSTRING("\\"); +const QString DOTS_QSTRING(".."); +const QLatin1String SLASH_LATIN("\\"); +const QLatin1String DOTS_LATIN(".."); + +const NifItem * BaseModel::getItem( const NifItem * parent, const QString & name, bool reportErrors ) const { if ( !parent ) return nullptr; - int slash = name.indexOf( QLatin1String("\\") ); - if ( slash > 0 ) { - QLatin1String left = name.left( slash ); - QLatin1String right = name.right( name.size() - slash - 1 ); + int slashPos = name.indexOf( SLASH_QSTRING ); + if ( slashPos > 0 ) { + QString left = name.left( slashPos ); + QString right = name.right( name.length() - slashPos - SLASH_QSTRING.length() ); - const NifItem * pp = ( left == QLatin1String("..") ) ? parent->parent() : getItem( parent, left ); - return getItem( pp, right ); + const NifItem * pp = ( left == DOTS_QSTRING ) ? parent->parent() : getItemInternal( parent, left, reportErrors ); + return getItem( pp, right, reportErrors ); } - for ( auto item : parent->childIter() ) - if ( item->hasName(name) && evalCondition(item) ) - return item; + return getItemInternal( parent, name, reportErrors ); +} - if ( reportErrors ) - reportError( parent, tr( "Could not find \"%1\" subitem." ).arg( name ) ); - return nullptr; +const NifItem * BaseModel::getItem( const NifItem * parent, const QLatin1String & name, bool reportErrors ) const +{ + if ( !parent ) + return nullptr; + + int slashPos = name.indexOf( SLASH_LATIN ); + if ( slashPos > 0 ) { + QLatin1String left = name.left( slashPos ); + QLatin1String right = name.right( name.size() - slashPos - SLASH_LATIN.size() ); + + const NifItem * pp = ( left == DOTS_LATIN ) ? parent->parent() : getItemInternal( parent, left, reportErrors ); + return getItem( pp, right, reportErrors ); + } + + return getItemInternal( parent, name, reportErrors ); } const NifItem * BaseModel::getItem( const NifItem * parent, int childIndex, bool reportErrors ) const @@ -658,19 +654,18 @@ const NifItem * BaseModel::getItem( const NifItem * parent, int childIndex, bool if ( item ) { if ( evalCondition(item) ) return item; - else { - if ( reportErrors ) { - QString repr; - if ( parent->isArray() ) - repr = QString::number( childIndex ); - else - repr = QString( "%1 (\"%2\")" ).arg( childIndex ).arg( item->name() ); - reportError( parent, QString( "Subitem %1 did not pass condition check." ).arg( repr ) ); - } + + if ( reportErrors ) { + QString repr; + if ( parent->isArray() ) + repr = QString::number( childIndex ); + else + repr = QString( "%1 (\"%2\")" ).arg( childIndex ).arg( item->name() ); + reportError( parent, tr( "Subitem with index %1 has failed condition check." ).arg( repr ) ); } } else { - if ( reportErrors ) - reportError( parent, QString( "Invalid child index %1." ).arg( childIndex ) ); + if ( reportErrors && childIndex >= 0 ) + reportError( parent, tr( "Invalid child index %1." ).arg( childIndex ) ); } return nullptr; @@ -680,17 +675,15 @@ const NifItem * BaseModel::getItem( const QModelIndex & index ) const { if ( !index.isValid() ) return nullptr; + if ( index.model() != this ) { - auto othermodel = static_cast( index.model() ); - const NifItem * tempItem = indexToItem( index ); - QString tempItemRepr = ( othermodel && tempItem ) ? othermodel->itemRepr(tempItem) : QString("???"); - reportError( tr( "BaseMode::getItem got an index from another model (%1)" ).arg( tempItemRepr ) ); + reportError( tr( "BaseModel::getItem got a model index from another model (%1)." ).arg( indexRepr(index) ) ); return nullptr; } const NifItem * item = indexToItem( index ); if ( !item ) - reportError( "BaseMode::getItem got a valid index with null internalPointer" ); + reportError( "BaseModel::getItem got a valid model index with null internalPointer." ); // WTF return item; } @@ -786,11 +779,13 @@ QString BaseModel::itemRepr( const NifItem * item ) const { if ( !item ) return QString("[NULL]"); + if ( item->model() != this ) + return item->model()->itemRepr( item ); if ( item == root ) return QString("[ROOT]"); QString result; - while( item ) { + while( true ) { const NifItem * parent = item->parent(); if ( !parent ) { result = "???" + result; // WTF... @@ -800,7 +795,7 @@ QString BaseModel::itemRepr( const NifItem * item ) const break; } else { QString subres; - if ( isArray( parent ) ) + if ( parent->isArrayEx() ) subres = QString(" [%1]").arg( item->row() ); else subres = "\\" + item->name(); @@ -812,6 +807,18 @@ QString BaseModel::itemRepr( const NifItem * item ) const return result; } +QString BaseModel::indexRepr( const QModelIndex & index ) +{ + if ( index.isValid() ) { + auto model = static_cast( index.model() ); + auto item = indexToItem( index ); + if ( model && item ) + return model->itemRepr( item ); + } + + return QString("[INVALID INDEX]"); +} + QString BaseModel::topItemRepr( const NifItem * item ) const { return QString("%2 [%1]").arg( item->row() ).arg( item->name() ); @@ -819,7 +826,7 @@ QString BaseModel::topItemRepr( const NifItem * item ) const void BaseModel::reportError( const QString & err ) const { - Message::append( getWindow(), "Parsing warnings.", err ); + Message::append( getWindow(), "Parsing warnings:", err ); } void BaseModel::reportError( const NifItem * item, const QString & err ) const @@ -832,6 +839,16 @@ void BaseModel::reportError( const NifItem * item, const QString & funcName, con reportError( QString("%1, %2: %3").arg( itemRepr( item ), funcName, err ) ); } +void BaseModel::reportError( const QModelIndex & index, const QString & err ) const +{ + reportError( indexRepr( index ) + ": " + err ); +} + +void BaseModel::reportError( const QModelIndex & index, const QString & funcName, const QString & err ) const +{ + reportError( QString("%1, %2: %3").arg( indexRepr( index ), funcName, err ) ); +} + void BaseModel::onItemValueChange( NifItem * item ) { if ( state != Processing ) { @@ -904,10 +921,10 @@ QVariant BaseModelEval::operator()( const QVariant & v ) const // resolve reference to sibling const NifItem * sibling = model->getItem( exprItem->parent(), left ); if ( sibling ) { - if ( sibling->valueIsCount() || sibling->valueIsFloat() ) { - return QVariant( sibling->valueToCount() ); - } else if ( sibling->valueIsFileVersion() ) { - return QVariant( sibling->valueToFileVersion() ); + if ( sibling->isCount() || sibling->isFloat() ) { + return QVariant( sibling->getCountValue() ); + } else if ( sibling->isFileVersion() ) { + return QVariant( sibling->getFileVersionValue() ); // this is tricky to understand // we check whether the reference is an array // if so, we get the current item's row number (exprItem->row()) @@ -916,12 +933,12 @@ QVariant BaseModelEval::operator()( const QVariant & v ) const } else if ( sibling->childCount() > 0 ) { const NifItem * i2 = sibling->child( exprItem->row() ); - if ( i2 && i2->valueIsCount() ) - return QVariant( i2->valueToCount() ); + if ( i2 && i2->isCount() ) + return QVariant( i2->getCountValue() ); } else if ( sibling->valueType() == NifValue::tBSVertexDesc ) { return QVariant( sibling->get().GetFlags() << 4 ); } else { - model->reportError( item, QString("BaseModelEval could not convert %1 to a count" ).arg( model->itemRepr(sibling) ) ); + model->reportError( item, QString( "BaseModelEval could not convert %1 to a count." ).arg( sibling->repr() ) ); } } diff --git a/src/model/basemodel.h b/src/model/basemodel.h index f33f5a303..eaa35dd0d 100644 --- a/src/model/basemodel.h +++ b/src/model/basemodel.h @@ -125,22 +125,20 @@ class BaseModel : public QAbstractItemModel /*! Return true if the index pointed to is an array. * - * @param array The index to check. + * @param iArray The index to check. * @return true if the index is an array. */ bool isArray( const QModelIndex & iArray ) const; - /*! Return true if the item is an array. + /*! Return true if the item is an array or its parent is a multi-array. * - * @param array The item to check. + * @param item The item to check. * @return true if the index is an array. */ - bool isArray( const NifItem * item ) const; + bool isArrayEx( const NifItem * item ) const; //! Get an item as a NifValue. NifValue getValue( const QModelIndex & index ) const; - // Get an item as a NifValue by name. - //NifValue getValue( const QModelIndex & parent, const QString & name ) const; //! Set an item from a NifValue. bool setIndexValue( const QModelIndex & index, const NifValue & v ); @@ -148,7 +146,7 @@ class BaseModel : public QAbstractItemModel //! Get the item name. QString itemName( const QModelIndex & index ) const; //! Get the item type string. - QString itemType( const QModelIndex & index ) const; + QString itemStrType( const QModelIndex & index ) const; //! Get the item argument string. QString itemArg( const QModelIndex & index ) const; //! Get the item arr1 string. @@ -164,7 +162,7 @@ class BaseModel : public QAbstractItemModel //! Get the item documentation. QString itemText( const QModelIndex & index ) const; //! Get the item template string. - QString itemTmplt( const QModelIndex & index ) const; + QString itemTempl( const QModelIndex & index ) const; //! Is name a NiBlock identifier ( or )? @@ -269,15 +267,23 @@ class BaseModel : public QAbstractItemModel void logWarning( const QString & details ) const; public: - //! Return string representation ("path") of an item within the model (e.g., "Block [0]\Vertex Data [3]\Vertex colors") as a QString. + //! Return string representation ("path") of an item within its model (e.g., "NiTriShape [0]\Vertex Data [3]\Vertex colors"). // Mostly for messages and debug. QString itemRepr( const NifItem * item ) const; + + //! Return string representation ("path") of a model index within its model (e.g., "NiTriShape [0]\Vertex Data [3]\Vertex colors"). + // Mostly for messages and debug. + static QString indexRepr( const QModelIndex & index ); + protected: virtual QString topItemRepr( const NifItem * item ) const; + public: void reportError( const QString & err ) const; void reportError( const NifItem * item, const QString & err ) const; void reportError( const NifItem * item, const QString & funcName, const QString & err ) const; + void reportError( const QModelIndex & index, const QString & err ) const; + void reportError( const QModelIndex & index, const QString & funcName, const QString & err ) const; public: QModelIndex itemToIndex( const NifItem * item, int column = 0 ) const; @@ -316,6 +322,10 @@ class BaseModel : public QAbstractItemModel virtual bool evalConditionImpl( const NifItem * item ) const; // NifItem getters +protected: + const NifItem * getItemInternal( const NifItem * parent, const QString & name, bool reportErrors ) const; + const NifItem * getItemInternal( const NifItem * parent, const QLatin1String & name, bool reportErrors ) const; + public: //! Get a child NifItem from its parent and name. const NifItem * getItem( const NifItem * parent, const QString & name, bool reportErrors = false ) const; @@ -598,7 +608,7 @@ class BaseModelEval inline QModelIndex BaseModel::itemToIndex( const NifItem * item, int column ) const { - return item ? createIndex( item->row(), column, const_cast(item) ) : QModelIndex(); + return ( item && item != root ) ? createIndex( item->row(), column, const_cast(item) ) : QModelIndex(); } inline NifItem * BaseModel::getTopItem( const NifItem * item ) @@ -628,6 +638,17 @@ inline bool BaseModel::setIndexValue( const QModelIndex & index, const NifValue return setItemValue( getItem(index), val ); } +inline bool BaseModel::isArray( const QModelIndex & index ) const +{ + auto item = getItem( index ); + return item && item->isArray(); +} + +inline bool BaseModel::isArrayEx( const NifItem * item ) const +{ + return item && item->isArrayEx(); +} + // Item getters @@ -831,19 +852,19 @@ inline bool BaseModel::updateArraySize( NifItem * arrayRootItem ) } inline bool BaseModel::updateArraySize( const NifItem * arrayParent, int arrayIndex ) { - return updateArraySizeImpl( getItem(arrayParent, arrayIndex) ); + return updateArraySizeImpl( getItem(arrayParent, arrayIndex, true) ); } inline bool BaseModel::updateArraySize( const NifItem * arrayParent, const QString & arrayName ) { - return updateArraySizeImpl( getItem(arrayParent, arrayName) ); + return updateArraySizeImpl( getItem(arrayParent, arrayName, true) ); } inline bool BaseModel::updateArraySize( const NifItem * arrayParent, const QLatin1String & arrayName ) { - return updateArraySizeImpl( getItem(arrayParent, arrayName) ); + return updateArraySizeImpl( getItem(arrayParent, arrayName, true) ); } inline bool BaseModel::updateArraySize( const NifItem * arrayParent, const char * arrayName ) { - return updateArraySizeImpl( getItem(arrayParent, QLatin1String(arrayName)) ); + return updateArraySizeImpl( getItem(arrayParent, QLatin1String(arrayName), true) ); } inline bool BaseModel::updateArraySize( const QModelIndex & iArray ) { @@ -851,19 +872,19 @@ inline bool BaseModel::updateArraySize( const QModelIndex & iArray ) } inline bool BaseModel::updateArraySize( const QModelIndex & arrayParent, int arrayIndex ) { - return updateArraySizeImpl( getItem(arrayParent, arrayIndex) ); + return updateArraySizeImpl( getItem(arrayParent, arrayIndex, true) ); } inline bool BaseModel::updateArraySize( const QModelIndex & arrayParent, const QString & arrayName ) { - return updateArraySizeImpl( getItem(arrayParent, arrayName) ); + return updateArraySizeImpl( getItem(arrayParent, arrayName, true) ); } inline bool BaseModel::updateArraySize( const QModelIndex & arrayParent, const QLatin1String & arrayName ) { - return updateArraySizeImpl( getItem(arrayParent, arrayName) ); + return updateArraySizeImpl( getItem(arrayParent, arrayName, true) ); } inline bool BaseModel::updateArraySize( const QModelIndex & arrayParent, const char * arrayName ) { - return updateArraySizeImpl( getItem(arrayParent, QLatin1String(arrayName)) ); + return updateArraySizeImpl( getItem(arrayParent, QLatin1String(arrayName), true) ); } diff --git a/src/model/kfmmodel.cpp b/src/model/kfmmodel.cpp index 86241c1e0..7c4f609d3 100644 --- a/src/model/kfmmodel.cpp +++ b/src/model/kfmmodel.cpp @@ -132,7 +132,7 @@ bool KfmModel::updateArraySizeImpl( NifItem * array ) { if ( !array->isArray() ) { if ( array ) - reportError( array, "updateArraySize", "The input item is not an array." ); + reportError( array, __func__, "The input item is not an array." ); return false; } @@ -140,10 +140,10 @@ bool KfmModel::updateArraySizeImpl( NifItem * array ) int nNewSize = evalArraySize( array ); if ( nNewSize > 1024 * 1024 * 8 ) { - reportError( array, "updateArraySize", tr( "Array size %1 is much too large." ).arg( nNewSize ) ); + reportError( array, __func__, tr( "Array size %1 is much too large." ).arg( nNewSize ) ); return false; } else if ( nNewSize < 0 ) { - reportError( array, "updateArraySize", tr( "Array size %1 is invalid." ).arg( nNewSize ) ); + reportError( array, __func__, tr( "Array size %1 is invalid." ).arg( nNewSize ) ); return false; } @@ -151,9 +151,9 @@ bool KfmModel::updateArraySizeImpl( NifItem * array ) if ( nNewSize > nOldSize ) { // Add missing items NifData data( array->name(), - array->type(), - array->temp(), - NifValue( NifValue::type( array->type() ) ), + array->strType(), + array->templ(), + NifValue( NifValue::type( array->strType() ) ), addConditionParentPrefix( array->arg() ), addConditionParentPrefix( array->arr2() ) // arr1 in children is parent arr2 ); @@ -198,16 +198,16 @@ void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) NifItem * branch = insertBranch( parent, data, at ); branch->prepareInsert( compound->types.count() ); - if ( !data.arg().isEmpty() || !data.temp().isEmpty() ) { + if ( !data.arg().isEmpty() || !data.templ().isEmpty() ) { QString arg = addConditionParentPrefix( data.arg() ); - QString tmp = data.temp(); + QString tmp = data.templ(); if ( tmp == XMLTMPL ) { NifItem * tItem = branch; while ( tmp == XMLTMPL && tItem->parent() ) { tItem = tItem->parent(); - tmp = tItem->temp(); + tmp = tItem->templ(); } } diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 5fd65210e..71f074809 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -56,6 +56,7 @@ void NifModel::setupArrayPseudonyms() registerPseudonym("Vertex Data", "Vertex"); registerPseudonym("Vertices", "Vertex"); registerPseudonym("Triangles", "Triangle"); + registerPseudonym("Triangles Copy", "Triangle Copy"); registerPseudonym("Normals", "Normal"); registerPseudonym("Tangents", "Tangent"); registerPseudonym("Bitangents", "Bitangent"); @@ -76,6 +77,20 @@ void NifModel::setupArrayPseudonyms() registerPseudonym("Bone Weights", "Bone Weight"); registerPseudonym("Vertex Weights", "Vertex Weight"); registerPseudonym("Bone Indices", "Bone Index"); + registerPseudonym("Vertex Indices", "Vertex Index"); + registerPseudonym("Match Groups", "Match Group"); + registerPseudonym("Strips", "Strip"); + registerPseudonym("Strip Lengths", "Strip Length"); + registerPseudonym("Properties", "Property"); + registerPseudonym("Connect Points", "Connect Point"); + registerPseudonym("Scales", "Scale"); + registerPseudonym("Texture Arrays", "Texture Array"); + registerPseudonym("Block Types", "Block Type"); + registerPseudonym("Groups", "Group"); + registerPseudonym("Segment Starts", "Segment Start"); + registerPseudonym("Cut Offsets", "Cut Offset"); + registerPseudonym("Regions", "Region"); + registerPseudonym("Component Formats", "Component Format"); } //! @file nifmodel.cpp The NIF data model. @@ -208,7 +223,9 @@ void NifModel::clear() NifItem * header = getHeaderItem(); - NifItem::valueFromFileVersion( getItem( header, "Version" ), version ); + auto headerVer = getItem( header, "Version" ); + if ( headerVer ) + headerVer->setFileVersionValue( version ); QString header_string( ( version <= 0x0A000100 ) ? "NetImmerse File Format, Version " : "Gamebryo File Format, Version " ); header_string += version2string( version ); @@ -384,8 +401,11 @@ void NifModel::updateFooter() if ( itemRoots ) { set( footer, "Num Roots", rootLinks.count() ); updateArraySize( itemRoots ); - for ( int r = 0; r < itemRoots->childCount(); r++ ) - NifItem::valueFromLink( itemRoots->child( r ), rootLinks.value( r ) ); + for ( int r = 0; r < itemRoots->childCount(); r++ ) { + auto child = itemRoots->child( r ); + if ( child ) + child->setLinkValue( rootLinks.value( r ) ); + } } } @@ -395,9 +415,9 @@ void NifModel::updateFooter() bool NifModel::updateArraySizeImpl( NifItem * array ) { - if ( !isArray( array ) ) { + if ( !isArrayEx( array ) ) { if ( array ) - reportError( array, "updateArraySize", "The input item is not an array." ); + reportError( array, __func__, "The input item is not an array." ); return false; } @@ -410,10 +430,10 @@ bool NifModel::updateArraySizeImpl( NifItem * array ) int nNewSize = evalArraySize( array ); if ( nNewSize > 1024 * 1024 * 8 ) { - reportError( array, "updateArraySize", tr( "Array size %1 is much too large." ).arg( nNewSize ) ); + reportError( array, __func__, tr( "Array size %1 is much too large." ).arg( nNewSize ) ); return false; } else if ( nNewSize < 0 ) { - reportError( array, "updateArraySize", tr( "Array size %1 is invalid." ).arg( nNewSize ) ); + reportError( array, __func__, tr( "Array size %1 is invalid." ).arg( nNewSize ) ); return false; } @@ -421,9 +441,9 @@ bool NifModel::updateArraySizeImpl( NifItem * array ) if ( nNewSize > nOldSize ) { // Add missing items NifData data( array->name(), - array->type(), - array->temp(), - NifValue( NifValue::type( array->type() ) ), + array->strType(), + array->templ(), + NifValue( NifValue::type( array->strType() ) ), addConditionParentPrefix( array->arg() ), addConditionParentPrefix( array->arr2() ) // arr1 in children is parent arr2 ); @@ -448,7 +468,7 @@ bool NifModel::updateArraySizeImpl( NifItem * array ) if ( nNewSize != nOldSize && state != Loading - && ( isCompound(array->type()) || NifValue::isLink(NifValue::type(array->type())) ) + && ( isCompound(array->strType()) || NifValue::isLink(NifValue::type(array->strType())) ) && getTopItem( array ) != getFooterItem() ) { updateLinks(); @@ -491,7 +511,7 @@ bool NifModel::updateByteArraySize( NifItem * array ) // Create the dummy row for holding the byte array if ( nOldRows == 0 ) { - NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::tBlob ), addConditionParentPrefix( array->arg() ) ); + NifData data( array->name(), array->strType(), array->templ(), NifValue( NifValue::tBlob ), addConditionParentPrefix( array->arg() ) ); data.setBinary( true ); beginInsertRows( itemToIndex(array), 0, 1 ); @@ -522,7 +542,7 @@ bool NifModel::updateChildArraySizes( NifItem * parent ) for ( auto child : parent->childIter() ) { if ( evalCondition( child ) ) { - if ( isArray(child) ) { + if ( child->isArrayEx() ) { if ( !updateArraySize(child) ) return false; } @@ -596,7 +616,7 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at ) void NifModel::removeNiBlock( int blocknum ) { - if ( blocknum < 0 || blocknum >= getBlockCount() ) + if ( !isValidBlockNumber( blocknum ) ) return; adjustLinks( root, blocknum, 0 ); @@ -611,7 +631,7 @@ void NifModel::removeNiBlock( int blocknum ) void NifModel::moveNiBlock( int src, int dst ) { - if ( src < 0 || src >= getBlockCount() ) + if ( !isValidBlockNumber( src ) ) return; beginRemoveRows( QModelIndex(), src + 1, src + 1 ); @@ -651,7 +671,7 @@ void NifModel::updateStrings( NifModel * src, NifModel * tgt, NifItem * item ) if ( !item ) return; - if ( item->valueType() == NifValue::tStringIndex || item->valueType() == NifValue::tSizedString || item->hasType("string") ) { + if ( item->hasValueType(NifValue::tStringIndex) || item->hasValueType(NifValue::tSizedString) || item->hasStrType("string") ) { QString str = src->resolveString( item ); tgt->assignString( tgt->createIndex( 0, 0, item ), str, false ); } @@ -718,7 +738,7 @@ void NifModel::reorderBlocks( const QVector & order ) QMap blockMap; for ( qint32 n = 0; n < order.count(); n++ ) { - if ( blockMap.contains( order[n] ) || order[n] < 0 || order[n] >= getBlockCount() ) { + if ( blockMap.contains( order[n] ) || !isValidBlockNumber( order[n] ) ) { logMessage(tr("Reorder Blocks error"), err, QMessageBox::Critical); return; } @@ -771,7 +791,7 @@ int NifModel::getBlockNumber( const NifItem * item ) const const NifItem * block = getTopItem( item ); if ( block ) { int iRow = block->row(); - if ( iRow >= firstBlockRow() && iRow <= lastBlockRow() ) + if ( isBlockRow( iRow ) ) return iRow - firstBlockRow(); } @@ -812,7 +832,7 @@ const NifItem * NifModel::_getBlockItem( const NifItem * block, const QStringLis const NifItem * NifModel::getBlockItem( qint32 link ) const { - if ( link >= 0 && link < getBlockCount() ) + if ( isValidBlockNumber( link ) ) return root->child( link + firstBlockRow() ); return nullptr; @@ -824,17 +844,6 @@ const NifItem * NifModel::getBlockItem( const NifItem * item ) const return isNiBlock(block) ? block : nullptr; } -bool NifModel::isNiBlock( const NifItem * item ) const -{ - if ( item && item->parent() == root ) { - int iRow = item->row(); - if ( iRow >= firstBlockRow() && iRow <= lastBlockRow() ) - return true; - } - - return false; -} - bool NifModel::isNiBlock( const NifItem * item, const std::initializer_list & testTypes ) const { if ( isNiBlock(item) ) { @@ -977,12 +986,12 @@ void NifModel::insertType( NifItem * parent, const NifData & data, int at ) insertType( parent, d ); } } else if ( data.isTemplated() ) { - QString tmp = parent->temp(); + QString tmp = parent->templ(); NifItem * tItem = parent; while ( tmp == XMLTMPL && tItem->parent() ) { tItem = tItem->parent(); - tmp = tItem->temp(); + tmp = tItem->templ(); } NifData d( data ); @@ -994,8 +1003,8 @@ void NifModel::insertType( NifItem * parent, const NifData & data, int at ) d.setTemplated( false ); } - if ( d.temp() == XMLTMPL ) - d.setTemp( tmp ); + if ( d.templ() == XMLTMPL ) + d.setTempl( tmp ); insertType( parent, d, at ); } else { @@ -1042,13 +1051,13 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const if ( ndr ) return iname; - if ( item->hasType("NiBlock") ) + if ( item->hasStrType("NiBlock") ) return QString::number( getBlockNumber(item) ) + " " + iname; - else if ( isArray( item->parent() ) ) { + else if ( isArrayEx( item->parent() ) ) { auto arrayName = arrayPseudonyms.value(iname); if ( arrayName.isEmpty() ) { if ( item->hasName("UV Sets") ) - arrayName = QString( item->valueIsVector2() ? "UV" : "UV Set" ); + arrayName = QString( item->isVector2() ? "UV" : "UV Set" ); else arrayName = iname; } @@ -1060,15 +1069,15 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const break; case TypeCol: { - if ( !item->temp().isEmpty() ) { + if ( !item->templ().isEmpty() ) { const NifItem * tempItem = item; - while ( tempItem && tempItem->temp() == XMLTMPL ) + while ( tempItem && tempItem->templ() == XMLTMPL ) tempItem = tempItem->parent(); - return QString( "%1<%2>" ).arg( item->type(), tempItem ? tempItem->temp() : QString() ); + return QString( "%1<%2>" ).arg( item->strType(), tempItem ? tempItem->templ() : QString() ); } - return item->type(); + return item->strType(); } break; case ValueCol: @@ -1130,8 +1139,8 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const return QString( "%2 [%1]" ).arg( iBlock ).arg( itemBlockEntry->get() ); - } else if ( item->valueIsLink() ) { - int link = item->valueToLink(); + } else if ( item->isLink() ) { + int link = item->getLinkValue(); if ( link < 0 ) return tr( "None" ); if ( link >= getBlockCount() ) @@ -1147,18 +1156,18 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const return tr( "%1 [%2]" ).arg( link ).arg( block->name() ); - } else if ( item->valueIsCount() ) { - if ( item->hasType("BSVertexDesc") ) + } else if ( item->isCount() ) { + if ( item->hasStrType("BSVertexDesc") ) return item->get().toString(); - QString optId = NifValue::enumOptionName( item->type(), item->valueToCount() ); + QString optId = NifValue::enumOptionName( item->strType(), item->getCountValue() ); if ( optId.isEmpty() ) - return item->valueToString(); + return item->getValueAsString(); return optId; } - return item->valueToString().replace( "\n", " " ).replace( "\r", " " ); + return item->getValueAsString().replace( "\n", " " ).replace( "\r", " " ); } break; case ArgCol: @@ -1184,7 +1193,7 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const switch ( column ) { case NameCol: // (QColor, QIcon or QPixmap) as stated in the docs - /*if ( itemType( index ) == "NiBlock" ) + /*if ( itemStrType( index ) == "NiBlock" ) return QString::number( getBlockNumber( index ) );*/ return QVariant(); default: @@ -1197,12 +1206,12 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const case NameCol: return item->name(); case TypeCol: - return item->type(); + return item->strType(); case ValueCol: { - if ( item->valueType() == NifValue::tString || item->valueType() == NifValue::tFilePath ) + if ( item->hasValueType(NifValue::tString) || item->hasValueType(NifValue::tFilePath) ) return resolveString( item ); - return item->valueToVariant(); + return item->getValueAsVariant(); } case ArgCol: return item->arg(); @@ -1227,7 +1236,7 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const switch ( column ) { case NameCol: { - if ( item->parent() && isArray( item->parent() ) ) { + if ( isArrayEx( item->parent() ) ) { return QString(); } else { QString tip = QString( "

%1

%2

" ) @@ -1250,7 +1259,7 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const } break; case TypeCol: - return NifValue::typeDescription( item->type() ); + return NifValue::typeDescription( item->strType() ); case ValueCol: { switch ( item->valueType() ) { @@ -1263,20 +1272,20 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const case NifValue::tULittle32: { return tr( "dec: %1\nhex: 0x%2" ) - .arg( item->valueToString() ) - .arg( item->valueToCount(), 8, 16, QChar( '0' ) ); + .arg( item->getValueAsString() ) + .arg( item->getCountValue(), 8, 16, QChar( '0' ) ); } case NifValue::tFloat: case NifValue::tHfloat: case NifValue::tNormbyte: { return tr( "float: %1\nhex: 0x%2" ) - .arg( NumOrMinMax( item->valueToFloat(), 'g', 8 ) ) - .arg( item->valueToCount(), 8, 16, QChar( '0' ) ); + .arg( NumOrMinMax( item->getFloatValue(), 'g', 8 ) ) + .arg( item->getCountValue(), 8, 16, QChar( '0' ) ); } case NifValue::tFlags: { - quint16 f = item->valueToCount(); + quint16 f = item->getCountValue(); return tr( "dec: %1\nhex: 0x%2\nbin: 0b%3" ) .arg( f ) .arg( f, 4, 16, QChar( '0' ) ) @@ -1284,10 +1293,10 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const } case NifValue::tStringIndex: return QString( "0x%1" ) - .arg( item->valueToCount(), 8, 16, QChar( '0' ) ); + .arg( item->getCountValue(), 8, 16, QChar( '0' ) ); case NifValue::tStringOffset: return QString( "0x%1" ) - .arg( item->valueToCount(), 8, 16, QChar( '0' ) ); + .arg( item->getCountValue(), 8, 16, QChar( '0' ) ); case NifValue::tVector3: return item->get().toHtml(); case NifValue::tHalfVector3: @@ -1343,7 +1352,7 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const { // "notify" about an invalid index in "Triangles" // TODO: checkbox, "show invalid only" - if ( column == ValueCol && item->valueIsTriangle() ) { + if ( column == ValueCol && item->isTriangle() ) { const NifItem * nv = findItemX( item, "Num Vertices" ); if ( !nv ) { @@ -1351,13 +1360,13 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const return QVariant(); } - quint32 nvc = nv->valueToCount(); + quint32 nvc = nv->getCountValue(); Triangle t = item->get(); if ( t[0] >= nvc || t[1] >= nvc || t[2] >= nvc ) return QColor::fromRgb( 240, 210, 210 ); - } else if ( column == ValueCol && item->valueIsColor() ) { - return item->valueToColor(); + } else if ( column == ValueCol && item->isColor() ) { + return item->getColorValue(); } } return QVariant(); @@ -1398,15 +1407,15 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r break; case NifModel::TypeCol: - item->setType( value.toString() ); + item->setStrType( value.toString() ); break; case NifModel::ValueCol: { - if ( item->valueType() == NifValue::tString || item->valueType() == NifValue::tFilePath ) { - item->valueChangeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); + if ( item->hasValueType(NifValue::tString) || item->hasValueType(NifValue::tFilePath) ) { + item->changeValueType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); assignString( item, value.toString(), true ); } else { - item->valueFromVariant( value ); + item->setValueFromVariant( value ); } } break; @@ -1440,7 +1449,7 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r NifItem * parent = item->parent(); if ( parent && ( parent->hasName("Texture Source") || parent->hasName("NiImage") ) ) { parent = parent->parent(); - if ( parent && parent->hasType("NiBlock") && parent->hasName("NiSourceTexture") ) { + if ( parent && parent->hasStrType("NiBlock") && parent->hasName("NiSourceTexture") ) { QModelIndex pidx = itemToIndex( parent, ValueCol ); emit dataChanged( pidx, pidx ); } @@ -1448,7 +1457,7 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r } else if ( item->hasName("Name") ) { NifItem * parent = item->parent(); - if ( parent && parent->hasType("NiBlock") ) { + if ( parent && parent->hasStrType("NiBlock") ) { QModelIndex pidx = itemToIndex( parent, ValueCol ); emit dataChanged( pidx, pidx ); } @@ -1510,7 +1519,7 @@ QModelIndex NifModel::buddy( const QModelIndex & index ) const if ( !item ) return QModelIndex(); - if ( index.column() == ValueCol && item->parent() == root && item->hasType("NiBlock") ) { + if ( index.column() == ValueCol && item->parent() == root && item->hasStrType("NiBlock") ) { QModelIndex buddy; if ( item->hasName("NiSourceTexture") || item->hasName("NiImage") ) { @@ -1528,7 +1537,7 @@ QModelIndex NifModel::buddy( const QModelIndex & index ) const return buddy; } else if ( index.column() == ValueCol && item->parent() != root ) { - if ( item->hasType("ControlledBlock") && item->hasName("Controlled Blocks") ) { + if ( item->hasStrType("ControlledBlock") && item->hasName("Controlled Blocks") ) { QModelIndex buddy; if ( version >= 0x14010003 ) { @@ -1617,7 +1626,7 @@ bool NifModel::load( QIODevice & device ) // read header NifItem * header = getHeaderItem(); - if ( !header || !loadHeader( header, stream ) ) { + if ( !loadHeader( header, stream ) ) { auto m = tr( "Failed to load file header (version %1, %2)" ).arg( version, 0, 16 ).arg( version2string( version ) ); logMessage(tr(readFail), m, QMessageBox::Critical); @@ -1855,7 +1864,7 @@ bool NifModel::save( QIODevice & device ) const //qDebug() << "saving block " << c << ": " << itemName( index( c, 0 ) ); - if ( itemType( index( c, 0 ) ) == "NiBlock" ) { + if ( itemStrType( index( c, 0 ) ) == "NiBlock" ) { if ( version > 0x0a000000 ) { if ( version < 0x0a020000 ) { int null = 0; @@ -1949,7 +1958,7 @@ bool NifModel::loadHeaderOnly( const QString & fname ) // read header NifItem * header = getHeaderItem(); - if ( !header || !loadHeader( header, stream ) ) { + if ( !loadHeader( header, stream ) ) { logMessage(tr(readFail), tr("Failed to load file header version %1").arg(version), QMessageBox::Critical); return false; } @@ -2011,7 +2020,7 @@ int NifModel::fileOffset( const QModelIndex & index ) const for ( int c = 0; c < root->childCount(); c++ ) { const NifItem * block = root->child( c ); - if ( c >= firstBlockRow() && c <= lastBlockRow() ) { + if ( isBlockRow( c ) ) { if ( version > 0x0a000000 ) { if ( version < 0x0a020000 ) ofs += 4; @@ -2058,14 +2067,14 @@ int NifModel::blockSize( const NifItem * item, NifSStream & stream ) const } if ( evalCondition( child ) ) { - if ( isArray( child ) || !child->arr2().isEmpty() || child->childCount() > 0 ) { - if ( isArray( child ) && !child->isBinary() ) { + if ( child->isArrayEx() || !child->arr2().isEmpty() || child->childCount() > 0 ) { + if ( child->isArrayEx() && !child->isBinary() ) { int nRealSize = child->childCount(); int nCalcSize = evalArraySize( child ); if ( nRealSize != nCalcSize ) { reportError( child, - "blockSize", + __func__, tr( "The array's size (%1) does not match its calculated size (%2)." ).arg( nRealSize ).arg( nCalcSize ) ); } @@ -2108,7 +2117,7 @@ bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) } if ( evalCondition( child ) ) { - if ( isArray( child ) ) { + if ( child->isArrayEx() ) { if ( !updateArraySize( child ) ) return false; if ( !loadItem( child, stream ) ) @@ -2150,8 +2159,30 @@ bool NifModel::loadHeader( NifItem * header, NifIStream & stream ) stream.reset(); bsVersion = 0; - set( header, "User Version", 0 ); - set( header, "BS Header\\BS Version", 0 ); + + // Reset User Version and BS Header\BS Version of the header without condition evaluation and + // assuming that there could be muliple children with those names (who knows what will happen to nif.xml). + bool bFoundUserVersion = false; + bool bFoundBSVersion = false; + for ( auto child : header->childIter() ) { + if ( child->hasName("User Version") ) { + child->set( 0 ); + bFoundUserVersion = true; + } else if ( child->hasName("BS Header") ) { + for ( auto subChild : child->childIter() ) { + if ( subChild->hasName("BS Version") ) { + subChild->set( 0 ); + bFoundBSVersion = true; + } + } + } + } + + if ( !bFoundUserVersion ) + reportError(header, "Could not find \"User Version\" subitem." ); + if ( !bFoundBSVersion ) + reportError(header, "Could not find \"BS Header\\BS Version\" subitem." ); + invalidateItemConditions( header ); bool result = loadItem(header, stream); cacheBSVersion( header ); @@ -2173,8 +2204,8 @@ bool NifModel::saveItem( const NifItem * parent, NifOStream & stream ) const } if ( evalCondition( child ) ) { - if ( isArray( child ) || !child->arr2().isEmpty() || child->childCount() > 0 ) { - if ( isArray( child ) && child->childCount() != evalArraySize( child ) ) { + if ( child->isArrayEx() || !child->arr2().isEmpty() || child->childCount() > 0 ) { + if ( child->isArrayEx() && child->childCount() != evalArraySize( child ) ) { if ( child->isBinary() ) { // special byte } else { @@ -2216,7 +2247,7 @@ bool NifModel::fileOffset( const NifItem * parent, const NifItem * target, NifSS return true; if ( evalCondition( child ) ) { - if ( isArray( child ) || !child->arr2().isEmpty() || child->childCount() > 0 ) { + if ( child->isArrayEx() || !child->arr2().isEmpty() || child->childCount() > 0 ) { if ( fileOffset( child, target, stream, ofs ) ) return true; } else { @@ -2240,7 +2271,7 @@ const NifItem * NifModel::getConditionCacheItem( const NifItem * item ) const const NifItem * compoundStruct = item->parent(); if ( compoundStruct ) { const NifItem * compoundArray = compoundStruct->parent(); - if ( isArray(compoundArray) && compoundStruct->row() > 0 && isFixedCompound( compoundStruct->type() ) ) { + if ( compoundArray->isArrayEx() && compoundStruct->row() > 0 && isFixedCompound( compoundStruct->strType() ) ) { const NifItem * refStruct = compoundArray->child( 0 ); if ( !refStruct ) // Just in case... return nullptr; @@ -2288,7 +2319,7 @@ void NifModel::invalidateDependentConditions( NifItem * item ) return; NifItem * p = item->parent(); - if ( !p || p == root || isArray(p) ) + if ( !p || p == root || p->isArrayEx() ) return; const QString & name = item->name(); @@ -2301,7 +2332,7 @@ void NifModel::invalidateDependentConditions( NifItem * item ) // Note: May cause some false positives but this is OK if ( c->cond().contains(name) || c->arg().contains(name) - || ( c->childCount() > 0 && !isArray(c) ) // If it has children but is not an array, let's reset conditions just to be safe. + || ( c->childCount() > 0 && !c->isArrayEx() ) // If it has children but is not an array, let's reset conditions just to be safe. ) { c->invalidateCondition(); } @@ -2385,7 +2416,7 @@ void NifModel::updateLinks( int block, NifItem * parent ) continue; } - int i = c->valueToLink(); + int i = c->getLinkValue(); if ( i >= 0 ) { if ( c->valueType() == NifValue::tUpLink ) { if ( !parentLinks[block].contains( i ) ) @@ -2429,13 +2460,13 @@ void NifModel::adjustLinks( NifItem * parent, int block, int delta ) for ( auto child : parent->children() ) adjustLinks( child, block, delta ); } else { - int l = parent->valueToLink(); + int l = parent->getLinkValue(); if ( l >= 0 && ( ( delta != 0 && l >= block ) || l == block ) ) { if ( delta == 0 ) - parent->valueFromLink( -1 ); + parent->setLinkValue( -1 ); else - parent->valueFromLink( l + delta ); + parent->setLinkValue( l + delta ); } } } @@ -2449,18 +2480,18 @@ void NifModel::mapLinks( NifItem * parent, const QMap & map ) for ( auto child : parent->children() ) mapLinks( child, map ); } else { - int l = parent->valueToLink(); + int l = parent->getLinkValue(); if ( l >= 0 ) { if ( map.contains( l ) ) - parent->valueFromLink( map[ l ] ); + parent->setLinkValue( map[ l ] ); } } } bool NifModel::setLink( NifItem * item, qint32 link ) { - if ( NifItem::valueFromLink( item, link) ) { + if ( item && item->setLinkValue(link) ) { onItemValueChange( item ); return true; } @@ -2472,16 +2503,16 @@ QVector NifModel::getLinkArray( const NifItem * arrayRootItem ) const { QVector links; - if ( isArray( arrayRootItem ) ) { + if ( isArrayEx(arrayRootItem) ) { int nLinks = arrayRootItem->childCount(); if ( nLinks > 0 ) { links.reserve( nLinks ); for ( auto child : arrayRootItem->childIter() ) - links.append( child->valueToLink() ); + links.append( child->getLinkValue() ); } } else { if ( arrayRootItem ) - reportError( arrayRootItem, "getLinkArray", "The item is not an array." ); + reportError( arrayRootItem, __func__, "The item is not an array." ); } return links; @@ -2489,9 +2520,9 @@ QVector NifModel::getLinkArray( const NifItem * arrayRootItem ) const bool NifModel::setLinkArray( NifItem * arrayRootItem, const QVector & links ) { - if ( !isArray(arrayRootItem) ) { + if ( !isArrayEx(arrayRootItem) ) { if ( arrayRootItem ) - reportError( arrayRootItem, "setLinkArray", "The item is not an array." ); + reportError( arrayRootItem, __func__, "The item is not an array." ); return false; } @@ -2499,7 +2530,7 @@ bool NifModel::setLinkArray( NifItem * arrayRootItem, const QVector & li if ( links.count() != nLinks ) { reportError( arrayRootItem, - "setLinkArray", + __func__, tr( "The input QVector's size (%1) does not match the array's size (%2)." ).arg( links.count() ).arg( nLinks ) ); return false; @@ -2507,8 +2538,11 @@ bool NifModel::setLinkArray( NifItem * arrayRootItem, const QVector & li if ( nLinks == 0 ) return true; - for ( int i = 0; i < nLinks; i++ ) - NifItem::valueFromLink( arrayRootItem->child( i ), links.at( i ) ); + for ( int i = 0; i < nLinks; i++ ) { + auto child = arrayRootItem->child( i ); + if ( child ) + child->setLinkValue( links.at( i ) ); + } onArrayValuesChange( arrayRootItem ); @@ -2546,7 +2580,7 @@ QString NifModel::resolveString( const NifItem * item ) const if ( !item ) return QString(); - if ( item->valueIsString() ) + if ( item->isString() ) return item->get(); if ( getVersionNumber() >= 0x14010003 ) { @@ -2559,9 +2593,9 @@ QString NifModel::resolveString( const NifItem * item ) const if ( itemIndex ) iStrIndex = itemIndex->get(); else - reportError( item, "resolveString", "Could not find \"Index\" subitem." ); + reportError( item, __func__, "Could not find \"Index\" subitem." ); } else { - reportError( item, "resolveString", tr( "Unsupported value type (%1)." ).arg( int(vt) ) ); + reportError( item, __func__, tr( "Unsupported value type (%1)." ).arg( int(vt) ) ); } if ( iStrIndex < 0 ) @@ -2578,9 +2612,9 @@ QString NifModel::resolveString( const NifItem * item ) const if ( itemString ) return itemString->get(); - reportError( item, "resolveString", "Could not find \"String\" subitem."); + reportError( item, __func__, "Could not find \"String\" subitem." ); } else { - item->value().reportConvertToError( this, item, "a QString"); + item->value().reportConvertToError( this, item, "a QString" ); } return QString(); @@ -2599,7 +2633,7 @@ bool NifModel::assignString( NifItem * item, const QString & string, bool replac case NifValue::tNone: itemIndex = getItem( item, "Index" ); if ( !itemIndex ) { - reportError( item, "assignString", "Could not find \"Index\" subitem." ); + reportError( item, __func__, "Could not find \"Index\" subitem." ); return false; } iOldStrIndex = itemIndex->get(); @@ -2609,7 +2643,7 @@ bool NifModel::assignString( NifItem * item, const QString & string, bool replac iOldStrIndex = itemIndex->get(); break; case NifValue::tSizedString: - if ( item->hasType("string") ) { + if ( item->hasStrType("string") ) { itemIndex = item; iOldStrIndex = -1; break; @@ -2630,7 +2664,7 @@ bool NifModel::assignString( NifItem * item, const QString & string, bool replac // TODO: Can we remove the string safely here? } - itemIndex->valueChangeType( NifValue::tStringIndex ); + itemIndex->changeValueType( NifValue::tStringIndex ); return set( itemIndex, 0xffffffff ); } @@ -2655,7 +2689,7 @@ bool NifModel::assignString( NifItem * item, const QString & string, bool replac BaseModel::set( headerStrings, nHeaderStrings, string ); } - itemIndex->valueChangeType( NifValue::tStringIndex ); + itemIndex->changeValueType( NifValue::tStringIndex ); return set( itemIndex, iNewStrIndex ); } // endif getVersionNumber() >= 0x14010003 @@ -2663,7 +2697,7 @@ bool NifModel::assignString( NifItem * item, const QString & string, bool replac if ( item->valueType() == NifValue::tNone ) { NifItem * itemString = getItem( item, "String" ); if ( !itemString ) { - reportError( item, "assignString", "Could not find \"String\" subitem."); + reportError( item, __func__, "Could not find \"String\" subitem." ); return false; } return BaseModel::set( itemString, string ); @@ -2797,7 +2831,7 @@ bool NifModel::testSkipIO( const NifItem * item ) const // Be advised, getBSVersion returns 0 if it's the file's header that is being loaded. // Though for shader properties loadItem happens after the header is fully processed, so the check below should work w/o issues. if ( getBSVersion() >= 151 && item->parent() == root ) { - if (item->hasName("BSLightingShaderProperty") || item->hasName("BSEffectShaderProperty") ) + if ( item->hasName("BSLightingShaderProperty") || item->hasName("BSEffectShaderProperty") ) testSkip = true; } return testSkip; @@ -2811,8 +2845,8 @@ void NifModel::cacheBSVersion( const NifItem * headerItem ) QString NifModel::topItemRepr( const NifItem * item ) const { int iRow = item->row(); - if ( iRow >= firstBlockRow() && iRow <= lastBlockRow() ) - return QString("%2 [%1]").arg( iRow - 1 ).arg( item->name() ); + if ( isBlockRow( iRow ) ) + return QString("%2 [%1]").arg( iRow - firstBlockRow() ).arg( item->name() ); return item->name(); } @@ -2822,7 +2856,7 @@ void NifModel::onItemValueChange( NifItem * item ) invalidateDependentConditions( item ); BaseModel::onItemValueChange( item ); - if ( item->valueIsLink() ) { + if ( item->isLink() ) { auto block = getTopItem( item ); if ( block && block != getFooterItem() ) { updateLinks(); @@ -2850,10 +2884,10 @@ QVariant NifModelEval::operator()( const QVariant & v ) const const NifItem * itemLeft = model->getItem( item, left, true ); if ( itemLeft ) { - if ( itemLeft->valueIsCount() ) - return QVariant( itemLeft->valueToCount() ); - else if ( itemLeft->valueIsFileVersion() ) - return QVariant( itemLeft->valueToFileVersion() ); + if ( itemLeft->isCount() ) + return QVariant( itemLeft->getCountValue() ); + else if ( itemLeft->isFileVersion() ) + return QVariant( itemLeft->getFileVersionValue() ); } return QVariant( 0 ); diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index dfa89b9b1..59f49dd48 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -217,6 +217,7 @@ class NifModel final : public BaseModel protected: constexpr int firstBlockRow() const; int lastBlockRow() const; + bool isBlockRow( int row ) const; public: //! Get the number of NiBlocks @@ -228,6 +229,9 @@ class NifModel final : public BaseModel //! Get the numerical index (or link) of the block an item belongs to. // Return -1 if the item is the root or header or footer or null. int getBlockNumber( const QModelIndex & index ) const; + + // Checks if blockNum is a valid block number. + bool isValidBlockNumber( qint32 blockNum ) const; //! Insert or append ( row == -1 ) a new NiBlock QModelIndex insertNiBlock( const QString & identifier, int row = -1 ); @@ -835,7 +839,7 @@ inline QList NifModel::getParentLinks( int block ) const inline bool NifModel::isLink( const NifItem * item ) const { - return item ? item->valueIsLink() : false; + return item && item->isLink(); } inline bool NifModel::isLink( const QModelIndex & index ) const @@ -858,6 +862,11 @@ inline int NifModel::lastBlockRow() const return root->childCount() - 2; // The last root's child is always the footer. } +inline bool NifModel::isBlockRow( int row ) const +{ + return ( row >= firstBlockRow() && row <= lastBlockRow() ); +} + inline int NifModel::getBlockCount() const { return std::max( lastBlockRow() - firstBlockRow() + 1, 0 ); @@ -868,6 +877,11 @@ inline int NifModel::getBlockNumber( const QModelIndex & index ) const return getBlockNumber( getItem(index) ); } +inline bool NifModel::isValidBlockNumber( qint32 blockNum ) const +{ + return blockNum >= 0 && blockNum < getBlockCount(); +} + // Block item getters @@ -1100,6 +1114,10 @@ inline QModelIndex NifModel::getBlockIndex( const QModelIndex & index, const QSt // isNiBlock +inline bool NifModel::isNiBlock( const NifItem * item ) const +{ + return item && item->parent() == root && isBlockRow( item->row() ); +} inline bool NifModel::isNiBlock( const NifItem * item, const QString & testType ) const { return isNiBlock(item) && item->hasName(testType); @@ -1350,7 +1368,7 @@ inline bool NifModel::assignString( const QModelIndex & itemParent, const char * inline qint32 NifModel::getLink( const NifItem * item ) const { - return NifItem::valueToLink( item ); + return item ? item->getLinkValue() : -1; } inline qint32 NifModel::getLink( const NifItem * itemParent, int itemIndex ) const { @@ -1394,19 +1412,19 @@ inline qint32 NifModel::getLink( const QModelIndex & itemParent, const char * it inline bool NifModel::setLink( const NifItem * itemParent, int itemIndex, qint32 link ) { - return setLink( getItem(itemParent, itemIndex), link ); + return setLink( getItem(itemParent, itemIndex, true), link ); } inline bool NifModel::setLink( const NifItem * itemParent, const QString & itemName, qint32 link ) { - return setLink( getItem(itemParent, itemName), link ); + return setLink( getItem(itemParent, itemName, true), link ); } inline bool NifModel::setLink( const NifItem * itemParent, const QLatin1String & itemName, qint32 link ) { - return setLink( getItem(itemParent, itemName), link ); + return setLink( getItem(itemParent, itemName, true), link ); } inline bool NifModel::setLink( const NifItem * itemParent, const char * itemName, qint32 link ) { - return setLink( getItem(itemParent, QLatin1String(itemName)), link ); + return setLink( getItem(itemParent, QLatin1String(itemName), true), link ); } inline bool NifModel::setLink( const QModelIndex & index, qint32 link ) { @@ -1414,19 +1432,19 @@ inline bool NifModel::setLink( const QModelIndex & index, qint32 link ) } inline bool NifModel::setLink( const QModelIndex & itemParent, int itemIndex, qint32 link ) { - return setLink( getItem(itemParent, itemIndex), link ); + return setLink( getItem(itemParent, itemIndex, true), link ); } inline bool NifModel::setLink( const QModelIndex & itemParent, const QString & itemName, qint32 link ) { - return setLink( getItem(itemParent, itemName), link ); + return setLink( getItem(itemParent, itemName, true), link ); } inline bool NifModel::setLink( const QModelIndex & itemParent, const QLatin1String & itemName, qint32 link ) { - return setLink( getItem(itemParent, itemName), link ); + return setLink( getItem(itemParent, itemName, true), link ); } inline bool NifModel::setLink( const QModelIndex & itemParent, const char * itemName, qint32 link ) { - return setLink( getItem(itemParent, QLatin1String(itemName)), link ); + return setLink( getItem(itemParent, QLatin1String(itemName), true), link ); } @@ -1474,19 +1492,19 @@ inline QVector NifModel::getLinkArray( const QModelIndex & arrayParent, inline bool NifModel::setLinkArray( const NifItem * arrayParent, int arrayIndex, const QVector & links ) { - return setLinkArray( getItem(arrayParent, arrayIndex), links ); + return setLinkArray( getItem(arrayParent, arrayIndex, true), links ); } inline bool NifModel::setLinkArray( const NifItem * arrayParent, const QString & arrayName, const QVector & links ) { - return setLinkArray( getItem(arrayParent, arrayName), links ); + return setLinkArray( getItem(arrayParent, arrayName, true), links ); } inline bool NifModel::setLinkArray( const NifItem * arrayParent, const QLatin1String & arrayName, const QVector & links ) { - return setLinkArray( getItem(arrayParent, arrayName), links ); + return setLinkArray( getItem(arrayParent, arrayName, true), links ); } inline bool NifModel::setLinkArray( const NifItem * arrayParent, const char * arrayName, const QVector & links ) { - return setLinkArray( getItem(arrayParent, QLatin1String(arrayName)), links ); + return setLinkArray( getItem(arrayParent, QLatin1String(arrayName), true), links ); } inline bool NifModel::setLinkArray( const QModelIndex & iArray, const QVector & links ) { @@ -1494,19 +1512,19 @@ inline bool NifModel::setLinkArray( const QModelIndex & iArray, const QVector & links ) { - return setLinkArray( getItem(arrayParent, arrayIndex), links ); + return setLinkArray( getItem(arrayParent, arrayIndex, true), links ); } inline bool NifModel::setLinkArray( const QModelIndex & arrayParent, const QString & arrayName, const QVector & links ) { - return setLinkArray( getItem(arrayParent, arrayName), links ); + return setLinkArray( getItem(arrayParent, arrayName, true), links ); } inline bool NifModel::setLinkArray( const QModelIndex & arrayParent, const QLatin1String & arrayName, const QVector & links ) { - return setLinkArray( getItem(arrayParent, arrayName), links ); + return setLinkArray( getItem(arrayParent, arrayName, true), links ); } inline bool NifModel::setLinkArray( const QModelIndex & arrayParent, const char * arrayName, const QVector & links ) { - return setLinkArray( getItem(arrayParent, QLatin1String(arrayName)), links ); + return setLinkArray( getItem(arrayParent, QLatin1String(arrayName), true), links ); } #endif diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index 064a92cf8..9163041a9 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -374,7 +374,7 @@ bool addLink( NifModel * nif, const QModelIndex & iParent, const QString & array if ( nif->isArray( iArray ) && item ) { for ( int c = 0; c < item->childCount(); c++ ) { - if ( item->child( c )->valueToLink() == -1 ) { + if ( item->child( c )->getLinkValue() == -1 ) { nif->setLink( iArray.child( c, 0 ), link ); return true; } @@ -415,7 +415,7 @@ void delLink( NifModel * nif, const QModelIndex & iParent, QString array, int li */ void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & iBlock ) { - if ( nif->isLink( index ) && nif->blockInherits( iBlock, nif->itemTmplt( index ) ) ) { + if ( nif->isLink( index ) && nif->blockInherits( iBlock, nif->itemTempl( index ) ) ) { nif->setLink( index, nif->getBlockNumber( iBlock ) ); } @@ -711,7 +711,7 @@ class spAttachProperty final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - if ( nif->itemType( index ) != "NiBlock" ) + if ( nif->itemStrType( index ) != "NiBlock" ) return false; if ( nif->getUserVersion() < 12 ) @@ -825,7 +825,7 @@ class spAddNewRef final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { NifItem * item = static_cast(index.internalPointer()); - auto type = item->temp(); + auto type = item->templ(); std::list allIds = nif->allNiBlocks().toStdList(); blockFilter( nif, allIds, type ); diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index d13714524..73c302c87 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -65,7 +65,7 @@ class spEditFlags : public Spell if ( index == iFlags ) return iFlags; - } else if ( nif->itemName( index ) == "Flags" && nif->itemType( index.parent() ) == "TexDesc" ) { + } else if ( nif->itemName( index ) == "Flags" && nif->itemStrType( index.parent() ) == "TexDesc" ) { return index; } @@ -96,7 +96,7 @@ class spEditFlags : public Spell return Shape; } else if ( name == "NiStencilProperty" ) { return Stencil; - } else if ( nif->itemType( index.parent() ) == "TexDesc" ) { + } else if ( nif->itemStrType( index.parent() ) == "TexDesc" ) { return TexDesc; } else if ( name == "NiVertexColorProperty" ) { return VertexColor; diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index a8d61ded2..5d9cda56b 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -245,14 +245,14 @@ class spFlipTexCoords final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif->itemType( index ).toLower() == "texcoord" || nif->blockInherits( index, "NiTriBasedGeomData" ); + return nif->itemStrType( index ).toLower() == "texcoord" || nif->blockInherits( index, "NiTriBasedGeomData" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { QModelIndex idx = index; - if ( nif->itemType( index ).toLower() != "texcoord" ) { + if ( nif->itemStrType( index ).toLower() != "texcoord" ) { idx = nif->getIndex( nif->getBlockIndex( index ), "UV Sets" ); } diff --git a/src/spells/misc.cpp b/src/spells/misc.cpp index 00304e9fe..016560be6 100644 --- a/src/spells/misc.cpp +++ b/src/spells/misc.cpp @@ -179,7 +179,7 @@ class spExportBinary final : public Spell dataItem = item->child( 0 ); } - if ( dataItem && dataItem->valueIsByteArray() ) { + if ( dataItem && dataItem->isByteArray() ) { auto bytes = dataItem->get(); data.append( *bytes ); } @@ -255,7 +255,7 @@ REGISTER_SPELL( spImportBinary ) bool spCollapseArray::isApplicable( const NifModel * nif, const QModelIndex & index ) { if ( nif->isArray( index ) && index.isValid() - && ( nif->itemType( index ) == "Ref" || nif->itemType( index ) == "Ptr" ) ) + && ( nif->itemStrType( index ) == "Ref" || nif->itemStrType( index ) == "Ptr" ) ) { // copy from spUpdateArray when that changes return true; diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index 6ac9d04a2..9526da0ce 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -324,7 +324,7 @@ class spSanityCheckLinks final : public Spell qCCritical( nsSpell ) << Spell::tr( "Invalid link '%1'." ).arg( QString::number(l) ); return idx; } else { - QString tmplt = nif->itemTmplt( idx ); + QString tmplt = nif->itemTempl( idx ); if ( !tmplt.isEmpty() ) { QModelIndex iBlock = nif->getBlockIndex( l ); diff --git a/src/spells/skeleton.cpp b/src/spells/skeleton.cpp index b127330ac..ff6fb0a05 100644 --- a/src/spells/skeleton.cpp +++ b/src/spells/skeleton.cpp @@ -37,7 +37,7 @@ class spFixSkeleton final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return ( nif->getVersion() == "4.0.0.2" && nif->itemType( index ) == "NiBlock" && nif->get( index, "Name" ) == "Bip01" ); //&& QFile::exists( SKEL_DAT ) ); + return ( nif->getVersion() == "4.0.0.2" && nif->itemStrType( index ) == "NiBlock" && nif->get( index, "Name" ) == "Bip01" ); //&& QFile::exists( SKEL_DAT ) ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -199,7 +199,7 @@ class spScanSkeleton final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return ( nif->getVersion() == "4.0.0.2" && nif->itemType( index ) == "NiBlock" && nif->get( index, "Name" ) == "Bip01" ); + return ( nif->getVersion() == "4.0.0.2" && nif->itemStrType( index ) == "NiBlock" && nif->get( index, "Name" ) == "Bip01" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -1237,7 +1237,7 @@ class spMirrorSkeleton final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return ( nif->getVersion() == "4.0.0.2" && nif->itemType( index ) == "NiBlock" ) + return ( nif->getVersion() == "4.0.0.2" && nif->itemStrType( index ) == "NiBlock" ) && ( ( nif->get( index, "Name" ).startsWith( "Bip01 L" ) ) || ( nif->get( index, "Name" ).startsWith( "Bip01 R" ) ) ); } diff --git a/src/spells/transform.cpp b/src/spells/transform.cpp index 5d5cf583b..bb29de672 100644 --- a/src/spells/transform.cpp +++ b/src/spells/transform.cpp @@ -94,7 +94,7 @@ static char const * transform_xpm[] = { bool spApplyTransformation::isApplicable( const NifModel * nif, const QModelIndex & index ) { - return nif->itemType( index ) == "NiBlock" && + return nif->itemStrType( index ) == "NiBlock" && ( nif->inherits( nif->itemName( index ), "NiNode" ) || nif->inherits( nif->itemName( index ), "NiTriBasedGeom" ) || nif->inherits( nif->itemName( index ), "BSTriShape" )); diff --git a/src/ui/widgets/nifeditors.cpp b/src/ui/widgets/nifeditors.cpp index 5844eb647..3ac2f3f30 100644 --- a/src/ui/widgets/nifeditors.cpp +++ b/src/ui/widgets/nifeditors.cpp @@ -313,9 +313,9 @@ void NifVectorEdit::updateData( NifModel * dstNif ) { NifItem * item = dstNif->getItem( index ); if ( item ) { - if ( item->valueIsVector3() ) + if ( item->isVector3() ) vector->setVector3( item->get() ); - else if ( item->valueIsVector2() ) + else if ( item->isVector2() ) vector->setVector2( item->get() ); } } @@ -324,9 +324,9 @@ void NifVectorEdit::applyData( NifModel * dstNif ) { NifItem * item = dstNif->getItem( index ); if ( item ) { - if ( item->valueIsVector3() ) + if ( item->isVector3() ) item->set( vector->getVector3() ); - else if ( item->valueIsVector2() ) + else if ( item->isVector2() ) item->set( vector->getVector2() ); } } @@ -342,9 +342,9 @@ void NifRotationEdit::updateData( NifModel * dstNif ) { const NifItem * item = dstNif->getItem( index ); if ( item ) { - if ( item->valueIsMatrix() ) + if ( item->isMatrix() ) rotation->setMatrix( item->get() ); - else if ( item->valueIsQuat() ) + else if ( item->isQuat() ) rotation->setQuat( item->get() ); } } @@ -353,9 +353,9 @@ void NifRotationEdit::applyData( NifModel * dstNif ) { NifItem * item = dstNif->getItem( index ); if ( item ) { - if ( item->valueIsMatrix() ) + if ( item->isMatrix() ) item->set( rotation->getMatrix() ); - else if ( item->valueIsQuat() ) + else if ( item->isQuat() ) item->set( rotation->getQuat() ); } } diff --git a/src/ui/widgets/refrbrowser.cpp b/src/ui/widgets/refrbrowser.cpp index 5cc517bed..b8287bfe3 100644 --- a/src/ui/widgets/refrbrowser.cpp +++ b/src/ui/widgets/refrbrowser.cpp @@ -88,7 +88,7 @@ void ReferenceBrowser::browse( const QModelIndex & index ) return; } - QString blockType = nif->itemType( index ); + QString blockType = nif->itemStrType( index ); if ( blockType == "NiBlock" ) { blockType = nif->itemName( index ); diff --git a/src/ui/widgets/xmlcheck.cpp b/src/ui/widgets/xmlcheck.cpp index 76c484a0f..0910a4f5b 100644 --- a/src/ui/widgets/xmlcheck.cpp +++ b/src/ui/widgets/xmlcheck.cpp @@ -572,7 +572,7 @@ void TestThread::run() static QString linkId( const NifModel * nif, QModelIndex idx ) { - QString id = QString( "%1 (%2)" ).arg( nif->itemName( idx ), nif->itemTmplt( idx ) ); + QString id = QString( "%1 (%2)" ).arg( nif->itemName( idx ), nif->itemTempl( idx ) ); while ( idx.parent().isValid() ) { idx = idx.parent(); @@ -599,7 +599,7 @@ QList TestThread::checkLinks( const NifModel * nif, const QModelInd } else if ( l >= nif->getBlockCount() ) { messages.append( TestMessage() << tr( "invalid link" ) << linkId( nif, idx ) ); } else { - QString tmplt = nif->itemTmplt( idx ); + QString tmplt = nif->itemTempl( idx ); if ( !tmplt.isEmpty() ) { QModelIndex iBlock = nif->getBlockIndex( l ); diff --git a/src/xml/kfmxml.cpp b/src/xml/kfmxml.cpp index 573b7d108..2372ee05e 100644 --- a/src/xml/kfmxml.cpp +++ b/src/xml/kfmxml.cpp @@ -223,7 +223,7 @@ class KfmXmlHandler final : public QXmlDefaultHandler bool checkTemp( const NifData & data ) { - return data.temp().isEmpty() || NifValue::type( data.temp() ) != NifValue::tNone || data.temp() == XMLTMPL; + return data.templ().isEmpty() || NifValue::type( data.templ() ) != NifValue::tNone || data.templ() == XMLTMPL; } bool endDocument() override final @@ -236,7 +236,7 @@ class KfmXmlHandler final : public QXmlDefaultHandler err( tr( "compound type %1 referes to unknown type %2" ).arg( key, data.type() ) ); if ( !checkTemp( data ) ) - err( tr( "compound type %1 refers to unknown template type %2" ).arg( key, data.temp() ) ); + err( tr( "compound type %1 refers to unknown template type %2" ).arg( key, data.templ() ) ); if ( data.type() == key ) err( tr( "compound type %1 contains itself" ).arg( key ) ); diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 53d376cf7..ef6f5fb7e 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -642,11 +642,11 @@ class NifXmlHandler final : public QXmlDefaultHandler //! Checks that a template type is valid bool checkTemp( const NifData & d ) { - return ( d.temp().isEmpty() - || NifValue::type( d.temp() ) != NifValue::tNone - || d.temp() == XMLTMPL - || NifModel::blocks.contains( d.temp() ) - || NifModel::compounds.contains( d.temp() ) + return ( d.templ().isEmpty() + || NifValue::type( d.templ() ) != NifValue::tNone + || d.templ() == XMLTMPL + || NifModel::blocks.contains( d.templ() ) + || NifModel::compounds.contains( d.templ() ) ); } @@ -661,7 +661,7 @@ class NifXmlHandler final : public QXmlDefaultHandler err( tr( "struct type %1 refers to unknown type %2" ).arg( key, data.type() ) ); if ( !checkTemp( data ) ) - err( tr( "struct type %1 refers to unknown template type %2" ).arg( key, data.temp() ) ); + err( tr( "struct type %1 refers to unknown template type %2" ).arg( key, data.templ() ) ); if ( data.type() == key ) err( tr( "struct type %1 contains itself" ).arg( key ) ); @@ -682,7 +682,7 @@ class NifXmlHandler final : public QXmlDefaultHandler err( tr( "niobject %1 refers to unknown type %2" ).arg( key, data.type() ) ); if ( !checkTemp( data ) ) - err( tr( "niobject %1 refers to unknown template type %2" ).arg( key, data.temp() ) ); + err( tr( "niobject %1 refers to unknown template type %2" ).arg( key, data.templ() ) ); } } From 281fcb7de634743d83855842b484751e9bff687a Mon Sep 17 00:00:00 2001 From: gavrant Date: Sun, 10 Sep 2023 22:00:44 +0300 Subject: [PATCH 083/118] Fixed "Could not convert ... to a link" message spam on inserting a node into a mesh --- src/model/nifmodel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 71f074809..c7772b158 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -2459,7 +2459,7 @@ void NifModel::adjustLinks( NifItem * parent, int block, int delta ) if ( parent->childCount() > 0 ) { for ( auto child : parent->children() ) adjustLinks( child, block, delta ); - } else { + } else if ( parent->isLink() ) { int l = parent->getLinkValue(); if ( l >= 0 && ( ( delta != 0 && l >= block ) || l == block ) ) { @@ -2479,7 +2479,7 @@ void NifModel::mapLinks( NifItem * parent, const QMap & map ) if ( parent->childCount() > 0 ) { for ( auto child : parent->children() ) mapLinks( child, map ); - } else { + } else if ( parent->isLink() ) { int l = parent->getLinkValue(); if ( l >= 0 ) { From 6f04f6cce1779b6358bb3005b7f7d5f51027bf4e Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:13:12 -0400 Subject: [PATCH 084/118] Game Manager - Starfield support --- src/gamemanager.cpp | 4 ++ src/gamemanager.h | 6 +++ src/ui/settingspane.cpp | 10 +++- src/ui/settingsresources.ui | 92 ++++++++++++++++++++++++++++++++++++- 4 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/gamemanager.cpp b/src/gamemanager.cpp index ba6a42796..ded9f8654 100644 --- a/src/gamemanager.cpp +++ b/src/gamemanager.cpp @@ -178,6 +178,8 @@ GameMode GameManager::get_game( uint32_t version, uint32_t user, uint32_t bsver return FALLOUT_4; case BSSTREAM_155: return FALLOUT_76; + case BSSTREAM_172: + return STARFIELD; default: break; }; @@ -211,6 +213,8 @@ void GameManager::init_settings( int& manager_version, QProgressDialog* dlg ) co QStringList filtered; if ( game.id == FALLOUT_4 || game.id == FALLOUT_76 ) filtered.append(archives_list(game.path, DATA.value(game.id, {}), "materials")); + if ( game.id == STARFIELD ) + filtered.append(archives_list(game.path, DATA.value(game.id, {}), "geometries")); filtered.append(archives_list(game.path, DATA.value(game.id, {}), "textures")); filtered.removeDuplicates(); diff --git a/src/gamemanager.h b/src/gamemanager.h index 54b69d787..3e74e01f4 100644 --- a/src/gamemanager.h +++ b/src/gamemanager.h @@ -49,6 +49,7 @@ enum GameMode : int SKYRIM_SE, FALLOUT_4, FALLOUT_76, + STARFIELD, NUM_GAMES, @@ -76,6 +77,7 @@ static const GameMap STRING = { {SKYRIM_SE, "Skyrim SE"}, {FALLOUT_4, "Fallout 4"}, {FALLOUT_76, "Fallout 76"}, + {STARFIELD, "Starfield"}, {OTHER, "Other Games"} }; @@ -100,6 +102,7 @@ static const GameMap DATA = { {SKYRIM_SE, "Data"}, {FALLOUT_4, "Data"}, {FALLOUT_76, "Data"}, + {STARFIELD, "Data"}, {OTHER, ""} }; @@ -112,6 +115,7 @@ static const ResourceListMap FOLDERS = { {SKYRIM_SE, {"."}}, {FALLOUT_4, {".", "Textures"}}, {FALLOUT_76, {".", "Textures"}}, + {STARFIELD, {".", "Textures"}}, {OTHER, {}} }; @@ -154,6 +158,7 @@ enum GameVersion : uint64_t V20_2_0_7_SSE = VersionDef(20, 2, 0, 7, 11, 100), V20_2_0_7_FO4 = VersionDef(20, 2, 0, 7, 11, 130), V20_2_0_7_F76 = VersionDef(20, 2, 0, 7, 11, 155), + V20_2_0_7_STF = VersionDef(20, 2, 0, 7, 11, 172), V20_2_0_8 = VersionDef(20, 2, 0, 8), V20_3_0_1 = VersionDef(20, 3, 0, 1), V20_3_0_2 = VersionDef(20, 3, 0, 2), @@ -198,6 +203,7 @@ enum BSVersion BSSTREAM_100 = 100, BSSTREAM_130 = 130, BSSTREAM_155 = 155, + BSSTREAM_172 = 172, }; QString StringForMode(GameMode game); diff --git a/src/ui/settingspane.cpp b/src/ui/settingspane.cpp index d346cb4d7..3dbd0024d 100644 --- a/src/ui/settingspane.cpp +++ b/src/ui/settingspane.cpp @@ -468,7 +468,7 @@ void SettingsRender::setDefault() #ifdef Q_OS_WIN32 bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QString & regValue, const QString & gameFolder, - QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) + QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) { QSettings reg( regPath, QSettings::Registry32Format ); @@ -503,7 +503,7 @@ bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QStr } bool regFolderPaths( QStringList & gamePaths, const QStringList & regPaths, const QString & regValue, const QString & gameFolder, - QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) + QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) { bool result = false; for ( const QString & path : regPaths ) { @@ -890,6 +890,12 @@ void SettingsResources::on_btnArchiveAutoDetect_clicked() archives_list << a; } + for ( const auto& a : GameManager::filter_archives(data_archives, "geometries") ) { + if ( archives_list.contains(a, Qt::CaseInsensitive) ) + continue; + archives_list << a; + } + archives_list.removeDuplicates(); archives->setStringList(archives_list); diff --git a/src/ui/settingsresources.ui b/src/ui/settingsresources.ui index 5917d40dd..1306c2994 100644 --- a/src/ui/settingsresources.ui +++ b/src/ui/settingsresources.ui @@ -38,7 +38,6 @@ Segoe UI Semibold 12 - 75 true @@ -89,7 +88,7 @@ 0 0 792 - 545 + 541 @@ -725,6 +724,85 @@ + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 4 + + + + + + 0 + 0 + + + + GAME_9 + + + edtGame9 + + + + + + + + 0 + 0 + + + + true + + + Game Not Found + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + Enabled + + + true + + + + + + @@ -918,6 +996,11 @@ GAME_8 + + + GAME_9 + + GAME_0 @@ -1068,6 +1151,11 @@ Game Paths GAME_8 + + + GAME_9 + + GAME_0 From 48c28757be16eff86b7540b61d1a97d45f639aa4 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:15:15 -0400 Subject: [PATCH 085/118] Update GNRL BA2 reader for Starfield geometries DX10 updates not yet implemented since materials are not parseable anyway. --- lib/fsengine/bsa.cpp | 25 +++++++++++++++++++------ lib/fsengine/bsa.h | 4 ++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index 6ef7a528a..596959931 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -166,7 +166,7 @@ bool BSA::canOpen( const QString & fn ) if ( magic == F4_BSAHEADER_FILEID ) { if ( f.read( (char *) & version, sizeof( version ) ) != 4 ) return false; - return version == F4_BSAHEADER_VERSION; + return version == F4_BSAHEADER_VERSION || version == SF_BSAHEADER_VERSION2 || version == SF_BSAHEADER_VERSION3; } else if ( magic == OB_BSAHEADER_FILEID ) { if ( f.read( (char *)&version, sizeof( version ) ) != 4 ) return false; @@ -198,13 +198,22 @@ bool BSA::open() if ( magic == F4_BSAHEADER_FILEID ) { bsa.read( (char*)&version, sizeof( version ) ); - if ( version != F4_BSAHEADER_VERSION ) - throw QString( "file version" ); + //if ( version != F4_BSAHEADER_VERSION ) + // throw QString( "file version" ); F4BSAHeader header; if ( bsa.read( (char *)&header, sizeof( header ) ) != sizeof( header ) ) throw QString( "header size" ); + quint32 unk1 = 0; + quint32 unk2 = 0; + if ( version == SF_BSAHEADER_VERSION2 || version == SF_BSAHEADER_VERSION3 ) { + bsa.read((char*)&unk1, sizeof(quint32)); + bsa.read((char*)&unk2, sizeof(quint32)); + } + if ( version == SF_BSAHEADER_VERSION3 ) + bsa.read((char*)&version3flag, sizeof(quint32)); + numFiles = header.numFiles; auto offset = header.nameTableOffset; @@ -222,10 +231,14 @@ bool BSA::open() } } + // Two new ints for Starfield + quint32 OFFSET = (version == F4_BSAHEADER_VERSION) ? 8 : 16; + OFFSET = (version >= SF_BSAHEADER_VERSION3) ? 20 : OFFSET; + QString h = QString::fromLatin1( header.type, 4 ); if ( h == "GNRL" ) { // General BA2 Format - if ( bsa.seek( sizeof( header ) + 8 ) ) { + if ( bsa.seek( sizeof( header ) + OFFSET ) ) { for ( quint32 i = 0; i < numFiles; i++ ) { F4GeneralInfo finfo; bsa.read( (char*)&finfo, sizeof( F4GeneralInfo ) ); @@ -241,7 +254,7 @@ bool BSA::open() } } else if ( h == "DX10" ) { // Texture BA2 Format - if ( bsa.seek( sizeof( header ) + 8 ) ) { + if ( bsa.seek( sizeof( header ) + OFFSET ) ) { for ( quint32 i = 0; i < numFiles; i++ ) { F4Tex tex; bsa.read( (char*)&tex.header, 24 ); @@ -339,7 +352,7 @@ bool BSA::open() folderInfos << info; } - for ( const BSAFolderInfo folderInfo : folderInfos ) { + for ( const BSAFolderInfo& folderInfo : folderInfos ) { QString folderName; if ( ! BSAReadSizedString( bsa, folderName ) || folderName.isEmpty() ) { diff --git a/lib/fsengine/bsa.h b/lib/fsengine/bsa.h index 04012da7d..827b9fda8 100644 --- a/lib/fsengine/bsa.h +++ b/lib/fsengine/bsa.h @@ -58,6 +58,8 @@ using namespace std; #define F3_BSAHEADER_VERSION 0x68 //!< Version number of a Fallout 3 BSA #define SSE_BSAHEADER_VERSION 0x69 //!< Version number of a Skyrim SE BSA #define F4_BSAHEADER_VERSION 0x01 //!< Version number of a Fallout 4 BA2 +#define SF_BSAHEADER_VERSION2 0x02 //!< Version number of a Starfield BA2 +#define SF_BSAHEADER_VERSION3 0x03 //!< Version number of a Starfield BA2 /* Archive flags */ #define OB_BSAARCHIVE_PATHNAMES 0x0001 //!< Whether the BSA has names for paths @@ -344,6 +346,8 @@ class BSA final : public FSArchiveFile quint32 version = 0; + quint32 version3flag; + //! Mutual exclusion handler QMutex bsaMutex; From 96a7277f1527632e71db9fe571149c26648013e1 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:16:01 -0400 Subject: [PATCH 086/118] Add UNORM bone weights --- src/gl/gltools.cpp | 8 ++++++++ src/gl/gltools.h | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 441b94ccc..5a357aa40 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -71,6 +71,14 @@ void BoneWeights::setTransform( const NifModel * nif, const QModelIndex & index radius = sph.radius; } +BoneWeightsUNorm::BoneWeightsUNorm(QVector unorms, int v) +{ + weights.resize(unorms.size()); + for ( int i = 0; i < unorms.size(); i++ ) { + weights[i] = VertexWeight(v, unorms[i] / (float)UINT_MAX); + } +} + SkinPartition::SkinPartition( const NifModel * nif, const QModelIndex & index ) { diff --git a/src/gl/gltools.h b/src/gl/gltools.h index 60cbd26fe..40762bbce 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -82,7 +82,7 @@ class VertexWeight final }; //! A set of vertices weighted to a bone -class BoneWeights final +class BoneWeights { public: BoneWeights() {} @@ -98,6 +98,13 @@ class BoneWeights final QVector weights; }; +class BoneWeightsUNorm : public BoneWeights +{ +public: + BoneWeightsUNorm() {} + BoneWeightsUNorm(QVector unorms, int v); +}; + //! A skin partition class SkinPartition final { From 2de0b93cddd80e71c1a9a4f7807a88238ab6abf2 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:17:47 -0400 Subject: [PATCH 087/118] Consistent parameter name for legibility --- src/gl/glcontroller.cpp | 2 +- src/gl/glnode.cpp | 2 +- src/gl/glnode.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 713c1370e..9a80c771c 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -42,7 +42,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * IControllable */ -IControllable::IControllable( Scene * s, const QModelIndex & i ) : scene( s ), iBlock( i ) +IControllable::IControllable( Scene * s, const QModelIndex & iBlock) : scene( s ), iBlock( iBlock ) { } diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index b585fbb53..ab8ab6fc6 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -192,7 +192,7 @@ void NodeList::alphaSort() */ -Node::Node( Scene * s, const QModelIndex & index ) : IControllable( s, index ), parent( 0 ), ref( 0 ) +Node::Node( Scene * s, const QModelIndex & iBlock) : IControllable( s, iBlock ), parent( 0 ), ref( 0 ) { nodeId = 0; flags.bits = 0; diff --git a/src/gl/glnode.h b/src/gl/glnode.h index 23544f5a6..92d0fe077 100644 --- a/src/gl/glnode.h +++ b/src/gl/glnode.h @@ -94,7 +94,7 @@ class Node : public IControllable } NodeFlags; public: - Node( Scene * scene, const QModelIndex & block ); + Node( Scene * scene, const QModelIndex & iBlock ); static int SELECTING; From 378c058b4b4df9f4e1e51b2e0f1b3eb0bc60e09c Mon Sep 17 00:00:00 2001 From: gavrant Date: Tue, 12 Sep 2023 11:03:37 +0300 Subject: [PATCH 088/118] Added a few new entries to the array singular pseudonyms map and sorted the map alphabetically --- src/model/nifmodel.cpp | 59 ++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index c7772b158..8bb7302b9 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -53,44 +53,47 @@ void NifModel::setupArrayPseudonyms() #define registerPseudonym(plural, singular) arrayPseudonyms.insert(plural, singular) - registerPseudonym("Vertex Data", "Vertex"); - registerPseudonym("Vertices", "Vertex"); - registerPseudonym("Triangles", "Triangle"); - registerPseudonym("Triangles Copy", "Triangle Copy"); - registerPseudonym("Normals", "Normal"); - registerPseudonym("Tangents", "Tangent"); + registerPseudonym("Big Tris", "Big Tri"); + registerPseudonym("Big Verts", "Big Vert"); registerPseudonym("Bitangents", "Bitangent"); - registerPseudonym("Vertex Colors", "Vertex Color"); - registerPseudonym("Textures", "Texture"); - registerPseudonym("Strings", "String"); + registerPseudonym("Block Types", "Block Type"); + registerPseudonym("Bone Indices", "Bone Index"); + registerPseudonym("Bone List", "Bone"); + registerPseudonym("Bone Weights", "Bone Weight"); + registerPseudonym("Bones", "Bone"); registerPseudonym("Children", "Child"); - registerPseudonym("Extra Data List", "Extra Data"); registerPseudonym("Chunk Materials", "Chunk Material"); registerPseudonym("Chunk Transforms", "Chunk Transform"); - registerPseudonym("Big Verts", "Big Vert"); - registerPseudonym("Big Tris", "Big Tri"); registerPseudonym("Chunks", "Chunk"); + registerPseudonym("Component Formats", "Component Format"); + registerPseudonym("Connect Points", "Connect Point"); + registerPseudonym("Cut Offsets", "Cut Offset"); registerPseudonym("Effects", "Effect"); - registerPseudonym("Partitions", "Partition"); - registerPseudonym("Bones", "Bone"); - registerPseudonym("Bone List", "Bone"); - registerPseudonym("Bone Weights", "Bone Weight"); - registerPseudonym("Vertex Weights", "Vertex Weight"); - registerPseudonym("Bone Indices", "Bone Index"); - registerPseudonym("Vertex Indices", "Vertex Index"); + registerPseudonym("Extra Data List", "Extra Data"); + registerPseudonym("Groups", "Group"); registerPseudonym("Match Groups", "Match Group"); - registerPseudonym("Strips", "Strip"); - registerPseudonym("Strip Lengths", "Strip Length"); + registerPseudonym("Normals", "Normal"); + registerPseudonym("Particle Normals", "Particle Normal"); + registerPseudonym("Particle Triangles", "Particle Triangle"); + registerPseudonym("Particle Vertices", "Particle Vertex"); + registerPseudonym("Partitions", "Partition"); registerPseudonym("Properties", "Property"); - registerPseudonym("Connect Points", "Connect Point"); + registerPseudonym("Regions", "Region"); registerPseudonym("Scales", "Scale"); - registerPseudonym("Texture Arrays", "Texture Array"); - registerPseudonym("Block Types", "Block Type"); - registerPseudonym("Groups", "Group"); registerPseudonym("Segment Starts", "Segment Start"); - registerPseudonym("Cut Offsets", "Cut Offset"); - registerPseudonym("Regions", "Region"); - registerPseudonym("Component Formats", "Component Format"); + registerPseudonym("Strings", "String"); + registerPseudonym("Strip Lengths", "Strip Length"); + registerPseudonym("Strips", "Strip"); + registerPseudonym("Tangents", "Tangent"); + registerPseudonym("Texture Arrays", "Texture Array"); + registerPseudonym("Textures", "Texture"); + registerPseudonym("Triangles Copy", "Triangle Copy"); + registerPseudonym("Triangles", "Triangle"); + registerPseudonym("Vertex Colors", "Vertex Color"); + registerPseudonym("Vertex Data", "Vertex"); + registerPseudonym("Vertex Indices", "Vertex Index"); + registerPseudonym("Vertex Weights", "Vertex Weight"); + registerPseudonym("Vertices", "Vertex"); } //! @file nifmodel.cpp The NIF data model. From 2dfcdd43eff942faa44e87d1c5be5d1b6122510b Mon Sep 17 00:00:00 2001 From: gavrant Date: Tue, 12 Sep 2023 13:14:27 +0300 Subject: [PATCH 089/118] Always hide fields in "Block Details" and "Header" views if they do not pass mesh version or block type checks, even when "Show Non-applicable Rows" option is on. This makes the views much cleaner by hiding all the fields that are irrelevant to the current mesh (its version) or the current block (for example, doubled "Triangles" and "Vertex Data", or always read-only "Shader Type" in BSTriShapes). --- src/data/nifitem.h | 10 +++++++++- src/ui/widgets/nifview.cpp | 23 +++++++++-------------- src/ui/widgets/nifview.h | 2 +- src/xml/nifxml.cpp | 6 ++++++ 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/data/nifitem.h b/src/data/nifitem.h index 3b1de699a..8ddc41ae0 100644 --- a/src/data/nifitem.h +++ b/src/data/nifitem.h @@ -63,7 +63,8 @@ class NifSharedData final : public QSharedData Array = 0x10, MultiArray = 0x20, Conditionless = 0x40, - Mixin = 0x80 + Mixin = 0x80, + TypeCondition = 0x100 }; typedef QFlags DataFlags; @@ -168,6 +169,7 @@ class NifData inline const QString & vercond() const { return d->vercond; } //! Get the version condition attribute of the data, as an expression. inline const NifExpr & verexpr() const { return d->verexpr; } + //! Get the abstract attribute of the data. inline bool isAbstract() const { return d->flags & NifSharedData::Abstract; } //! Is the data binary. Binary means the data is being treated as one blob. @@ -182,6 +184,8 @@ class NifData inline bool isMultiArray() const { return d->flags & NifSharedData::MultiArray; } //! Is the data conditionless. Conditionless means no expression evaluation is necessary. inline bool isConditionless() const { return d->flags & NifSharedData::Conditionless; } + //! Does the data's condition checks only the type of the parent block. + inline bool hasTypeCondition() const { return d->flags & NifSharedData::TypeCondition; } //! Is the data a mixin. Mixin is a specialized compound which creates no nesting. inline bool isMixin() const { return d->flags & NifSharedData::Mixin; } @@ -244,6 +248,8 @@ class NifData inline void setIsConditionless( bool flag ) { setFlag( NifSharedData::Conditionless, flag ); } //! Sets the mixin data flag. Mixin is a specialized compound which creates no nesting. inline void setIsMixin( bool flag ) { setFlag( NifSharedData::Mixin, flag ); } + //! Sets the type condition data flag (does the data's condition checks only the type of the parent block). + inline void setHasTypeCondition( bool flag ) { setFlag( NifSharedData::TypeCondition, flag ); } //! Gets the data's value type (NifValue::Type). inline NifValue::Type valueType() const { return value.type(); } @@ -633,6 +639,8 @@ class NifItem inline bool isArrayEx() const { return isArray() || ( parentItem && parentItem->isMultiArray() ); } //! Is the item data conditionless. Conditionless means no expression evaluation is necessary. inline bool isConditionless() const { return itemData.isConditionless(); } + //! Does the items data's condition checks only the type of the parent block. + inline bool hasTypeCondition() const { return itemData.hasTypeCondition(); } //! Does the item's name match testName? inline bool hasName( const QString & testName ) const { return itemData.name() == testName; } diff --git a/src/ui/widgets/nifview.cpp b/src/ui/widgets/nifview.cpp index 9c5bf3b06..e37764e77 100644 --- a/src/ui/widgets/nifview.cpp +++ b/src/ui/widgets/nifview.cpp @@ -66,9 +66,8 @@ void NifTreeView::setModel( QAbstractItemModel * model ) QTreeView::setModel( model ); - if ( nif && doRowHiding ) { + if ( nif ) connect( nif, &BaseModel::dataChanged, this, &NifTreeView::updateConditions ); - } } void NifTreeView::setRootIndex( const QModelIndex & index ) @@ -93,11 +92,8 @@ void NifTreeView::setRowHiding( bool show ) doRowHiding = !show; - if ( nif && doRowHiding ) { + if ( nif ) connect( nif, &BaseModel::dataChanged, this, &NifTreeView::updateConditions ); - } else if ( nif ) { - disconnect( nif, &BaseModel::dataChanged, this, &NifTreeView::updateConditions ); - } // refresh updateConditionRecurse( rootIndex() ); @@ -114,13 +110,13 @@ bool NifTreeView::isRowHidden( int r, const QModelIndex & index ) const bool NifTreeView::isRowHidden( const NifItem * rowItem ) const { if ( rowItem && nif ) { - if ( doRowHiding ) { - if ( !nif->evalCondition( rowItem )) + if ( doRowHiding || rowItem->hasTypeCondition() ) { + if ( !nif->evalCondition( rowItem ) ) return true; - } /* else { + } else { if ( !nif->evalVersion( rowItem ) ) return true; - } */ + } } return false; @@ -167,7 +163,7 @@ void NifTreeView::copy() } } -void NifTreeView::pasteTo( QModelIndex iDest, const NifValue & srcValue ) +void NifTreeView::pasteTo( const QModelIndex iDest, const NifValue & srcValue ) { // Only run once per row for the correct column if ( iDest.column() != NifModel::ValueCol ) @@ -254,7 +250,7 @@ void NifTreeView::pasteTo( QModelIndex iDest, const NifValue & srcValue ) void NifTreeView::paste() { ChangeValueCommand::createTransaction(); - for ( const auto i : valueIndexList( selectionModel()->selectedIndexes() ) ) + for ( const auto & i : valueIndexList( selectionModel()->selectedIndexes() ) ) pasteTo( i, valueClipboard->getValue() ); } @@ -490,9 +486,8 @@ void NifTreeView::currentChanged( const QModelIndex & current, const QModelIndex { QTreeView::currentChanged( current, last ); - if ( nif && doRowHiding ) { + if ( nif ) updateConditionRecurse( current ); - } autoExpanded = false; auto mdl = static_cast( nif ); diff --git a/src/ui/widgets/nifview.h b/src/ui/widgets/nifview.h index f87b420e1..e3427aa37 100644 --- a/src/ui/widgets/nifview.h +++ b/src/ui/widgets/nifview.h @@ -110,7 +110,7 @@ protected slots: void copy(); //! Row Paste void paste(); - void pasteTo( QModelIndex idx, const NifValue & srcValue ); + void pasteTo( const QModelIndex idx, const NifValue & srcValue ); //! Array/Compound Paste void pasteArray(); diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index ef6f5fb7e..effe1087e 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -347,11 +347,16 @@ class NifXmlHandler final : public QXmlDefaultHandler QString vercond = get( "vercond" ); QString defval = get( "default" ); + bool hasTypeCondition = false; + QString onlyT = list.value( "onlyT" ); QString excludeT = list.value( "excludeT" ); if ( !onlyT.isEmpty() || !excludeT.isEmpty() ) { Q_ASSERT( cond.isEmpty() ); Q_ASSERT( onlyT.isEmpty() != excludeT.isEmpty() ); + // hasTypeCondition flag here relies on that cond is always either "cond" attribute or "onlyT/excludeT", not a combination of both at the same time. + // If it ever changes, hasTypeCondition logic will have to be rewritten. + hasTypeCondition = true; if ( !onlyT.isEmpty() ) cond = onlyT; else @@ -417,6 +422,7 @@ class NifXmlHandler final : public QXmlDefaultHandler data.setIsArray( isArray ); data.setIsMultiArray( isMultiArray ); data.setIsMixin( isMixin ); + data.setHasTypeCondition( hasTypeCondition ); if ( !defval.isEmpty() ) { From a9b21831d04656a9b2f9d7ee42cf27f1043d39aa Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:37:07 -0400 Subject: [PATCH 090/118] Fix View > Show actions --- src/nifskope_ui.cpp | 8 ++ src/ui/nifskope.ui | 328 +------------------------------------------- 2 files changed, 9 insertions(+), 327 deletions(-) diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index d4b651d18..c31807017 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -393,6 +393,14 @@ void NifSkope::initDockWidgets() dInsp->setVisible( false ); dKfm->setVisible( false ); + ui->menuShow->addAction(dList->toggleViewAction()); + ui->menuShow->addAction(dTree->toggleViewAction()); + ui->menuShow->addAction(dBrowser->toggleViewAction()); + ui->menuShow->addAction(dHeader->toggleViewAction()); + ui->menuShow->addAction(dInsp->toggleViewAction()); + ui->menuShow->addAction(dKfm->toggleViewAction()); + ui->menuShow->addAction(dRefr->toggleViewAction()); + // Set Inspect widget dInsp->setWidget( inspect ); diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 6c31f1d77..139e057c8 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -10,7 +10,7 @@ 800 - + @@ -272,12 +272,6 @@ Show - - - - - - @@ -2110,166 +2104,6 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - - aToggleHelp - triggered(bool) - RefrDock - setVisible(bool) - - - -1 - -1 - - - 908 - 630 - - - - - aToggleBlockList - triggered(bool) - ListDock - setVisible(bool) - - - -1 - -1 - - - 99 - 327 - - - - - aToggleBlockDetails - triggered(bool) - TreeDock - setVisible(bool) - - - -1 - -1 - - - 282 - 743 - - - - - aToggleKfm - triggered(bool) - KfmDock - setVisible(bool) - - - -1 - -1 - - - 885 - 311 - - - - - aToggleInspect - triggered(bool) - InspectDock - setVisible(bool) - - - -1 - -1 - - - 885 - 76 - - - - - InspectDock - visibilityChanged(bool) - aToggleInspect - setChecked(bool) - - - 885 - 76 - - - -1 - -1 - - - - - ListDock - visibilityChanged(bool) - aToggleBlockList - setChecked(bool) - - - 99 - 327 - - - -1 - -1 - - - - - TreeDock - visibilityChanged(bool) - aToggleBlockDetails - setChecked(bool) - - - 282 - 743 - - - -1 - -1 - - - - - RefrDock - visibilityChanged(bool) - aToggleHelp - setChecked(bool) - - - 908 - 630 - - - -1 - -1 - - - - - KfmDock - visibilityChanged(bool) - aToggleKfm - setChecked(bool) - - - 885 - 311 - - - -1 - -1 - - - aNifToolsWebsite triggered() @@ -2350,86 +2184,6 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - - aToggleKfm - triggered() - KfmDock - raise() - - - -1 - -1 - - - 885 - 349 - - - - - aToggleInspect - triggered() - InspectDock - raise() - - - -1 - -1 - - - 885 - 111 - - - - - aToggleHelp - triggered() - RefrDock - raise() - - - -1 - -1 - - - 908 - 633 - - - - - aToggleBlockList - triggered() - ListDock - raise() - - - -1 - -1 - - - 99 - 330 - - - - - aToggleBlockDetails - triggered() - TreeDock - raise() - - - -1 - -1 - - - 282 - 743 - - - bExpandAllTree clicked() @@ -2558,86 +2312,6 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 - - aToggleHeader - triggered(bool) - HeaderDock - setVisible(bool) - - - -1 - -1 - - - 979 - 635 - - - - - aToggleHeader - triggered() - HeaderDock - raise() - - - -1 - -1 - - - 979 - 635 - - - - - aToggleArchiveBrowser - triggered(bool) - BrowserDock - setVisible(bool) - - - -1 - -1 - - - 883 - 634 - - - - - aToggleArchiveBrowser - triggered() - BrowserDock - raise() - - - -1 - -1 - - - 883 - 634 - - - - - BrowserDock - visibilityChanged(bool) - aToggleArchiveBrowser - setChecked(bool) - - - 883 - 634 - - - -1 - -1 - - - aDiscord triggered() From d0a460c235e10c63750f111d2d481a383109dfa8 Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:52:33 -0400 Subject: [PATCH 091/118] Fix tView actions --- src/nifskope_ui.cpp | 10 +++++++++- src/ui/nifskope.ui | 6 ------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index c31807017..58dc4e3c7 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -395,12 +395,20 @@ void NifSkope::initDockWidgets() ui->menuShow->addAction(dList->toggleViewAction()); ui->menuShow->addAction(dTree->toggleViewAction()); - ui->menuShow->addAction(dBrowser->toggleViewAction()); ui->menuShow->addAction(dHeader->toggleViewAction()); + ui->menuShow->addAction(dBrowser->toggleViewAction()); ui->menuShow->addAction(dInsp->toggleViewAction()); ui->menuShow->addAction(dKfm->toggleViewAction()); ui->menuShow->addAction(dRefr->toggleViewAction()); + ui->tView->addAction(dList->toggleViewAction()); + ui->tView->addAction(dTree->toggleViewAction()); + ui->tView->addAction(dHeader->toggleViewAction()); + ui->tView->addAction(dBrowser->toggleViewAction()); + ui->tView->addAction(dInsp->toggleViewAction()); + ui->tView->addAction(dKfm->toggleViewAction()); + ui->tView->addAction(dRefr->toggleViewAction()); + // Set Inspect widget dInsp->setWidget( inspect ); diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 139e057c8..c4622a111 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -175,12 +175,6 @@ false - - - - - - From cd3a3a24f0eca5b1011d40d2c30f6fe032a034e6 Mon Sep 17 00:00:00 2001 From: gavrant Date: Wed, 13 Sep 2023 23:33:27 +0300 Subject: [PATCH 092/118] Fixed Settings dialog blocking all other windows in OS, not just NifSkope's ones. It used to stay in the middle of the screen even when you switch to your browser or any other app, covering everything underneath it. Now Settings will block only NifSkope's windows and disappear if you switch to another app. --- src/ui/settingsdialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index 5835faa9a..39c14f92f 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -21,7 +21,8 @@ SettingsDialog::SettingsDialog( QWidget * parent ) : categories = ui->categoryList; setWindowTitle( tr( "Settings" ) ); - setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); + setWindowFlags( Qt::Tool ); + setWindowModality( Qt::WindowModality::ApplicationModal ); installEventFilter( this ); content->addWidget( new SettingsGeneral( this ) ); From 7be0b42e9423868e48fdccf9dc857a201fc04f21 Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 14 Sep 2023 09:38:13 +0300 Subject: [PATCH 093/118] Fixed modality of NifBlockEditor dialog (transform edit, etc.) It used to stay on top always (even if you switch to another application), but did not block input to the parent mesh window. Now NifBlockEditor stays on top of the parent window only and blocks input to it. --- src/ui/widgets/nifeditors.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/widgets/nifeditors.cpp b/src/ui/widgets/nifeditors.cpp index 3ac2f3f30..99e0904ce 100644 --- a/src/ui/widgets/nifeditors.cpp +++ b/src/ui/widgets/nifeditors.cpp @@ -45,8 +45,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NifBlockEditor::NifBlockEditor( NifModel * n, const QModelIndex & i, bool fireAndForget ) - : QWidget(), nif( n ), iBlock( i ) + : QWidget( n->getWindow(), Qt::Tool ), nif( n ), iBlock( i ) { + setWindowModality( Qt::WindowModality::WindowModal ); + connect( nif, &NifModel::dataChanged, this, &NifBlockEditor::nifDataChanged ); connect( nif, &NifModel::modelReset, this, &NifBlockEditor::updateData ); connect( nif, &NifModel::destroyed, this, &NifBlockEditor::nifDestroyed ); @@ -75,8 +77,6 @@ NifBlockEditor::NifBlockEditor( NifModel * n, const QModelIndex & i, bool fireAn layout->addLayout( btnlayout ); } - - setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); } void NifBlockEditor::add( NifEditBox * box ) From 03ad1c27e17df438c703e33437a12bc4dee1025c Mon Sep 17 00:00:00 2001 From: gavrant Date: Thu, 14 Sep 2023 23:53:10 +0300 Subject: [PATCH 094/118] Finishing touches to pseudonyms for displaying array items in "Block Details" - Added pseudonyms for almost 200 arrays. All the arrays from nif.xml ( https://github.com/niftools/nifxml/tree/develop version) are now covered, with the exception of arrays that don't need pseudonyms or have too generic names ("Data", "Value", "Unknown", ...). - Multi-arrays (2-level arrays; e.g., UV Sets, Vertex Weights) got their own pseudonym subsystem, with a different logic. - Binary arrays are now ignored because it's always a single item with a "X bytes" value. --- src/model/nifmodel.cpp | 199 ++++++++++++++++++++++++++++++++++++++--- src/model/nifmodel.h | 3 - 2 files changed, 185 insertions(+), 17 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 8bb7302b9..39ed46149 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -44,56 +44,217 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -QHash NifModel::arrayPseudonyms; +QHash arrayPseudonyms; +QHash multiArrayPseudonyms1; +QHash multiArrayPseudonyms2; -void NifModel::setupArrayPseudonyms() +void setupArrayPseudonyms() { - if (!arrayPseudonyms.isEmpty()) + if ( !arrayPseudonyms.isEmpty() ) return; #define registerPseudonym(plural, singular) arrayPseudonyms.insert(plural, singular) + registerPseudonym("Active Keys", "Active Key"); + registerPseudonym("Actors", "Actor"); + registerPseudonym("Affected Node Pointers", "Affected Node Pointer"); + registerPseudonym("Affected Nodes", "Affected Node"); + registerPseudonym("Anim Note Arrays", "Anim Note Array"); + registerPseudonym("Anim Notes", "Anim Note"); + registerPseudonym("Attachments", "Attachment"); registerPseudonym("Big Tris", "Big Tri"); registerPseudonym("Big Verts", "Big Vert"); registerPseudonym("Bitangents", "Bitangent"); + registerPseudonym("Block Infos", "Block Info"); + registerPseudonym("Block Offsets", "Block Offset"); + registerPseudonym("Block Type Hashes", "Block Type Hash"); registerPseudonym("Block Types", "Block Type"); + registerPseudonym("Blocks", "Block"); + registerPseudonym("Bone Bounds", "Bone Bound"); registerPseudonym("Bone Indices", "Bone Index"); registerPseudonym("Bone List", "Bone"); + registerPseudonym("Bone Transforms", "Bone Transform"); registerPseudonym("Bone Weights", "Bone Weight"); registerPseudonym("Bones", "Bone"); + registerPseudonym("Bounding Volumes", "Bounding Volume"); + registerPseudonym("Buttons", "Button"); + registerPseudonym("Chained Entities", "Chained Entity"); + registerPseudonym("Channel Types", "Channel Type"); + registerPseudonym("Channels", "Channel"); registerPseudonym("Children", "Child"); registerPseudonym("Chunk Materials", "Chunk Material"); registerPseudonym("Chunk Transforms", "Chunk Transform"); registerPseudonym("Chunks", "Chunk"); + registerPseudonym("Clothes", "Cloth"); + registerPseudonym("Colliders", "Collider"); + registerPseudonym("Color Keys", "Color Key"); + registerPseudonym("Colors", "Color"); + registerPseudonym("Compact Control Points", "Compact Control Point"); + registerPseudonym("Compartments", "Compartment"); + registerPseudonym("Complete Points", "Complete Point"); registerPseudonym("Component Formats", "Component Format"); + registerPseudonym("Compressed Vertices", "Compressed Vertex"); registerPseudonym("Connect Points", "Connect Point"); + registerPseudonym("Constraints", "Constraint"); + registerPseudonym("Control Points", "Control Point"); + registerPseudonym("Controlled Blocks", "Controlled Block"); + registerPseudonym("Controller Seq List", "Controller Seq"); + registerPseudonym("Controller Sequences", "Controller Sequence"); + registerPseudonym("Corners", "Corner"); registerPseudonym("Cut Offsets", "Cut Offset"); + registerPseudonym("Data Sizes", "Data Size"); + registerPseudonym("Datastreams", "Datastream"); + registerPseudonym("Dests", "Dest"); + registerPseudonym("DIV2 Floats", "DIV2 Float"); + registerPseudonym("DIV2 Ints", "DIV2 Int"); registerPseudonym("Effects", "Effect"); + registerPseudonym("Elements", "Element"); + registerPseudonym("Emitter Meshes", "Emitter Mesh"); + registerPseudonym("Emitters", "Emitter"); + registerPseudonym("Evaluators", "Evaluator"); registerPseudonym("Extra Data List", "Extra Data"); + registerPseudonym("Extra Targets", "Extra Target"); + registerPseudonym("Filter Constants", "Filter Constant"); + registerPseudonym("Filter Ops", "Filter Op"); + registerPseudonym("Filters", "Filter"); + registerPseudonym("Fixtures", "Fixture"); + registerPseudonym("Float Control Points", "Float Control Point"); + registerPseudonym("Forces", "Force"); + registerPseudonym("Generations", "Generation"); + registerPseudonym("Group Collision Flags", "Group Collision Flag"); registerPseudonym("Groups", "Group"); + registerPseudonym("Images", "Image"); + registerPseudonym("In Portals", "In Portal"); + registerPseudonym("Indices", "Index"); + registerPseudonym("Instance Nodes", "Instance Node"); + registerPseudonym("Instances", "Instance"); + registerPseudonym("Interp Array Items", "Interp Item"); + registerPseudonym("Interpolator Weights", "Interpolator Weight"); + registerPseudonym("Interpolators", "Interpolator"); + registerPseudonym("Items", "Item"); + registerPseudonym("Joints", "Joint"); + registerPseudonym("Keys", "Key"); + registerPseudonym("Knots", "Knot"); + registerPseudonym("Limits", "Limit"); + registerPseudonym("Lines", "Line"); + registerPseudonym("LOD Distances", "LOD Distance"); + registerPseudonym("LOD Entries", "LOD Entry"); + registerPseudonym("LOD Levels", "LOD Level"); + registerPseudonym("LODs", "LOD"); + registerPseudonym("Mapped Primitives", "Mapped Primitive"); + registerPseudonym("Master Particles", "Master Particle"); registerPseudonym("Match Groups", "Match Group"); + registerPseudonym("Material Descs", "Material Desc"); + registerPseudonym("Materials", "Material"); + registerPseudonym("Mesh Emitters", "Mesh Emitter"); + registerPseudonym("Meshes", "Mesh"); + registerPseudonym("Mipmaps", "Mipmap"); + registerPseudonym("Modified Meshes", "Modified Mesh"); + registerPseudonym("Modifiers", "Modifier"); + registerPseudonym("Morphs", "Morph"); + registerPseudonym("Node Groups", "Node Group"); + registerPseudonym("Nodes", "Node"); registerPseudonym("Normals", "Normal"); + registerPseudonym("Objs", "Obj"); + registerPseudonym("Out Portals", "Out Portal"); + registerPseudonym("Particle Meshes", "Particle Mesh"); registerPseudonym("Particle Normals", "Particle Normal"); + registerPseudonym("Particle Systems", "Particle System"); registerPseudonym("Particle Triangles", "Particle Triangle"); registerPseudonym("Particle Vertices", "Particle Vertex"); + registerPseudonym("Particles", "Particle"); registerPseudonym("Partitions", "Partition"); + registerPseudonym("Pivots", "Pivot"); + registerPseudonym("Points", "Point"); + registerPseudonym("Polygon Indices", "Polygon Index"); + registerPseudonym("Polygons", "Polygon"); + registerPseudonym("Poses", "Pose"); + registerPseudonym("Positions", "Position"); registerPseudonym("Properties", "Property"); + registerPseudonym("Proportion Levels", "Proportion Level"); + registerPseudonym("Props", "Prop"); + registerPseudonym("Quaternion Keys", "Quaternion Key"); + registerPseudonym("Radii", "Radius"); + registerPseudonym("Refs", "Ref"); registerPseudonym("Regions", "Region"); + registerPseudonym("Rooms", "Room"); + registerPseudonym("Roots", "Root"); + registerPseudonym("Rotation Angles", "Rotation Angle"); + registerPseudonym("Rotation Axes", "Rotation Axis"); + registerPseudonym("Rotation Keys", "Rotation Key"); + registerPseudonym("Rotation Speeds", "Rotation Speed"); + registerPseudonym("Rotations", "Rotation"); registerPseudonym("Scales", "Scale"); registerPseudonym("Segment Starts", "Segment Start"); + registerPseudonym("Shader Textures", "Shader Texture"); + registerPseudonym("Shadow Casters", "Shadow Caster"); + registerPseudonym("Shadow Receivers", "Shadow Receiver"); + registerPseudonym("Shape Descriptions", "Shape Description"); + registerPseudonym("Shape Properties", "Shape Property"); + registerPseudonym("Simulation Steps", "Simulation Step"); + registerPseudonym("Size Keys", "Size Key"); + registerPseudonym("Sizes", "Size"); + registerPseudonym("Skin Indices", "Skin Index"); + registerPseudonym("Skins", "Skin"); + registerPseudonym("Sources", "Source"); + registerPseudonym("Spawn Rate Keys", "Spawn Rate Key"); + registerPseudonym("Spawners", "Spawner"); + registerPseudonym("Spheres", "Sphere"); + registerPseudonym("States", "State"); registerPseudonym("Strings", "String"); registerPseudonym("Strip Lengths", "Strip Length"); registerPseudonym("Strips", "Strip"); + registerPseudonym("Sub Shapes", "Sub Shape"); + registerPseudonym("SubEntry List", "SubEntry"); + registerPseudonym("Submesh To Region Map", "Submesh To Region"); + registerPseudonym("Submit Points", "Submit Point"); + registerPseudonym("Subtexture Offsets", "Subtexture Offset"); + registerPseudonym("Systems", "System"); registerPseudonym("Tangents", "Tangent"); + registerPseudonym("Target Names", "Target Name"); + registerPseudonym("Tear Indices", "Tear Index"); + registerPseudonym("Tear Split Planes", "Tear Split Plane"); + registerPseudonym("Text Keys", "Text Key"); + registerPseudonym("Texture Array", "Texture"); registerPseudonym("Texture Arrays", "Texture Array"); registerPseudonym("Textures", "Texture"); + registerPseudonym("Transforms", "Transform"); + registerPseudonym("Translations", "Translation"); + registerPseudonym("Tread Transforms", "Tread Transform"); registerPseudonym("Triangles Copy", "Triangle Copy"); registerPseudonym("Triangles", "Triangle"); + registerPseudonym("UV Groups", "UV Group"); + registerPseudonym("Vector Blocks", "Vector Block"); + registerPseudonym("Vectors", "Vector"); + registerPseudonym("Vels", "Vel"); registerPseudonym("Vertex Colors", "Vertex Color"); + registerPseudonym("Vertex Counts", "Vertex Count"); registerPseudonym("Vertex Data", "Vertex"); registerPseudonym("Vertex Indices", "Vertex Index"); + registerPseudonym("Vertex Positions", "Vertex Position"); registerPseudonym("Vertex Weights", "Vertex Weight"); registerPseudonym("Vertices", "Vertex"); + registerPseudonym("Wall Planes", "Wall Plane"); + registerPseudonym("Walls", "Wall"); + registerPseudonym("Weight Indices", "Weight Index"); + registerPseudonym("Weights", "Weight"); + registerPseudonym("XYZ Rotations", "XYZ Rotation"); + + #define registerMultiPseudonym(plural, singular1, singular2) multiArrayPseudonyms1.insert(plural, singular1); multiArrayPseudonyms2.insert(plural, singular2) + + registerMultiPseudonym("Bone Data", "Bone", "Weight"); + registerMultiPseudonym("Bone Indices", "Vertex", "Bone Index"); + registerMultiPseudonym("Points", "Strip", "Point"); + registerMultiPseudonym("RGB Image Data", "RGB X", "RGB Y"); + registerMultiPseudonym("RGBA Image Data", "RGBA X", "RGBA Y"); + registerMultiPseudonym("Strips", "Strip", "Point"); + registerMultiPseudonym("UV Sets", "UV Set", "UV"); + registerMultiPseudonym("Vertex Weights", "Vertex", "Weight"); +} + +inline QString resolveArrayPseudonym( const QHash & pseudonymMap, const NifItem * item ) +{ + return pseudonymMap.value( item->name(), item->name() ) + " " + QString::number( item->row() ); } //! @file nifmodel.cpp The NIF data model. @@ -1054,20 +1215,30 @@ QVariant NifModel::data( const QModelIndex & index, int role ) const if ( ndr ) return iname; - if ( item->hasStrType("NiBlock") ) + if ( isNiBlock(item) ) return QString::number( getBlockNumber(item) ) + " " + iname; - else if ( isArrayEx( item->parent() ) ) { - auto arrayName = arrayPseudonyms.value(iname); - if ( arrayName.isEmpty() ) { - if ( item->hasName("UV Sets") ) - arrayName = QString( item->isVector2() ? "UV" : "UV Set" ); - else - arrayName = iname; - } - return arrayName + " " + QString::number( item->row() ); + + auto p = item->parent(); + if ( p && p->isArray() && !p->isBinary() ) { + // Is it a 2nd level array of a multi-array? + if ( p->isMultiArray() ) + return resolveArrayPseudonym( multiArrayPseudonyms1, item ); + + // Is it an item (3rd level) of a multi-array? + auto pp = p->parent(); + if ( pp && pp->isMultiArray() ) + return resolveArrayPseudonym( multiArrayPseudonyms2, item ); + + // Is it an item of an non-binary array? + return resolveArrayPseudonym( arrayPseudonyms, item ); } - return " " + iname; + // Gavrant: not sure why the previous code prepended the item's name with a whitespace. + // Let's leave that whitespace for first level items, but omit it for their subitems to save a bit of screen space. + if ( !p || !p->parent() || p->parent() == root ) + return " " + iname; + + return iname; } break; case TypeCol: diff --git a/src/model/nifmodel.h b/src/model/nifmodel.h index 59f49dd48..48b3b037f 100644 --- a/src/model/nifmodel.h +++ b/src/model/nifmodel.h @@ -722,9 +722,6 @@ public slots: static QHash blocks; static QMap blockHashes; - static QHash arrayPseudonyms; - static void setupArrayPseudonyms(); - private: struct Settings { From ed1d9b3d18ad61945efafa69956f6841aa69af0c Mon Sep 17 00:00:00 2001 From: Jon <170077+hexabits@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:18:16 -0400 Subject: [PATCH 095/118] GLView scale for Starfield --- lib/json.hpp | 24608 +++++++++++++++++++++++++++++++++++++++++++++++ src/glview.cpp | 22 +- src/glview.h | 2 + 3 files changed, 24622 insertions(+), 10 deletions(-) create mode 100644 lib/json.hpp diff --git a/lib/json.hpp b/lib/json.hpp new file mode 100644 index 000000000..f44b20163 --- /dev/null +++ b/lib/json.hpp @@ -0,0 +1,24608 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows to call any std function as if (e.g. with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +static void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +inline constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#include // tuple + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& + is_complete_type < + detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template