Skip to content

Commit

Permalink
Minor improvements to Starfield material editing
Browse files Browse the repository at this point in the history
  • Loading branch information
fo76utils committed Sep 26, 2024
1 parent 8d71734 commit 82886ef
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 62 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
== CHANGELOG ==

#### NifSkope-2.0.dev9-20240926

* Added support for editing Starfield materials, and a new spell to save the edited material in .mat format. Water settings and global layer data are not saved yet, nor any material setting that is not shown on the user interface.
* Implemented the 'Transform/Scale Vertices' spell for Skyrim: Special Edition, Fallout 4, Fallout 76 and Starfield. Note that skin partitions are currently not supported, and normals, tangent space and bounds should be updated after using this spell.
* CharacterCombine blend mode is now correctly implemented on Starfield materials as multiplicative blending, and defaults to multiplying by 1.0 if the overlay textures are missing.
Expand Down
86 changes: 52 additions & 34 deletions build/nif.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6729,6 +6729,15 @@
<field name="UV Stream" type="BSUVStream" />
</struct>

<enum name="BSLayerIndex" storage="byte" versions="#STF#">
<option name="Layer 1" value="0" />
<option name="Layer 2" value="1" />
<option name="Layer 3" value="2" />
<option name="Layer 4" value="3" />
<option name="Layer 5" value="4" />
<option name="Layer 6" value="5" />
</enum>

<enum name="BSBlenderMode" storage="byte" versions="#STF#">
<option name="Linear" value="0" />
<option name="Additive" value="1" />
Expand Down Expand Up @@ -6766,6 +6775,14 @@
<field name="Use Detail Blend Mask" type="bool" />
</struct>

<enum name="BSBlenderIndex" storage="byte" versions="#STF#">
<option name="Blender 1" value="0" />
<option name="Blender 2" value="1" />
<option name="Blender 3" value="2" />
<option name="Blender 4" value="3" />
<option name="Blender 5" value="4" />
</enum>

<enum name="BSAlphaBlendMode" storage="byte" versions="#STF#">
<option name="Linear" value="0" />
<option name="Additive" value="1" />
Expand All @@ -6775,7 +6792,7 @@

<struct name="BSAlphaSettingsComponent" abstract="true" module="BSMain" versions="#STF#">
<field name="Alpha Test Threshold" type="float" />
<field name="Opacity Source Layer" type="byte" />
<field name="Opacity Source Layer" type="BSLayerIndex" />
<field name="Alpha Blender Mode" type="BSAlphaBlendMode" />
<field name="Use Detail Blend Mask" type="bool" />
<field name="Use Vertex Color" type="bool" />
Expand All @@ -6801,14 +6818,14 @@
</enum>

<struct name="BSOpacityComponent" abstract="true" module="BSMain" versions="#STF#">
<field name="First Layer Index" type="byte" />
<field name="First Layer Index" type="BSLayerIndex" />
<field name="Second Layer Active" type="bool" />
<field name="Second Layer Index" type="byte" />
<field name="First Blender Index" type="byte" />
<field name="Second Layer Index" type="BSLayerIndex" />
<field name="First Blender Index" type="BSBlenderIndex" />
<field name="First Blender Mode" type="BSOpacityBlenderMode" />
<field name="Third Layer Active" type="bool" />
<field name="Third Layer Index" type="byte" />
<field name="Second Blender Index" type="byte" />
<field name="Third Layer Index" type="BSLayerIndex" />
<field name="Second Blender Index" type="BSBlenderIndex" />
<field name="Second Blender Mode" type="BSOpacityBlenderMode" />
<field name="Specular Opacity Override" type="float" />
</struct>
Expand Down Expand Up @@ -6880,6 +6897,11 @@
<option name="Bottom" value="2" />
</enum>

<enum name="BSDecalBlendMode" storage="byte" versions="#STF#">
<option name="None" value="0" />
<option name="Additive" value="1" />
</enum>

<struct name="BSDecalSettingsComponent" abstract="true" module="BSMain" versions="#STF#">
<field name="Material Overall Alpha" type="float" />
<field name="Write Mask" type="uint" />
Expand All @@ -6892,7 +6914,7 @@
<field name="Max Parallax Occlusion Steps" type="byte" cond="Is Projected" />
<field name="Render Layer" type="BSProjectedDecalRenderLayer" cond="Is Projected" />
<field name="Use G Buffer Normals" type="bool" cond="Is Projected" />
<field name="Blend Mode" type="BSAlphaBlendMode" />
<field name="Blend Mode" type="BSDecalBlendMode" />
<field name="Animated Decal Ignores TAA" type="bool" />
</struct>

Expand Down Expand Up @@ -6923,13 +6945,13 @@

<enum name="BSMaskSourceBlender" storage="byte" versions="#STF#">
<option name="None" value="0" />
<option name="Blender 0" value="1" />
<option name="Blender 1" value="2" />
<option name="Blender 2" value="3" />
<option name="Blender 1" value="1" />
<option name="Blender 2" value="2" />
<option name="Blender 3" value="3" />
</enum>

<struct name="BSEmissiveSettingsComponent" abstract="true" module="BSMain" versions="#STF#">
<field name="Emissive Source Layer" type="byte" />
<field name="Emissive Source Layer" type="BSLayerIndex" />
<field name="Emissive Tint" type="Color4" />
<field name="Emissive Mask Source Blender" type="BSMaskSourceBlender" />
<field name="Emissive Clip Threshold" type="float" />
Expand All @@ -6942,20 +6964,20 @@
</struct>

<struct name="BSLayeredEmissivityComponent" abstract="true" module="BSMain" versions="#STF#">
<field name="First Layer Index" type="byte" />
<field name="First Layer Index" type="BSLayerIndex" />
<field name="First Layer Tint" type="Color4" />
<field name="First Layer Mask Source" type="BSMaskSourceBlender" />
<field name="Second Layer Active" type="bool" />
<field name="Second Layer Index" type="byte" cond="Second Layer Active" />
<field name="Second Layer Index" type="BSLayerIndex" cond="Second Layer Active" />
<field name="Second Layer Tint" type="Color4" cond="Second Layer Active" />
<field name="Second Layer Mask Source" type="BSMaskSourceBlender" cond="Second Layer Active" />
<field name="First Blender Index" type="byte" cond="Second Layer Active" />
<field name="First Blender Index" type="BSBlenderIndex" cond="Second Layer Active" />
<field name="First Blender Mode" type="BSOpacityBlenderMode" cond="Second Layer Active" />
<field name="Third Layer Active" type="bool" />
<field name="Third Layer Index" type="byte" cond="Third Layer Active" />
<field name="Third Layer Index" type="BSLayerIndex" cond="Third Layer Active" />
<field name="Third Layer Tint" type="Color4" cond="Third Layer Active" />
<field name="Third Layer Mask Source" type="BSMaskSourceBlender" cond="Third Layer Active" />
<field name="Second Blender Index" type="byte" cond="Third Layer Active" />
<field name="Second Blender Index" type="BSBlenderIndex" cond="Third Layer Active" />
<field name="Second Blender Mode" type="BSOpacityBlenderMode" cond="Third Layer Active" />
<field name="Emissive Clip Threshold" type="float" />
<field name="Adaptive Emittance" type="bool" />
Expand All @@ -6977,7 +6999,7 @@
<field name="Transmittance Width" type="float" />
<field name="Spec Lobe 0 Roughness Scale" type="float" />
<field name="Spec Lobe 1 Roughness Scale" type="float" />
<field name="Transmittance Source Layer" type="byte" />
<field name="Transmittance Source Layer" type="BSLayerIndex" />
</struct>

<enum name="BSShaderRoute" storage="byte" versions="#STF#">
Expand All @@ -7001,23 +7023,19 @@

<struct name="BSLayeredMaterial" abstract="true" module="BSMain" versions="#STF#">
<field name="Name" type="SizedString" />
<field name="Layer 0 Enabled" type="bool" />
<field name="Layer 0" type="BSLayer" cond="Layer 0 Enabled" />
<field name="Layer 1 Enabled" type="bool" />
<field name="Layer 1" type="BSLayer" cond="Layer 1 Enabled" />
<field name="Blender 0" type="BSBlender" cond="Layer 1 Enabled" />
<field name="Layer 2 Enabled" type="bool" />
<field name="Layer 2" type="BSLayer" cond="Layer 2 Enabled" />
<field name="Blender 1" type="BSBlender" cond="Layer 2 Enabled" />
<field name="Layer 3 Enabled" type="bool" />
<field name="Layer 3" type="BSLayer" cond="Layer 3 Enabled" />
<field name="Blender 2" type="BSBlender" cond="Layer 3 Enabled" />
<field name="Layer 4 Enabled" type="bool" />
<field name="Layer 4" type="BSLayer" cond="Layer 4 Enabled" />
<field name="Blender 3" type="BSBlender" cond="Layer 4 Enabled" />
<field name="Layer 5 Enabled" type="bool" />
<field name="Layer 5" type="BSLayer" cond="Layer 5 Enabled" />
<field name="Blender 4" type="BSBlender" cond="Layer 5 Enabled" />
<field name="Layer Enable Mask" type="uint" />
<field name="Layer 1" type="BSLayer" cond="Layer Enable Mask #BITAND# 1" />
<field name="Layer 2" type="BSLayer" cond="Layer Enable Mask #BITAND# 2" />
<field name="Blender Enable Mask" type="uint" />
<field name="Blender 1" type="BSBlender" cond="Blender Enable Mask #BITAND# 1" />
<field name="Layer 3" type="BSLayer" cond="Layer Enable Mask #BITAND# 4" />
<field name="Blender 2" type="BSBlender" cond="Blender Enable Mask #BITAND# 2" />
<field name="Layer 4" type="BSLayer" cond="Layer Enable Mask #BITAND# 8" />
<field name="Blender 3" type="BSBlender" cond="Blender Enable Mask #BITAND# 4" />
<field name="Layer 5" type="BSLayer" cond="Layer Enable Mask #BITAND# 16" />
<field name="Blender 4" type="BSBlender" cond="Blender Enable Mask #BITAND# 8" />
<field name="Layer 6" type="BSLayer" cond="Layer Enable Mask #BITAND# 32" />
<field name="Blender 5" type="BSBlender" cond="Blender Enable Mask #BITAND# 16" />
<field name="Shader Model" type="SizedString" default="BaseMaterial" />
<field name="Shader Route" type="BSShaderRoute" />
<field name="Two Sided" type="bool" />
Expand Down
2 changes: 1 addition & 1 deletion src/model/nifdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class NifDelegate final : public QItemDelegate
if ( !nif->get<bool>( i, "Is Modified" ) ) {
if ( !item->hasName( "Is Modified" ) )
nif->set<bool>( i, "Is Modified", true );
QMessageBox::warning( nullptr, "NifSkope warning", QString( "Changes to Starfield material data are not saved" ) );
QMessageBox::warning( nullptr, "NifSkope warning", QString( "Changes to material data are not saved automatically, use the spell 'Material/Save Edited Material...'" ) );
}
break;
} else {
Expand Down
54 changes: 36 additions & 18 deletions src/model/nifextfiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,28 @@ void NifModel::loadSFMaterial( const QModelIndex & parent, const void *matPtr, i

setValue<QString>( m, "Name", ( material ? material->name : "" ) );
setValue<bool>( m, "Is Modified", false );
for ( int l = 0; l < CE2Material::maxLayers; l++ ) {
bool layerEnabled = false;
if ( material )
layerEnabled = bool( material->layerMask & (1 << l) );
setValue<bool>( m, QString("Layer %1 Enabled").arg(l), layerEnabled );
if ( layerEnabled ) {
loadSFLayer( getItem( m, QString("Layer %1").arg(l) ), material->layers[l] );
if ( l > 0 && material->blenders[l - 1] )
loadSFBlender( getItem( m, QString("Blender %1").arg(l - 1) ), material->blenders[l - 1] );
{
std::uint32_t layerMask = 0;
std::uint32_t blenderMask = 0;
if ( material ) {
for ( int l = 0; l < CE2Material::maxLayers; l++ ) {
if ( ( material->layerMask & ( 1U << l ) ) && material->layers[l] )
layerMask |= std::uint32_t( 1U << l );
}
for ( int l = 0; l < CE2Material::maxBlenders; l++ ) {
if ( material->blenders[l] )
blenderMask |= std::uint32_t( 1U << l );
}
}
setValue<quint32>( m, "Layer Enable Mask", layerMask );
for ( int l = 0; layerMask; l++, layerMask = layerMask >> 1 ) {
if ( layerMask & 1 )
loadSFLayer( getItem( m, QString("Layer %1").arg(l + 1) ), material->layers[l] );
}
setValue<quint32>( m, "Blender Enable Mask", blenderMask );
for ( int l = 0; blenderMask; l++, blenderMask = blenderMask >> 1 ) {
if ( blenderMask & 1 )
loadSFBlender( getItem( m, QString("Blender %1").arg(l + 1) ), material->blenders[l] );
}
}
setValue<QString>( m, "Shader Model", QString( material ? CE2Material::shaderModelNames[material->shaderModel] : "BaseMaterial" ) );
Expand Down Expand Up @@ -840,21 +853,26 @@ const void * NifModel::updateSFMaterial( AllocBuffers & bufs, const QModelIndex
CE2Material * mat = bufs.constructObject< CE2Material >();
mat->name = copySFMatString( bufs, get<QString>( m, "Name" ) )->data();

quint32 layerMask = get<quint32>( m, "Layer Enable Mask" );
for ( int l = 0; l < CE2Material::maxLayers; l++ ) {
if ( !get<bool>( m, QString( "Layer %1 Enabled" ).arg( l ) ) )
if ( !( layerMask & ( 1U << l ) ) )
continue;

mat->layers[l] = reinterpret_cast< const CE2Material::Layer * >(
createSFLayer( bufs, getItem( m, QString( "Layer %1" ).arg( l ) ) ) );
if ( mat->layers[l] )
createSFLayer( bufs, getItem( m, QString( "Layer %1" ).arg( l + 1 ) ) ) );
if ( mat->layers[l] ) {
const_cast< CE2Material::Layer * >( mat->layers[l] )->parent = mat;
if ( l > 0 ) {
mat->blenders[l - 1] = reinterpret_cast< const CE2Material::Blender * >(
createSFBlender( bufs, getItem( m, QString( "Blender %1" ).arg( l - 1 ) ) ) );
if ( mat->blenders[l - 1] )
const_cast< CE2Material::Blender * >( mat->blenders[l - 1] )->parent = mat;
mat->layerMask |= std::uint32_t( 1U << l );
}
mat->layerMask |= std::uint32_t( 1 << l );
}
quint32 blenderMask = get<quint32>( m, "Blender Enable Mask" );
for ( int l = 0; l < CE2Material::maxBlenders; l++ ) {
if ( !( blenderMask & ( 1U << l ) ) )
continue;
mat->blenders[l] = reinterpret_cast< const CE2Material::Blender * >(
createSFBlender( bufs, getItem( m, QString( "Blender %1" ).arg( l + 1 ) ) ) );
if ( mat->blenders[l] )
const_cast< CE2Material::Blender * >( mat->blenders[l] )->parent = mat;
}

QString shaderModel = get<QString>( m, "Shader Model" ).trimmed();
Expand Down
14 changes: 6 additions & 8 deletions src/spells/sfmatexport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -650,15 +650,13 @@ void CE2MaterialToJSON::createLink( QJsonArray & components, const CE2MaterialOb
void CE2MaterialToJSON::createLayeredMaterial( QJsonArray & components, const CE2Material * o )
{
for ( int i = 0; i < CE2Material::maxLayers; i++ ) {
if ( !( ( o->layerMask & ( 1U << i ) ) && o->layers[i] ) )
continue;

createLink( components, o->layers[i], i );

if ( i > 0 && i <= CE2Material::maxBlenders && o->blenders[i - 1] )
createLink( components, o->blenders[i - 1], i - 1 );
if ( ( o->layerMask & ( 1U << i ) ) && o->layers[i] )
createLink( components, o->layers[i], i );
}
for ( int i = 0; i < CE2Material::maxBlenders; i++ ) {
if ( o->blenders[i] )
createLink( components, o->blenders[i], i );
}

for ( int i = 0; i < CE2Material::maxLODMaterials; i++ ) {
if ( o->lodMaterials[i] )
createLink( components, o->lodMaterials[i], i );
Expand Down
2 changes: 1 addition & 1 deletion src/spells/texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class spChooseTexture final : public Spell
for ( const NifItem * i = nif->getItem( iFile ); i; i = i->parent() ) {
if ( i->isAbstract() && i->hasStrType( "BSLayeredMaterial" ) && !nif->get<bool>( i, "Is Modified" ) ) {
nif->set<bool>( i, "Is Modified", true );
QMessageBox::warning( nullptr, "NifSkope warning", QString( "Changes to Starfield material data are not saved" ) );
QMessageBox::warning( nullptr, "NifSkope warning", QString( "Changes to material data are not saved automatically, use the spell 'Material/Save Edited Material...'" ) );
break;
}
}
Expand Down

0 comments on commit 82886ef

Please sign in to comment.