diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f533a1a..ad6dc9a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
== CHANGELOG ==
+* Added new spell (Batch/Copy and Rename all Meshes) by 1OfAKindMods to copy and rename all .mesh files used by a Starfield model. Note that the spell expects a loose NIF file, if the model is from an archive, the renamed mesh files are written under the NifSkope installation directory.
+* The right click menu on header strings that end with .bgsm, .bgem or .mat has a new option to browse materials. Updating the view (Alt+U) may be needed for material path changes to take effect.
+* Added limited support for rendering translucent Starfield materials.
+* Fixed the contrast parameter of Starfield blenders that is inverted in the material data.
+* The default height scale for Starfield parallax occlusion mapping is now 0.0 in the render settings, as height textures do not actually work in the game.
+
#### NifSkope-2.0.dev9-20240616
* Starfield material rendering improvements, including support for layered materials with PositionContrast blend mode. CharacterCombine mode is also rendered, but it is interpreted as linear blending.
diff --git a/build/nif.xml b/build/nif.xml
index 0dcd2017..34d4aabe 100644
--- a/build/nif.xml
+++ b/build/nif.xml
@@ -20,7 +20,7 @@
Skyrim and later.
SSE and later.
Fallout 76 and later.
- Starfield and later.
+ Starfield and later.
SSE only.
Fallout 4 strictly, excluding stream 132 and 139 in dev files.
Fallout 4/76 including dev files.
@@ -29,7 +29,7 @@
Bethesda 132 and later.
Bethesda 152 and later.
Fallout 76 stream 155 only.
- SSE, FO4, FO76
+ SSE, FO4, FO76
FO4, FO76
Bethesda 20.2 only.
Divinity 2
@@ -215,7 +215,7 @@
{{Fallout 4}}
Fallout 4 (LS_Mirelurk.nif, Screen.nif)
{{Fallout 76}}
- {{Starfield}}
+ {{Starfield}}
{{Empire Earth III}}, {{FFT Online}}, Atlantica Online, IRIS Online, Wizard101
QQSpeed
Emerge
@@ -1967,8 +1967,8 @@
-
-
+
+
diff --git a/lib/libfo76utils b/lib/libfo76utils
index a03424b8..f24877b0 160000
--- a/lib/libfo76utils
+++ b/lib/libfo76utils
@@ -1 +1 @@
-Subproject commit a03424b8f76ed47763c1cd2db72d876aeba05663
+Subproject commit f24877b0bee9acdf164ecd91e3e6dd023df8abec
diff --git a/res/shaders/stf_default.frag b/res/shaders/stf_default.frag
index 6cd43b59..bd6da3ef 100644
--- a/res/shaders/stf_default.frag
+++ b/res/shaders/stf_default.frag
@@ -359,6 +359,9 @@ float getBlenderMask(int n)
vec2 parallaxMapping( int n, vec3 V, vec2 offset )
{
+ if ( parallaxOcclusionSettings.z < 0.0005 )
+ return offset; // disabled
+
// determine optimal height of each layer
float layerHeight = 1.0 / mix( parallaxOcclusionSettings.y, parallaxOcclusionSettings.x, abs(V.z) );
@@ -446,6 +449,7 @@ void main()
vec3 pbrMap = vec3(0.75, 0.0, 1.0); // roughness, metalness, AO
float alpha = 1.0;
vec3 emissive = vec3(0.0);
+ vec3 transmissive = vec3(0.0);
for (int i = 0; i < 4; i++) {
if ( !lm.layersEnabled[i] )
@@ -498,7 +502,7 @@ void main()
if ( lm.blenders[i - 1].blendMode == 2 ) {
float blendPosition = lm.blenders[i - 1].floatParams[2];
float blendContrast = lm.blenders[i - 1].floatParams[3];
- blendContrast = max( (1.0 - blendContrast) * min(blendPosition, 1.0 - blendPosition), 0.001 );
+ blendContrast = max( blendContrast * min(blendPosition, 1.0 - blendPosition), 0.001 );
blendPosition = ( blendPosition - 0.5 ) * 3.17;
blendPosition = ( blendPosition * blendPosition + 1.0 ) * blendPosition + 0.5;
float maskMin = blendPosition - blendContrast;
@@ -581,6 +585,12 @@ void main()
continue;
emissive += getLayerTexture( i, 7, offset ).rgb * tmp.rgb * tmp.a;
}
+
+ if ( lm.layers[i].material.textureSet.textures[8] != 0 ) {
+ // _transmissive.dds
+ if ( lm.translucencySettings.isEnabled && i == lm.translucencySettings.transmittanceSourceLayer )
+ transmissive = vec3( getLayerTexture( i, 8, offset ).r * lm.translucencySettings.transmissiveScale );
+ }
}
normal = normalize( btnMatrix_norm * normal );
@@ -667,8 +677,6 @@ void main()
float ao = pbrMap.b;
refl *= f * envLUT.g * ao;
- // TODO: translucency
-
// Diffuse
color.rgb = diffuse * albedo * D.rgb;
// Ambient
@@ -680,6 +688,17 @@ void main()
// Emissive
color.rgb += emissive;
+ // Transmissive
+ if ( lm.translucencySettings.isEnabled && lm.translucencySettings.isThin ) {
+ transmissive *= albedo * ( vec3(1.0) - f );
+ // TODO: implement flipBackFaceNormalsInViewSpace
+ color.rgb += transmissive * D.rgb * max( -NdotL, 0.0 );
+ if ( hasCubeMap )
+ color.rgb += textureLod( CubeMap2, -normalWS, 0.0 ).rgb * transmissive * A.rgb * ao;
+ else
+ color.rgb += transmissive * A.rgb * ( ao * 0.08 );
+ }
+
color.rgb = tonemap(color.rgb * D.a, A.a);
color.a = baseMap.a * alpha;
diff --git a/res/shaders/stf_default.prog b/res/shaders/stf_default.prog
index 7b613e42..2f1b0263 100644
--- a/res/shaders/stf_default.prog
+++ b/res/shaders/stf_default.prog
@@ -2,7 +2,7 @@
checkgroup begin and
# Starfield
- check HEADER/BS Header/BS Version >= 172
+ check HEADER/BS Header/BS Version >= 170
check BSGeometry
check BSLightingShaderProperty
checkgroup end
diff --git a/res/shaders/stf_effectshader.prog b/res/shaders/stf_effectshader.prog
index a1f5ec62..89c7a68a 100644
--- a/res/shaders/stf_effectshader.prog
+++ b/res/shaders/stf_effectshader.prog
@@ -2,7 +2,7 @@
checkgroup begin and
# Starfield
- check HEADER/BS Header/BS Version == 172
+ check HEADER/BS Header/BS Version >= 170
check BSGeometry
check BSEffectShaderProperty
checkgroup end
diff --git a/src/gamemanager.cpp b/src/gamemanager.cpp
index 983febc7..e5560b47 100644
--- a/src/gamemanager.cpp
+++ b/src/gamemanager.cpp
@@ -430,6 +430,7 @@ GameMode GameManager::get_game( const NifModel * nif )
return FALLOUT_4;
case BSSTREAM_155:
return FALLOUT_76;
+ case BSSTREAM_170:
case BSSTREAM_172:
case BSSTREAM_173:
return STARFIELD;
diff --git a/src/gamemanager.h b/src/gamemanager.h
index bf896623..2f509107 100644
--- a/src/gamemanager.h
+++ b/src/gamemanager.h
@@ -59,6 +59,7 @@ enum BSVersion
BSSTREAM_100 = 100,
BSSTREAM_130 = 130,
BSSTREAM_155 = 155,
+ BSSTREAM_170 = 170,
BSSTREAM_172 = 172,
BSSTREAM_173 = 173
};
diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp
index 4b7d0f97..bb73ef76 100644
--- a/src/gl/glproperty.cpp
+++ b/src/gl/glproperty.cpp
@@ -851,7 +851,7 @@ void BSShaderLightingProperty::updateImpl( const NifModel * nif, const QModelInd
if ( index == iBlock ) {
bsVersion = (unsigned short) nif->getBSVersion();
- if ( bsVersion >= 160 ) {
+ if ( bsVersion >= 170 ) {
setSFMaterial( name );
} else {
if ( bsVersion < 83 )
@@ -1072,7 +1072,7 @@ enum
QString BSShaderLightingProperty::fileName( int id ) const
{
// Starfield (not implemented here)
- if ( bsVersion >= 160 )
+ if ( bsVersion >= 170 )
return QString();
// Fallout 4 or 76 BGSM file
@@ -1241,7 +1241,7 @@ void BSLightingShaderProperty::updateImpl( const NifModel * nif, const QModelInd
BSShaderLightingProperty::updateImpl( nif, index );
if ( index == iBlock ) {
- if ( name.endsWith(".bgsm", Qt::CaseInsensitive) && bsVersion < 160 ) {
+ if ( name.endsWith(".bgsm", Qt::CaseInsensitive) && bsVersion < 170 ) {
setMaterial( new ShaderMaterial( name, nif ) );
if ( bsVersion >= 151 )
const_cast< NifModel * >(nif)->loadFO76Material( index, material );
@@ -1307,7 +1307,7 @@ void BSLightingShaderProperty::updateParams( const NifModel * nif )
{
resetParams();
- if ( bsVersion >= 172 ) {
+ if ( bsVersion >= 170 ) {
setSFMaterial( nif->get( iBlock, "Name" ) );
return;
}
@@ -1482,7 +1482,7 @@ void BSEffectShaderProperty::updateImpl( const NifModel * nif, const QModelIndex
BSShaderLightingProperty::updateImpl( nif, index );
if ( index == iBlock ) {
- if ( name.endsWith(".bgem", Qt::CaseInsensitive) && bsVersion < 160 ) {
+ if ( name.endsWith(".bgem", Qt::CaseInsensitive) && bsVersion < 170 ) {
setMaterial( new EffectMaterial( name, nif ) );
if ( bsVersion >= 151 )
const_cast< NifModel * >(nif)->loadFO76Material( index, material );
diff --git a/src/gl/glshape.cpp b/src/gl/glshape.cpp
index d070cbe4..59665d50 100644
--- a/src/gl/glshape.cpp
+++ b/src/gl/glshape.cpp
@@ -205,7 +205,7 @@ void Shape::updateShader()
else if ( alphaProperty && alphaProperty->hasAlphaBlend() )
drawInSecondPass = true;
else if ( bssp ) {
- if ( bssp->bsVersion >= 160 ) {
+ if ( bssp->bsVersion >= 170 ) {
const CE2Material * sfMat = nullptr;
bssp->getSFMaterial( sfMat, scene->nifModel );
if ( sfMat && ( sfMat->shaderRoute != 0 || (sfMat->flags & CE2Material::Flag_IsDecal) ) )
diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp
index 47975212..eb553e2b 100644
--- a/src/gl/renderer.cpp
+++ b/src/gl/renderer.cpp
@@ -442,7 +442,7 @@ void Renderer::updateSettings()
cfg.useShaders = settings.value( "Settings/Render/General/Use Shaders", true ).toBool();
cfg.sfParallaxMaxSteps = short( settings.value( "Settings/Render/General/Sf Parallax Steps", 200 ).toInt() );
- cfg.sfParallaxScale = settings.value( "Settings/Render/General/Sf Parallax Scale", 0.033f).toFloat();
+ cfg.sfParallaxScale = settings.value( "Settings/Render/General/Sf Parallax Scale", 0.0f).toFloat();
cfg.sfParallaxOffset = settings.value( "Settings/Render/General/Sf Parallax Offset", 0.5f).toFloat();
cfg.cubeMapPathFO76 = settings.value( "Settings/Render/General/Cube Map Path FO 76", "textures/shared/cubemaps/mipblur_defaultoutside1.dds" ).toString();
cfg.cubeMapPathSTF = settings.value( "Settings/Render/General/Cube Map Path STF", "textures/cubemaps/cell_cityplazacube.dds" ).toString();
@@ -957,6 +957,24 @@ bool Renderer::setupProgramSF( Program * prog, Shape * mesh )
prog->uni1b_l( prog->uniLocation("lm.emissiveSettings.isEnabled"), false );
}
+ // translucency settings
+ if ( mat->flags & CE2Material::Flag_Translucency ) {
+ const CE2Material::TranslucencySettings * sp = mat->translucencySettings;
+ prog->uni1b_l( prog->uniLocation("lm.translucencySettings.isEnabled"), sp->isEnabled );
+ prog->uni1b_l( prog->uniLocation("lm.translucencySettings.isThin"), sp->isThin );
+ prog->uni1b_l( prog->uniLocation("lm.translucencySettings.flipBackFaceNormalsInViewSpace"), sp->flipBackFaceNormalsInVS );
+ prog->uni1b_l( prog->uniLocation("lm.translucencySettings.useSSS"), sp->useSSS );
+ prog->uni1f_l( prog->uniLocation("lm.translucencySettings.sssWidth"), sp->sssWidth );
+ prog->uni1f_l( prog->uniLocation("lm.translucencySettings.sssStrength"), sp->sssStrength );
+ prog->uni1f_l( prog->uniLocation("lm.translucencySettings.transmissiveScale"), sp->transmissiveScale );
+ prog->uni1f_l( prog->uniLocation("lm.translucencySettings.transmittanceWidth"), sp->transmittanceWidth );
+ prog->uni1f_l( prog->uniLocation("lm.translucencySettings.specLobe0RoughnessScale"), sp->specLobe0RoughnessScale );
+ prog->uni1f_l( prog->uniLocation("lm.translucencySettings.specLobe1RoughnessScale"), sp->specLobe1RoughnessScale );
+ prog->uni1i_l( prog->uniLocation("lm.translucencySettings.transmittanceSourceLayer"), sp->sourceLayer );
+ } else {
+ prog->uni1b_l( prog->uniLocation("lm.translucencySettings.isEnabled"), false );
+ }
+
// decal settings
if ( mat->flags & CE2Material::Flag_IsDecal ) {
const CE2Material::DecalSettings * sp = mat->decalSettings;
diff --git a/src/gl/renderer.h b/src/gl/renderer.h
index 381848b9..9429e392 100644
--- a/src/gl/renderer.h
+++ b/src/gl/renderer.h
@@ -411,7 +411,7 @@ public slots:
{
bool useShaders = true;
short sfParallaxMaxSteps = 200;
- float sfParallaxScale = 0.033f;
+ float sfParallaxScale = 0.0f;
float sfParallaxOffset = 0.5f;
QString cubeMapPathFO76;
QString cubeMapPathSTF;
diff --git a/src/io/MeshFile.cpp b/src/io/MeshFile.cpp
index 5235f347..9b89c041 100644
--- a/src/io/MeshFile.cpp
+++ b/src/io/MeshFile.cpp
@@ -42,7 +42,7 @@ quint32 MeshFile::readMesh()
quint32 magic;
in >> magic;
- if ( magic != 1 && magic != 2 )
+ if ( magic > 2U )
return 0;
quint32 indicesSize;
@@ -173,18 +173,20 @@ quint32 MeshFile::readMesh()
weights[i] = BoneWeightsUNorm(weightsUNORM, i);
}
- quint32 numLODs;
- in >> numLODs;
- lods.resize(numLODs);
- for ( quint32 i = 0; i < numLODs; i++ ) {
- quint32 indicesSize2;
- in >> indicesSize2;
- lods[i].resize(indicesSize2 / 3);
-
- for ( quint32 j = 0; j < indicesSize2 / 3; j++ ) {
- Triangle tri;
- in >> tri;
- lods[i][j] = tri;
+ if ( magic ) {
+ quint32 numLODs;
+ in >> numLODs;
+ lods.resize(numLODs);
+ for ( quint32 i = 0; i < numLODs; i++ ) {
+ quint32 indicesSize2;
+ in >> indicesSize2;
+ lods[i].resize(indicesSize2 / 3);
+
+ for ( quint32 j = 0; j < indicesSize2 / 3; j++ ) {
+ Triangle tri;
+ in >> tri;
+ lods[i][j] = tri;
+ }
}
}
diff --git a/src/lib/importex/importex.cpp b/src/lib/importex/importex.cpp
index 5c85e078..817cf4a6 100644
--- a/src/lib/importex/importex.cpp
+++ b/src/lib/importex/importex.cpp
@@ -69,9 +69,9 @@ struct ImportExportOption
QVector impexOptions{
- ImportExportOption{ ".OBJ", importObj, exportObj, 0, 171 },
- ImportExportOption{ ".OBJ as Collision", importObjAsCollision, nullptr, 0, 171 },
- ImportExportOption{ ".glTF", nullptr, exportGltf, 172 },
+ ImportExportOption{ ".OBJ", importObj, exportObj, 0, 169 },
+ ImportExportOption{ ".OBJ as Collision", importObjAsCollision, nullptr, 0, 169 },
+ ImportExportOption{ ".glTF", nullptr, exportGltf, 170 },
};
@@ -126,4 +126,4 @@ void NifSkope::sltExport( QAction* a )
if ( impex.exportFn ) {
impex.exportFn(nif, ogl->scene, index);
}
-}
\ No newline at end of file
+}
diff --git a/src/model/nifextfiles.cpp b/src/model/nifextfiles.cpp
index af7c7c0f..137fff80 100644
--- a/src/model/nifextfiles.cpp
+++ b/src/model/nifextfiles.cpp
@@ -85,7 +85,7 @@ void NifModel::loadSFBlender( NifItem * parent, const void * o )
setValue( parent, "Height Blend Threshold", floatParams[0] );
setValue( parent, "Height Blend Factor", floatParams[1] );
setValue( parent, "Position", floatParams[2] );
- setValue( parent, "Contrast", floatParams[3] );
+ setValue( parent, "Contrast", 1.0f - floatParams[3] );
setValue( parent, "Mask Intensity", floatParams[4] );
setValue( parent, "Blend Color", boolParams[0] );
setValue( parent, "Blend Metalness", boolParams[1] );
diff --git a/src/spells/filerename.cpp b/src/spells/filerename.cpp
index 72e862e6..45da4998 100644
--- a/src/spells/filerename.cpp
+++ b/src/spells/filerename.cpp
@@ -20,7 +20,7 @@ class spResourceRename final : public Spell
{
public:
QString name() const override final { return Spell::tr( "Search/Replace Resource Paths" ); }
- QString page() const override final { return Spell::tr( "" ); }
+ QString page() const override final { return Spell::tr( "Batch" ); }
QIcon icon() const override final
{
return QIcon();
diff --git a/src/spells/headerstring.cpp b/src/spells/headerstring.cpp
index 557102cb..0fc2ad0e 100644
--- a/src/spells/headerstring.cpp
+++ b/src/spells/headerstring.cpp
@@ -96,6 +96,7 @@ class spEditStringIndex final : public Spell
return false;
}
+ static QString browseMaterial( const NifModel * nif, const QString & matPath );
void browseMaterial( QLineEdit * le, const NifModel * nif );
QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final
@@ -170,7 +171,7 @@ static bool bgsmFileNameFilterFunc( [[maybe_unused]] void * p, const std::string
return ( s.starts_with( "materials/" ) && ( s.ends_with( ".bgsm" ) || s.ends_with( ".bgem" ) ) );
}
-void spEditStringIndex::browseMaterial( QLineEdit * le, const NifModel * nif )
+QString spEditStringIndex::browseMaterial( const NifModel * nif, const QString & matPath )
{
std::set< std::string_view > materials;
AllocBuffers stringBuf;
@@ -184,16 +185,69 @@ void spEditStringIndex::browseMaterial( QLineEdit * le, const NifModel * nif )
}
std::string prvPath;
- if ( !le->text().isEmpty() )
- prvPath = Game::GameManager::get_full_path( le->text(), "materials", ( bsVersion >= 170 ? ".mat" : nullptr ) );
+ if ( !matPath.isEmpty() )
+ prvPath = Game::GameManager::get_full_path( matPath, "materials", ( bsVersion >= 170 ? ".mat" : nullptr ) );
FileBrowserWidget fileBrowser( 800, 600, "Select Material", materials, prvPath );
if ( fileBrowser.exec() == QDialog::Accepted ) {
const std::string_view * s = fileBrowser.getItemSelected();
if ( s )
- le->setText( QString::fromUtf8( s->data(), qsizetype(s->length()) ) );
+ return QString::fromUtf8( s->data(), qsizetype(s->length()) );
}
+ return QString();
+}
+
+void spEditStringIndex::browseMaterial( QLineEdit * le, const NifModel * nif )
+{
+ QString newPath( browseMaterial( nif, le->text() ) );
+ if ( !newPath.isEmpty() )
+ le->setText( newPath );
}
REGISTER_SPELL( spEditStringIndex )
+//! Browse a material path stored as header string
+class spBrowseHeaderMaterialPath final : public Spell
+{
+public:
+ QString name() const override final { return Spell::tr( "Browse Material" ); }
+ QString page() const override final { return Spell::tr( "" ); }
+ QIcon icon() const override final
+ {
+ if ( !txt_xpm_icon )
+ txt_xpm_icon = QIconPtr( new QIcon(QPixmap( txt_xpm )) );
+
+ 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
+ {
+ if ( !( nif && nif->getBSVersion() >= 130 ) )
+ return false;
+ auto block = nif->getTopItem( index );
+ if ( !( block && block == nif->getHeaderItem() ) )
+ return false;
+ const NifItem * item = nif->getItem( index );
+ 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 ) );
+ }
+
+ QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final
+ {
+ NifItem * item = nif->getItem( index );
+ if ( !( item && item->valueType() == NifValue::tSizedString ) )
+ return index;
+
+ QString newPath( spEditStringIndex::browseMaterial( nif, item->getValueAsString() ) );
+ if ( !newPath.isEmpty() )
+ item->setValueFromString( newPath );
+
+ return index;
+ }
+};
+
+REGISTER_SPELL( spBrowseHeaderMaterialPath )
diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp
index 69ec0932..e2bf30f5 100644
--- a/src/spells/mesh.cpp
+++ b/src/spells/mesh.cpp
@@ -825,7 +825,7 @@ class spUpdateBounds final : public Spell
bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final
{
- if ( nif->getBSVersion() >= 172 && nif->blockInherits( index, "BSGeometry" ) )
+ if ( nif->getBSVersion() >= 170 && nif->blockInherits( index, "BSGeometry" ) )
return true;
return nif->blockInherits( index, "BSTriShape" ) && nif->getIndex( index, "Vertex Data" ).isValid();
}
@@ -834,7 +834,7 @@ class spUpdateBounds final : public Spell
QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final
{
- if ( nif->getBSVersion() >= 172 && nif->blockInherits( index, "BSGeometry" ) )
+ if ( nif->getBSVersion() >= 170 && nif->blockInherits( index, "BSGeometry" ) )
return cast_Starfield( nif, index );
auto vertData = nif->getIndex( index, "Vertex Data" );
diff --git a/src/spells/meshfilecopy.cpp b/src/spells/meshfilecopy.cpp
index 03c7b899..bde84195 100644
--- a/src/spells/meshfilecopy.cpp
+++ b/src/spells/meshfilecopy.cpp
@@ -24,7 +24,7 @@ class spResourceCopy final : public Spell
{
public:
QString name() const override final { return Spell::tr( "Copy and Rename all Meshes" ); }
- QString page() const override final { return Spell::tr( "" ); }
+ QString page() const override final { return Spell::tr( "Batch" ); }
QIcon icon() const override final
{
return QIcon();
@@ -34,35 +34,35 @@ class spResourceCopy final : public Spell
bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final
{
- return ( nif && !index.isValid() );
+ return ( nif && nif->getBSVersion() >= 170 && !index.isValid() );
}
- void copyPaths(NifModel* nif, NifItem* item, const QString& author, const QString& project, const QString& nifFolder);
- NifItem* findChildByName(NifItem* parent, const QString& name);
- QString sanitizeFileName(const QString& input);
+ void copyPaths(NifModel* nif, NifItem* item, const QString& author, const QString& project, const QString& nifFolder);
+ NifItem* findChildByName(NifItem* parent, const QString& name);
+ QString sanitizeFileName(const QString& input);
QModelIndex cast(NifModel* nif, const QModelIndex& index) override final;
};
QString spResourceCopy::sanitizeFileName(const QString& input)
{
- // Convert to lowercase
- QString sanitized = input.toLower();
-
- // Remove periods, spaces, and slashes
- sanitized.remove(QRegularExpression("[\\.\\s/\\\\]"));
-
- // Replace special filesystem characters with an underscore
- QMap specialChars = {
- {'<', '_'}, {'>', '_'}, {':', '_'}, {'"', '_'},
- {'|', '_'}, {'?', '_'}, {'*', '_'}
- };
- for (auto it = specialChars.constBegin(); it != specialChars.constEnd(); ++it) {
- sanitized.replace(it.key(), it.value());
- }
-
- // Convert Unicode characters to ASCII representation
- QString result;
+ // Convert to lowercase
+ QString sanitized = input.toLower();
+
+ // Remove periods, spaces, and slashes
+ sanitized.remove(QRegularExpression("[\\.\\s/\\\\]"));
+
+ // Replace special filesystem characters with an underscore
+ QMap specialChars = {
+ {'<', '_'}, {'>', '_'}, {':', '_'}, {'"', '_'},
+ {'|', '_'}, {'?', '_'}, {'*', '_'}
+ };
+ for (auto it = specialChars.constBegin(); it != specialChars.constEnd(); ++it) {
+ sanitized.replace(it.key(), it.value());
+ }
+
+ // Convert Unicode characters to ASCII representation
+ QString result;
for (int i = 0; i < sanitized.size(); ++i) {
QChar c = sanitized.at(i);
if (c.unicode() < 128) {
@@ -83,46 +83,46 @@ QString spResourceCopy::sanitizeFileName(const QString& input)
}
result.append(asciiChunk);
finder.setPosition(nextBoundary);
- }
- if (result.isEmpty() || result.back() != '_') {
- result.append('_');
- }
- }
- }
+ }
+ if (result.isEmpty() || result.back() != '_') {
+ result.append('_');
+ }
+ }
+ }
- // Remove any remaining non-ASCII characters
- result.remove(QRegularExpression("[^\\x20-\\x7E]"));
+ // Remove any remaining non-ASCII characters
+ result.remove(QRegularExpression("[^\\x20-\\x7E]"));
- return result;
+ return result;
}
NifItem* spResourceCopy::findChildByName(NifItem* parent, const QString& name)
{
- for (int i = 0; i < parent->childCount(); i++) {
- NifItem* child = parent->child(i);
- if (child && child->name() == name) {
- return child;
- }
- }
- return nullptr;
+ for (int i = 0; i < parent->childCount(); i++) {
+ NifItem* child = parent->child(i);
+ if (child && child->name() == name) {
+ return child;
+ }
+ }
+ return nullptr;
}
void spResourceCopy::copyPaths(NifModel* nif, NifItem* item, const QString& author, const QString& project, const QString& nifFolder)
{
- if (item && item->name() == "BSGeometry") {
- QString objectName = nif->get(item, "Name");
- NifItem* meshArrayItems = findChildByName(item, "Meshes");
+ if (item && item->name() == "BSGeometry") {
+ QString objectName = nif->get(item, "Name");
+ NifItem* meshArrayItems = findChildByName(item, "Meshes");
- if (meshArrayItems) {
- for (int i = 0; i < meshArrayItems->childCount(); i++) {
+ if (meshArrayItems) {
+ for (int i = 0; i < meshArrayItems->childCount(); i++) {
NifItem* meshArrayItem = meshArrayItems->child(i);
- if (!nif->get(meshArrayItem, "Has Mesh")) {
- continue;
- }
+ if (!nif->get(meshArrayItem, "Has Mesh")) {
+ continue;
+ }
NifItem* mesh = findChildByName(meshArrayItem, "Mesh");
- if (mesh) {
+ if (mesh) {
// The nif field doesn't include the .mesh extension, and always uses a forward slash
- QString meshPath = nif->get(mesh, "Mesh Path");
+ QString meshPath = nif->get(mesh, "Mesh Path");
// Not using QDir because file as stored uses forward slashes and no extension
QString newMeshPath = author + "/" + project + "/" + sanitizeFileName(objectName) + "_lod" + QString::number(i + 1);
@@ -135,60 +135,67 @@ void spResourceCopy::copyPaths(NifModel* nif, NifItem* item, const QString& auth
newDir.mkpath(newDir.absolutePath());
// Copy the file (platform-independent with the slashes)
- QFile::copy(QDir::fromNativeSeparators(oldPath), QDir::fromNativeSeparators(newPath));
+ if ( !QFile::copy(QDir::fromNativeSeparators(oldPath), QDir::fromNativeSeparators(newPath)) ) {
+ QByteArray meshData;
+ if ( nif->getResourceFile(meshData, meshPath, "geometries/", ".mesh") ) {
+ QFile newFile( QDir::fromNativeSeparators(newPath) );
+ if ( newFile.open(QIODevice::WriteOnly) )
+ (void) newFile.write( meshData );
+ }
+ }
// Update the value in the nif
- findChildByName(mesh,"Mesh Path")->setValueFromString(newMeshPath);
- }
- }
- }
- // BSGeometri are leaf structures so no need to process children
+ findChildByName(mesh,"Mesh Path")->setValueFromString(newMeshPath);
+ }
+ }
+ }
+ // BSGeometri are leaf structures so no need to process children
}
else {
- // Process children
- for (int i = 0; i < item->childCount(); i++) {
- if (item->child(i)) {
+ // Process children
+ for (int i = 0; i < item->childCount(); i++) {
+ if (item->child(i)) {
copyPaths(nif, item->child(i), author, project, nifFolder);
- }
- }
- }
+ }
+ }
+ }
}
QModelIndex spResourceCopy::cast(NifModel* nif, const QModelIndex& index)
{
- if (!nif)
- return index;
+ if (!nif)
+ return index;
- QDialog dlg;
- QLabel* lb = new QLabel(&dlg);
- lb->setAlignment(Qt::AlignCenter);
+ QDialog dlg;
+ QLabel* lb = new QLabel(&dlg);
+ lb->setAlignment(Qt::AlignCenter);
lb->setText(tr("Copy and rename meshes to this format:\ngeometries/author/project/objectname_lod#"));
- QLabel* lb1 = new QLabel(&dlg);
- lb1->setText(tr("Author Prefix:"));
- QLineEdit* le1 = new QLineEdit(&dlg);
- le1->setFocus();
+ QLabel* lb1 = new QLabel(&dlg);
+ lb1->setText(tr("Author Prefix:"));
+ QLineEdit* le1 = new QLineEdit(&dlg);
+ le1->setFocus();
- QLabel* lb2 = new QLabel(&dlg);
- lb2->setText(tr("Project Name:"));
- QLineEdit* le2 = new QLineEdit(&dlg);
- le2->setFocus();
+ QLabel* lb2 = new QLabel(&dlg);
+ lb2->setText(tr("Project Name:"));
+ QLineEdit* le2 = new QLineEdit(&dlg);
+ le2->setFocus();
- QPushButton* bo = new QPushButton(tr("Ok"), &dlg);
- QObject::connect(bo, &QPushButton::clicked, &dlg, &QDialog::accept);
+ QPushButton* bo = new QPushButton(tr("Ok"), &dlg);
+ QObject::connect(bo, &QPushButton::clicked, &dlg, &QDialog::accept);
- QPushButton* bc = new QPushButton(tr("Cancel"), &dlg);
- QObject::connect(bc, &QPushButton::clicked, &dlg, &QDialog::reject);
+ QPushButton* bc = new QPushButton(tr("Cancel"), &dlg);
+ QObject::connect(bc, &QPushButton::clicked, &dlg, &QDialog::reject);
- QGridLayout* grid = new QGridLayout;
- dlg.setLayout(grid);
- grid->addWidget(lb, 0, 0, 1, 2);
- grid->addWidget(lb1, 1, 0, 1, 2);
- grid->addWidget(le1, 2, 0, 1, 2);
- grid->addWidget(lb2, 3, 0, 1, 2);
- grid->addWidget(le2, 4, 0, 1, 2);
- grid->addWidget(bo, 5, 0, 1, 1);
- grid->addWidget(bc, 5, 1, 1, 1);
+ QGridLayout* grid = new QGridLayout;
+ dlg.setLayout(grid);
+ grid->addWidget(lb, 0, 0, 1, 2);
+ grid->addWidget(lb1, 1, 0, 1, 2);
+ grid->addWidget(le1, 2, 0, 1, 2);
+ grid->addWidget(lb2, 3, 0, 1, 2);
+ grid->addWidget(le2, 4, 0, 1, 2);
+ grid->addWidget(bo, 5, 0, 1, 1);
+ grid->addWidget(bc, 5, 1, 1, 1);
if (dlg.exec() != QDialog::Accepted) {
return index;
@@ -198,13 +205,13 @@ QModelIndex spResourceCopy::cast(NifModel* nif, const QModelIndex& index)
QString authorPrefix = sanitizeFileName(le1->text().trimmed().remove(QRegularExpression("^[\\\\/]+|[\\\\/]+$")));
QString projectName = sanitizeFileName(le2->text().trimmed().remove(QRegularExpression("^[\\\\/]+|[\\\\/]+$")));
- for (int b = 0; b < nif->getBlockCount(); b++) {
- NifItem* item = nif->getBlockItem(quint32(b));
- if (item)
+ for (int b = 0; b < nif->getBlockCount(); b++) {
+ NifItem* item = nif->getBlockItem(quint32(b));
+ if (item)
copyPaths(nif, item, authorPrefix, projectName, nif->getFolder());
- }
+ }
- return index;
+ return index;
}
-REGISTER_SPELL( spResourceCopy )
\ No newline at end of file
+REGISTER_SPELL( spResourceCopy )
diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp
index 773d33c3..3a4f9a3d 100644
--- a/src/spells/sanitize.cpp
+++ b/src/spells/sanitize.cpp
@@ -448,7 +448,7 @@ class spFixInvalidNames final : public Spell
}
// Fix "Root Material" field
- if ( nif->getBSVersion() < 172 && isProp && nif->getIndex(iBlock, "Root Material").isValid() ) {
+ if ( nif->getBSVersion() < 170 && isProp && nif->getIndex(iBlock, "Root Material").isValid() ) {
auto rootIdx = nif->get(iBlock, "Root Material");
auto rootString = nif->get(iBlock, "Root Material");
@@ -665,7 +665,7 @@ QModelIndex spErrorInvalidPaths::cast( NifModel * nif, const QModelIndex & )
auto iBSLSP = nif->getBlockIndex( i, "BSLightingShaderProperty" );
if ( iBSLSP.isValid() ) {
checkPath( nif, iBSLSP, "Name", P_NO_EXT );
- if ( nif->getBSVersion() < 172 )
+ if ( nif->getBSVersion() < 170 )
checkPath( nif, iBSLSP, "Root Material", P_NO_EXT );
}
diff --git a/src/ui/settingsrender.ui b/src/ui/settingsrender.ui
index c1a4c1a1..cbe71808 100644
--- a/src/ui/settingsrender.ui
+++ b/src/ui/settingsrender.ui
@@ -275,7 +275,7 @@
0.001
- 0.033
+ 0.0