From 58418b33b5a626aba8cf30698dc509346b289a57 Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:08:43 +0200 Subject: [PATCH 1/7] Minor code changes --- src/spells/filerename.cpp | 2 +- src/spells/meshfilecopy.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spells/filerename.cpp b/src/spells/filerename.cpp index 45da4998..e0918971 100644 --- a/src/spells/filerename.cpp +++ b/src/spells/filerename.cpp @@ -108,7 +108,7 @@ QModelIndex spResourceRename::cast ( NifModel * nif, const QModelIndex & index ) QRegularExpression filterPattern( le3->text().trimmed(), QRegularExpression::CaseInsensitiveOption ); for ( int b = 0; b < nif->getBlockCount(); b++ ) { - NifItem * item = nif->getBlockItem( quint32(b) ); + NifItem * item = nif->getBlockItem( qint32(b) ); if ( item ) renamePaths( nif, item, searchPattern, replacementString, filterPattern ); } diff --git a/src/spells/meshfilecopy.cpp b/src/spells/meshfilecopy.cpp index 301ad897..9efec3ae 100644 --- a/src/spells/meshfilecopy.cpp +++ b/src/spells/meshfilecopy.cpp @@ -211,7 +211,7 @@ QModelIndex spResourceCopy::cast(NifModel* nif, const QModelIndex& index) QString projectName = sanitizeFileName(le2->text().trimmed().remove(QRegularExpression("^[\\\\/]+|[\\\\/]+$"))); for (int b = 0; b < nif->getBlockCount(); b++) { - NifItem* item = nif->getBlockItem(quint32(b)); + NifItem* item = nif->getBlockItem(qint32(b)); if (item) copyPaths(nif, item, authorPrefix, projectName, nif->getFolder()); } From ee9bac8e52548a5d902f5bde9a304fef32c7623f Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:10:38 +0200 Subject: [PATCH 2/7] spBrowseMaterialPath: quick access to spEditStringIndex --- src/spells/headerstring.cpp | 46 ++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/spells/headerstring.cpp b/src/spells/headerstring.cpp index 0fc2ad0e..4d825085 100644 --- a/src/spells/headerstring.cpp +++ b/src/spells/headerstring.cpp @@ -206,6 +206,48 @@ void spEditStringIndex::browseMaterial( QLineEdit * le, const NifModel * nif ) REGISTER_SPELL( spEditStringIndex ) +//! Choose a material path +class spBrowseMaterialPath final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Choose" ); } + QString page() const override final { return Spell::tr( "Material" ); } + QIcon icon() const override final + { + return QIcon(); + } + bool constant() const override final { return true; } + bool instant() const override final { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + if ( !( nif && nif->getBSVersion() >= 130 && index.isValid() ) ) + return false; + if ( nif->getBSVersion() >= 170 ) + return nif->blockInherits( index, "BSGeometry" ) || nif->blockInherits( index, "BSLightingShaderProperty" ); + return ( nif->blockInherits( index, "BSTriShape" ) || nif->blockInherits( index, "BSLightingShaderProperty" ) + || nif->blockInherits( index, "BSEffectShaderProperty" ) ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + QModelIndex idx = index; + if ( nif->blockInherits( idx, ( nif->getBSVersion() < 170 ? "BSTriShape" : "BSGeometry" ) ) ) + idx = nif->getBlockIndex( nif->getLink( idx, "Shader Property" ) ); + if ( nif->blockInherits( idx, "BSShaderProperty" ) ) + idx = nif->getIndex( idx, "Name" ); + else + return index; + if ( idx.isValid() ) { + spEditStringIndex sp; + (void) sp.cast( nif, idx ); + } + return index; + } +}; + +REGISTER_SPELL( spBrowseMaterialPath ) + //! Browse a material path stored as header string class spBrowseHeaderMaterialPath final : public Spell { @@ -233,7 +275,9 @@ class spBrowseHeaderMaterialPath final : public Spell if ( !( item && item->valueType() == NifValue::tSizedString ) ) return false; QString s( item->getValueAsString() ); - return ( s.endsWith( ".bgsm", Qt::CaseInsensitive ) || s.endsWith( ".bgem", Qt::CaseInsensitive ) || s.endsWith( ".mat", Qt::CaseInsensitive ) ); + if ( nif->getBSVersion() < 170 ) + return ( s.endsWith( ".bgsm", Qt::CaseInsensitive ) || s.endsWith( ".bgem", Qt::CaseInsensitive ) ); + return ( s.endsWith( ".mat", Qt::CaseInsensitive ) || s == "MATERIAL_PATH" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final From 00c3cd4d7d739fa8e1bd4ea15f0bb0091ee644a7 Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:11:48 +0200 Subject: [PATCH 3/7] New setting to convert Starfield models to internal geometry on load --- src/model/nifmodel.cpp | 16 ++++++++++++++-- src/spells/fileextract.cpp | 23 ++++++++++++++-------- src/ui/settingsgeneral.ui | 39 ++++++++++++++++++++++++++++++-------- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/model/nifmodel.cpp b/src/model/nifmodel.cpp index 5d63c016..eebd39be 100644 --- a/src/model/nifmodel.cpp +++ b/src/model/nifmodel.cpp @@ -1813,10 +1813,18 @@ static QString getNIFDataPath( const char * pathName ) return QString(); } +class spMeshFileImport +{ +public: + static bool processAllItems( NifModel * nif ); +}; + bool NifModel::load( QIODevice & device, const char* fileName ) { QSettings settings; bool ignoreSize = settings.value( "Ignore Block Size", true ).toBool(); + bool convertSFMeshes = + settings.value( "Settings/Nif/Convert Starfield meshes to internal geometry on load", true ).toBool(); clear(); @@ -2045,6 +2053,10 @@ bool NifModel::load( QIODevice & device, const char* fileName ) //qDebug() << t.msecsTo( QTime::currentTime() ); reset(); // notify model views that a significant change to the data structure has occurded + + if ( getBSVersion() >= 170 && convertSFMeshes ) + spMeshFileImport::processAllItems( this ); + return true; } @@ -2569,8 +2581,8 @@ void NifModel::updateLinks( int block ) for ( int c = 0; c < n; c++ ) { if ( !hasrefs[c] ) { const NifItem * b; - if ( bsVersion >= 151 && ( b = getBlockItem( quint32(c) ) ) != nullptr && b->name() == "BSShaderTextureSet" ) { - if ( c > 0 && ( b = getBlockItem( quint32(c - 1) ) ) != nullptr && b->name() == "BSLightingShaderProperty" ) + if ( bsVersion >= 151 && ( b = getBlockItem( qint32(c) ) ) != nullptr && b->name() == "BSShaderTextureSet" ) { + if ( c > 0 && ( b = getBlockItem( qint32(c - 1) ) ) != nullptr && b->name() == "BSLightingShaderProperty" ) childLinks[c - 1] += c; } else { rootLinks.append( c ); diff --git a/src/spells/fileextract.cpp b/src/spells/fileextract.cpp index 153bd1bf..feecbdc9 100644 --- a/src/spells/fileextract.cpp +++ b/src/spells/fileextract.cpp @@ -253,7 +253,7 @@ QModelIndex spExtractAllResources::cast( NifModel * nif, const QModelIndex & ind std::set< std::string > fileSet; for ( int b = 0; b < nif->getBlockCount(); b++ ) { - const NifItem * item = nif->getBlockItem( quint32(b) ); + const NifItem * item = nif->getBlockItem( qint32(b) ); if ( item ) findPaths( fileSet, nif, item ); } @@ -400,7 +400,7 @@ QModelIndex spMeshFileExport::cast( NifModel * nif, const QModelIndex & index ) meshesConverted = processItem( nif, item, outputDirectory ); } else { for ( int b = 0; b < nif->getBlockCount(); b++ ) - meshesConverted |= processItem( nif, nif->getBlockItem( quint32(b) ), outputDirectory ); + meshesConverted |= processItem( nif, nif->getBlockItem( qint32(b) ), outputDirectory ); } if ( meshesConverted ) Game::GameManager::close_resources(); @@ -432,7 +432,8 @@ class spMeshFileImport final : public Spell return ( item->name() == "BSGeometry" && ( nif->get(item, "Flags") & 0x0200 ) == 0 ); } - bool processItem( NifModel * nif, NifItem * item ); + static bool processItem( NifModel * nif, NifItem * item ); + static bool processAllItems( NifModel * nif ); QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final; }; @@ -486,6 +487,14 @@ bool spMeshFileImport::processItem( NifModel * nif, NifItem * item ) return true; } +bool spMeshFileImport::processAllItems( NifModel * nif ) +{ + bool r = false; + for ( int b = 0; b < nif->getBlockCount(); b++ ) + r = r | processItem( nif, nif->getBlockItem( qint32(b) ) ); + return r; +} + QModelIndex spMeshFileImport::cast( NifModel * nif, const QModelIndex & index ) { if ( !( nif && nif->getBSVersion() >= 170 ) ) @@ -495,12 +504,10 @@ QModelIndex spMeshFileImport::cast( NifModel * nif, const QModelIndex & index ) if ( item && !( item->name() == "BSGeometry" && (nif->get(item, "Flags") & 0x0200) == 0 ) ) return index; - if ( item ) { + if ( item ) processItem( nif, item ); - } else { - for ( int b = 0; b < nif->getBlockCount(); b++ ) - processItem( nif, nif->getBlockItem( quint32(b) ) ); - } + else + processAllItems( nif ); return index; } diff --git a/src/ui/settingsgeneral.ui b/src/ui/settingsgeneral.ui index c9504333..c8089046 100644 --- a/src/ui/settingsgeneral.ui +++ b/src/ui/settingsgeneral.ui @@ -89,7 +89,7 @@ - + 0 @@ -139,7 +139,7 @@ - + Misc @@ -162,7 +162,7 @@ 20 - 40 + 20 @@ -273,7 +273,30 @@ 20 - 40 + 20 + + + + + + + + Convert Starfield meshes to internal geometry on load + + + true + + + + + + + Qt::Vertical + + + + 20 + 20 @@ -343,14 +366,14 @@ - + Qt::Vertical 20 - 40 + 20 @@ -406,14 +429,14 @@ - + Qt::Vertical 20 - 40 + 20 From 94db91927f233230d95a11e31ced31ee6dcaa337 Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:13:53 +0200 Subject: [PATCH 4/7] Added new spell to copy a Starfield material with new resource IDs --- CHANGELOG.md | 5 + src/spells/sfmatexport.cpp | 200 ++++++++++++++++++++++++++++++++----- 2 files changed, 181 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba8312c..772fb08e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ == CHANGELOG == +* Added a new spell (Material/Clone and Copy to Clipboard) that is available in the right click menu on the name of a Starfield BSLightingShaderProperty, and copies the material to the clipboard in JSON (.mat) format, but with randomly generated new resource IDs. This is useful when creating a new material using an existing one as the base. +* Starfield NIF files are now automatically converted to internal geometry on load. This is often useful because most geometry editing functionality is not implemented for external .mesh files, but it can be disabled in the general settings under 'NIF'. +* New 'Material' spell menu for BSGeometry, BSTriShape (Fallout 4 and 76), BSLightingShaderProperty and BSEffectShaderProperty blocks, with shortcuts to material spells. +* Bug fixes in drawing the normals or tangents of a selected Starfield shape, the Starfield grid scale, and simplifying skinned meshes. + #### NifSkope-2.0.dev9-20240716 * Implemented Starfield glTF import, fixes and improvements to glTF export. diff --git a/src/spells/sfmatexport.cpp b/src/spells/sfmatexport.cpp index ae8b4267..b8805e66 100644 --- a/src/spells/sfmatexport.cpp +++ b/src/spells/sfmatexport.cpp @@ -2,14 +2,17 @@ #include "libfo76utils/src/material.hpp" #include "model/nifmodel.h" +#include +#include + #include //! Export Starfield material as JSON format .mat file class spStarfieldMaterialExport final : public Spell { public: - QString name() const override final { return Spell::tr( "Copy JSON Material to Clipboard" ); } - QString page() const override final { return Spell::tr( "" ); } + QString name() const override final { return Spell::tr( "Copy JSON to Clipboard" ); } + QString page() const override final { return Spell::tr( "Material" ); } QIcon icon() const override final { return QIcon(); @@ -19,37 +22,186 @@ class spStarfieldMaterialExport final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - if ( nif && nif->getBSVersion() >= 170 ) { - const NifItem * item = nif->getItem( index ); - if ( item && item->parent() && item->name() == "Name" && item->parent()->name() == "BSLightingShaderProperty" ) { - return true; - } - } + if ( nif && nif->getBSVersion() >= 170 && index.isValid() ) + return nif->blockInherits( index, "BSGeometry" ) || nif->blockInherits( index, "BSLightingShaderProperty" ); return false; } + static std::mt19937_64 rndGen; + static bool rndGenInitFlag; + + static BSMaterialsCDB::BSResourceID generateResourceID( + const BSMaterialsCDB::BSResourceID * id = nullptr, std::set< BSMaterialsCDB::BSResourceID > * idsUsed = nullptr, + const BSMaterialsCDB * matDB = nullptr ); + static void generateResourceIDs( std::string & matFileData, const BSMaterialsCDB * matDB = nullptr ); + static void processItem( NifModel * nif, const QModelIndex & index, bool generateIDs = false ); + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - QString materialPath( nif->resolveString( nif->getItem( index ) ) ); - if ( !materialPath.isEmpty() ) { - std::string matFilePath( Game::GameManager::get_full_path( materialPath, "materials/", ".mat" ) ); - std::string matFileData; - try { - CE2MaterialDB * materials = nif->getCE2Materials(); - if ( materials ) { - (void) materials->loadMaterial( matFilePath ); - materials->getJSONMaterial( matFileData, matFilePath ); - QClipboard *clipboard; - if ( !matFileData.empty() && ( clipboard = QGuiApplication::clipboard() ) != nullptr ) - clipboard->setText( QString::fromStdString( matFileData ) ); - } - } catch ( std::exception& e ) { - QMessageBox::critical( nullptr, "NifSkope error", QString("Error loading material '%1': %2" ).arg( materialPath ).arg( e.what() ) ); + processItem( nif, index, false ); + return index; + } +}; + +std::mt19937_64 spStarfieldMaterialExport::rndGen; +bool spStarfieldMaterialExport::rndGenInitFlag = false; + +BSMaterialsCDB::BSResourceID spStarfieldMaterialExport::generateResourceID( + const BSMaterialsCDB::BSResourceID * id, std::set< BSMaterialsCDB::BSResourceID > * idsUsed, + const BSMaterialsCDB * matDB ) +{ + BSMaterialsCDB::BSResourceID newID( 0x00040000U, 0U, 0U ); + if ( id ) { + newID = *id; + if ( !( newID.ext & 0x80808080U ) ) + return newID; + } + if ( !rndGenInitFlag ) [[unlikely]] { + rndGenInitFlag = true; + unsigned long long s1, s2; +#if ENABLE_X86_64_SIMD >= 4 + __builtin_ia32_rdrand64_step( &s1 ); + __builtin_ia32_rdrand64_step( &s2 ); +#else + auto t = std::chrono::steady_clock::now().time_since_epoch(); + s1 = (unsigned long long) std::chrono::duration_cast< std::chrono::microseconds >( t ).count(); + s2 = timerFunctionRDTSC(); +#endif + std::seed_seq s{ std::uint32_t(s1), std::uint32_t(s1 >> 32), std::uint32_t(s2), std::uint32_t(s2 >> 32) }; + rndGen.seed( s ); + } + while ( true ) { + std::uint64_t tmp1 = rndGen(); + std::uint64_t tmp2 = rndGen(); + newID.file = ( newID.file & 0xFFFC0000U ) | ( std::uint32_t(tmp1) & 0x0003FFFFU ); + newID.ext = 0xA0000000U | std::uint32_t( tmp1 >> 37 ); + newID.dir = std::uint32_t( tmp2 ); + if ( matDB && matDB->getMaterial( newID ) ) [[unlikely]] + continue; + if ( idsUsed && !idsUsed->insert( newID ).second ) [[unlikely]] + continue; + break; + } + return newID; +} + +void spStarfieldMaterialExport::generateResourceIDs( std::string & matFileData, const BSMaterialsCDB * matDB ) +{ + std::set< BSMaterialsCDB::BSResourceID > idsUsed; + std::map< BSMaterialsCDB::BSResourceID, BSMaterialsCDB::BSResourceID > idsDefined; + for ( size_t i = 0; ( i + 32 ) <= matFileData.length(); i++ ) { + if ( !( matFileData[i] == '"' && matFileData[i + 31] == '"' ) ) + continue; + std::string_view s( matFileData.c_str() + ( i + 1 ), 30 ); + if ( !( s.starts_with( "res:" ) && s[12] == ':' && s[21] == ':' ) ) + continue; + BSMaterialsCDB::BSResourceID tmp( 0, 0, 0 ); + tmp.fromJSONString( s ); + if ( tmp ) { + idsUsed.insert( tmp ); + if ( i >= 13 && std::string_view( matFileData.c_str() + ( i - 13 ), 13 ) == "\n \"ID\": " ) + idsDefined.emplace( tmp, tmp ); + } + } + for ( auto & i : idsDefined ) + i.second = generateResourceID( &(i.first), &idsUsed, matDB ); + bool warningFlag = false; + for ( size_t i = 0; ( i + 32 ) <= matFileData.length(); i++ ) { + if ( !( matFileData[i] == '"' && matFileData[i + 31] == '"' ) ) + continue; + std::string_view s( matFileData.c_str() + ( i + 1 ), 30 ); + if ( !( s.starts_with( "res:" ) && s[12] == ':' && s[21] == ':' ) ) + continue; + BSMaterialsCDB::BSResourceID tmp( 0, 0, 0 ); + tmp.fromJSONString( s ); + if ( !tmp ) + continue; + auto j = idsDefined.find( tmp ); + if ( j == idsDefined.end() || j->second == tmp ) { + if ( j == idsDefined.end() ) + warningFlag = true; + continue; + } + tmp = j->second; + char * t = matFileData.data() + ( i + 5 ); + for ( size_t k = 0; k < 26; k++ ) { + if ( k == 8 || k == 17 ) { + t[k] = ':'; + continue; } + char c = char( tmp.dir >> 28 ); + tmp.dir = ( tmp.dir << 4 ) | ( tmp.file >> 28 ); + tmp.file = ( tmp.file << 4 ) | ( tmp.ext >> 28 ); + tmp.ext = tmp.ext << 4; + t[k] = c + ( (unsigned char) c < 10 ? '0' : '7' ); } + } + if ( warningFlag ) { + QMessageBox::warning( nullptr, "NifSkope warning", QString("The material references undefined or external resource IDs") ); + } +} + +void spStarfieldMaterialExport::processItem( NifModel * nif, const QModelIndex & index, bool generateIDs ) +{ + QModelIndex idx = index; + if ( nif->blockInherits( idx, "BSGeometry" ) ) + idx = nif->getBlockIndex( nif->getLink( idx, "Shader Property" ) ); + if ( nif->blockInherits( idx, "BSLightingShaderProperty" ) ) + idx = nif->getIndex( idx, "Name" ); + else + return; + if ( !idx.isValid() ) + return; + + QString materialPath( nif->resolveString( nif->getItem( idx ) ) ); + if ( !materialPath.isEmpty() ) { + std::string matFilePath( Game::GameManager::get_full_path( materialPath, "materials/", ".mat" ) ); + std::string matFileData; + try { + CE2MaterialDB * materials = nif->getCE2Materials(); + if ( materials ) { + (void) materials->loadMaterial( matFilePath ); + materials->getJSONMaterial( matFileData, matFilePath ); + if ( generateIDs ) + generateResourceIDs( matFileData, materials ); + QClipboard *clipboard; + if ( !matFileData.empty() && ( clipboard = QGuiApplication::clipboard() ) != nullptr ) + clipboard->setText( QString::fromStdString( matFileData ) ); + } + } catch ( std::exception& e ) { + QMessageBox::critical( nullptr, "NifSkope error", QString("Error loading material '%1': %2" ).arg( materialPath ).arg( e.what() ) ); + } + } +} + +REGISTER_SPELL( spStarfieldMaterialExport ) + +//! Clone Starfield material with new resource IDs as JSON format .mat file +class spStarfieldMaterialClone final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Clone and Copy to Clipboard" ); } + QString page() const override final { return Spell::tr( "Material" ); } + QIcon icon() const override final + { + return QIcon(); + } + bool constant() const override final { return true; } + bool instant() const override final { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + if ( nif && nif->getBSVersion() >= 170 && index.isValid() ) + return nif->blockInherits( index, "BSGeometry" ) || nif->blockInherits( index, "BSLightingShaderProperty" ); + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + spStarfieldMaterialExport::processItem( nif, index, true ); return index; } }; -REGISTER_SPELL( spStarfieldMaterialExport ) +REGISTER_SPELL( spStarfieldMaterialClone ) From 57bf607a9173b0640027f69fc86b15e2ac9503fe Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Fri, 19 Jul 2024 19:04:47 +0200 Subject: [PATCH 5/7] Minor changes in spStarfieldMaterialExport --- src/spells/sfmatexport.cpp | 41 ++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/spells/sfmatexport.cpp b/src/spells/sfmatexport.cpp index b8805e66..7be6aa09 100644 --- a/src/spells/sfmatexport.cpp +++ b/src/spells/sfmatexport.cpp @@ -33,6 +33,7 @@ class spStarfieldMaterialExport final : public Spell static BSMaterialsCDB::BSResourceID generateResourceID( const BSMaterialsCDB::BSResourceID * id = nullptr, std::set< BSMaterialsCDB::BSResourceID > * idsUsed = nullptr, const BSMaterialsCDB * matDB = nullptr ); + static inline bool readResourceID( BSMaterialsCDB::BSResourceID & id, const std::string_view & s ); static void generateResourceIDs( std::string & matFileData, const BSMaterialsCDB * matDB = nullptr ); static void processItem( NifModel * nif, const QModelIndex & index, bool generateIDs = false ); @@ -85,6 +86,26 @@ BSMaterialsCDB::BSResourceID spStarfieldMaterialExport::generateResourceID( return newID; } +inline bool spStarfieldMaterialExport::readResourceID( BSMaterialsCDB::BSResourceID & id, const std::string_view & s ) +{ + id = BSMaterialsCDB::BSResourceID( 0, 0, 0 ); + if ( !( s.length() == 30 && s.starts_with( "res:" ) && s[12] == ':' && s[21] == ':' ) ) + return false; + for ( size_t i = 4; i < 30; i++ ) { + if ( i == 12 || i == 21 ) + continue; + char c = s[i]; + if ( ( c >= 'A' && c <= 'F' ) || ( c >= 'a' && c <= 'f' ) ) + c = c + 9; + else if ( !( c >= '0' && c <= '9' ) ) + return false; + id.dir = ( id.dir << 4 ) | ( id.file >> 28 ); + id.file = ( id.file << 4 ) | ( id.ext >> 28 ); + id.ext = ( id.ext << 4 ) | std::uint32_t( c & 0x0F ); + } + return bool( id ); +} + void spStarfieldMaterialExport::generateResourceIDs( std::string & matFileData, const BSMaterialsCDB * matDB ) { std::set< BSMaterialsCDB::BSResourceID > idsUsed; @@ -93,15 +114,12 @@ void spStarfieldMaterialExport::generateResourceIDs( std::string & matFileData, if ( !( matFileData[i] == '"' && matFileData[i + 31] == '"' ) ) continue; std::string_view s( matFileData.c_str() + ( i + 1 ), 30 ); - if ( !( s.starts_with( "res:" ) && s[12] == ':' && s[21] == ':' ) ) + BSMaterialsCDB::BSResourceID tmp; + if ( !readResourceID( tmp, s ) ) continue; - BSMaterialsCDB::BSResourceID tmp( 0, 0, 0 ); - tmp.fromJSONString( s ); - if ( tmp ) { - idsUsed.insert( tmp ); - if ( i >= 13 && std::string_view( matFileData.c_str() + ( i - 13 ), 13 ) == "\n \"ID\": " ) - idsDefined.emplace( tmp, tmp ); - } + idsUsed.insert( tmp ); + if ( i >= 13 && std::string_view( matFileData.c_str() + ( i - 13 ), 13 ) == "\n \"ID\": " ) + idsDefined.emplace( tmp, tmp ); } for ( auto & i : idsDefined ) i.second = generateResourceID( &(i.first), &idsUsed, matDB ); @@ -110,11 +128,8 @@ void spStarfieldMaterialExport::generateResourceIDs( std::string & matFileData, if ( !( matFileData[i] == '"' && matFileData[i + 31] == '"' ) ) continue; std::string_view s( matFileData.c_str() + ( i + 1 ), 30 ); - if ( !( s.starts_with( "res:" ) && s[12] == ':' && s[21] == ':' ) ) - continue; - BSMaterialsCDB::BSResourceID tmp( 0, 0, 0 ); - tmp.fromJSONString( s ); - if ( !tmp ) + BSMaterialsCDB::BSResourceID tmp; + if ( !readResourceID( tmp, s ) ) continue; auto j = idsDefined.find( tmp ); if ( j == idsDefined.end() || j->second == tmp ) { From 1e3aa8a0f96008ea19439b636bf7be9ecd05eacd Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Sat, 20 Jul 2024 14:26:40 +0200 Subject: [PATCH 6/7] Updated documentation --- CHANGELOG.md | 2 +- README.md | 4 ++-- build/README.md.in | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 772fb08e..7e55fa15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ == CHANGELOG == -* Added a new spell (Material/Clone and Copy to Clipboard) that is available in the right click menu on the name of a Starfield BSLightingShaderProperty, and copies the material to the clipboard in JSON (.mat) format, but with randomly generated new resource IDs. This is useful when creating a new material using an existing one as the base. +* Added a new spell (Material/Clone and Copy to Clipboard) that is available in the right click menu on the name of a Starfield BSLightingShaderProperty, and copies the material to the clipboard in JSON (.mat) format, but with randomly generated new resource IDs. This is useful when creating a new material using an existing one as the base. **Note:** if a modified material is saved to a different path, the first BSComponentDB::CTName needs to be updated to reflect the new base name of the .mat file. Additionally, the asset database needs to be cleared in NifSkope (Alt+Q) to ensure that any new materials are loaded and used. * Starfield NIF files are now automatically converted to internal geometry on load. This is often useful because most geometry editing functionality is not implemented for external .mesh files, but it can be disabled in the general settings under 'NIF'. * New 'Material' spell menu for BSGeometry, BSTriShape (Fallout 4 and 76), BSLightingShaderProperty and BSEffectShaderProperty blocks, with shortcuts to material spells. * Bug fixes in drawing the normals or tangents of a selected Starfield shape, the Starfield grid scale, and simplifying skinned meshes. diff --git a/README.md b/README.md index 525b2672..5f3aeaab 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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/NV/4/76, Starfield, Civilization IV, and more. -This is an experimental fork of 2.0.dev9 with partial support for Starfield materials, and improvements and fixes to Fallout 76 rendering. +This is an experimental fork of 2.0.dev9 with many improvements to Starfield support, and a number of fixes to issues related to older games. See [CHANGELOG.md](https://github.com/fo76utils/nifskope/blob/develop/CHANGELOG.md) for details. ### Download @@ -42,7 +42,7 @@ Anyone can report issues specific to this fork at [GitHub](https://github.com/fo ### Contribute -You can fork the latest source from [GitHub](https://github.com/niftools/nifskope). See [Fork A Repo](https://help.github.com/articles/fork-a-repo) on how to send your contributions upstream. To grab all submodules, make sure to use `--recursive` like so: +You can fork the latest source from [GitHub](https://github.com/fo76utils/nifskope). See [Fork A Repo](https://help.github.com/articles/fork-a-repo) on how to send your contributions upstream. To grab all submodules, make sure to use `--recursive` like so: ``` git clone --recursive git://github.com//nifskope.git diff --git a/build/README.md.in b/build/README.md.in index ee98d595..ce7d340d 100644 --- a/build/README.md.in +++ b/build/README.md.in @@ -2,7 +2,7 @@ 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/NV/4/76, Starfield, Civilization IV, and more. -This is an experimental fork of 2.0.dev9 with partial support for Starfield materials, and improvements and fixes to Fallout 76 rendering. +This is an experimental fork of 2.0.dev9 with many improvements to Starfield support, and a number of fixes to issues related to older games. See [CHANGELOG.md](https://github.com/fo76utils/nifskope/blob/develop/CHANGELOG.md) for details. ### Download @@ -42,7 +42,7 @@ Anyone can report issues specific to this fork at [GitHub](https://github.com/fo ### Contribute -You can fork the latest source from [GitHub](https://github.com/niftools/nifskope). See [Fork A Repo](https://help.github.com/articles/fork-a-repo) on how to send your contributions upstream. To grab all submodules, make sure to use `--recursive` like so: +You can fork the latest source from [GitHub](https://github.com/fo76utils/nifskope). See [Fork A Repo](https://help.github.com/articles/fork-a-repo) on how to send your contributions upstream. To grab all submodules, make sure to use `--recursive` like so: ``` git clone --recursive git://github.com//nifskope.git From ac14e2f5fe5b7920e426c4d0ac57c3193eed42ac Mon Sep 17 00:00:00 2001 From: fo76utils <87907510+fo76utils@users.noreply.github.com> Date: Sat, 20 Jul 2024 19:33:28 +0200 Subject: [PATCH 7/7] GLView: fixed walk scale --- src/glview.cpp | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/glview.cpp b/src/glview.cpp index 77c6b45b..90f25075 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -415,7 +415,7 @@ void GLView::glProjection( int x, int y ) bs |= BoundSphere( scene->view * Vector3(), axis ); } - float bounds = (bs.radius > 1024.0 * scale()) ? bs.radius : 1024.0 * scale(); + float bounds = std::max< float >( bs.radius, 1024.0f * scale() ); GLdouble nr = fabs( bs.center[2] ) - bounds * 1.5; @@ -423,23 +423,13 @@ void GLView::glProjection( int x, int y ) if ( perspectiveMode || (view == ViewWalk) ) { // Perspective View - if ( nr < 1.0 * scale() ) - nr = 1.0 * scale(); - if ( fr < 2.0 * scale() ) - fr = 2.0 * scale(); - if ( nr > fr ) { // add: swap them when needed - GLfloat tmp = nr; - nr = fr; - fr = tmp; - } - - if ( (fr - nr) < 0.00001f ) { - // add: ensure distance - nr = 1.0 * scale(); - fr = 2.0 * scale(); + std::swap( nr, fr ); } + nr = std::max< GLdouble >( nr, scale() ); + // ensure distance + fr = std::max< GLdouble >( fr, nr + scale() ); GLdouble h2 = tan( ( cfg.fov / Zoom ) / 360 * M_PI ) * nr; GLdouble w2 = h2 * aspect; @@ -1078,7 +1068,7 @@ void GLView::setCenter() // Center on entire mesh BoundSphere bs = scene->bounds(); - if ( bs.radius < 1 * scale() ) + if ( bs.radius < scale() ) bs.radius = 1024.0 * scale(); setDistance( bs.radius * 1.2 ); @@ -1924,7 +1914,7 @@ void GLView::mouseReleaseEvent( QMouseEvent * event ) void GLView::wheelEvent( QWheelEvent * event ) { if ( view == ViewWalk ) - mouseMov += Vector3( 0, 0, double( event->angleDelta().y() ) / 8.0 ) * scale(); + mouseMov += Vector3( 0, 0, double( event->angleDelta().y() ) / 4.0 ) * scale(); else { if (event->angleDelta().y() < 0)