fpopov thanks

This commit is contained in:
bird_egop
2026-05-09 20:36:43 +03:00
parent d73a6dde70
commit eb458ad9a8
43 changed files with 1562 additions and 1233 deletions
+107 -113
View File
@@ -1,152 +1,148 @@
namespace MaterialLib;
public class MaterialFile
{
// === Metadata (not from file content) ===
public string FileName { get; set; }
public int Version { get; set; } // From NRes ElementCount
public int Magic1 { get; set; } // From NRes Magic1
// === Derived from Version/ElementCount ===
public int MaterialRenderingType { get; set; } // (Version >> 2) & 0xF - 0=Standard, 1=Special, 2=Particle
public bool SupportsBumpMapping { get; set; } // (Version & 2) != 0
public int IsParticleEffect { get; set; } // Version & 40 - 0=Normal, 8=Particle/Effect
// === File Content (in read order) ===
// Read order: StageCount (ushort), AnimCount (ushort), then conditionally blend modes and params
// Global Blend Modes (read if Magic1 >= 2)
public BlendMode SourceBlendMode { get; set; } // Default: Unknown (0xFF)
public BlendMode DestBlendMode { get; set; } // Default: Unknown (0xFF)
// Global Parameters (read if Magic1 > 2 and > 3 respectively)
public float GlobalAlphaMultiplier { get; set; } // Default: 1.0 (always 1.0 in all 628 materials)
public float GlobalEmissiveIntensity { get; set; } // Default: 0.0 (0=no glow, rare values: 1000, 10000)
public List<MaterialStage> Stages { get; set; } = new();
public List<MaterialAnimation> Animations { get; set; } = new();
}
/// <summary>Файл материала MAT0.</summary>
/// <param name="FileName">Имя файла из NRes metadata, не из payload материала.</param>
/// <param name="Version">Значение attr1 / ElementCount из NRes metadata.</param>
/// <param name="Magic1">Значение attr2 / Magic1 из NRes metadata.</param>
/// <param name="MaterialRenderingType">Производное от Version: (Version >> 2) &amp; 0xF. 0 = standard, 1 = special, 2 = particle.</param>
/// <param name="SupportsBumpMapping">Производное от Version: (Version &amp; 2) != 0.</param>
/// <param name="IsParticleEffect">Производное от Version: Version &amp; 40. 0 = normal, 8 = particle/effect.</param>
/// <param name="SourceBlendMode">Глобальный source blend mode; читается, если Magic1 >= 2. Значение по умолчанию: Unknown (0xFF).</param>
/// <param name="DestBlendMode">Глобальный destination blend mode; читается, если Magic1 >= 2. Значение по умолчанию: Unknown (0xFF).</param>
/// <param name="GlobalAlphaMultiplier">Глобальный alpha multiplier; читается, если Magic1 > 2. Значение по умолчанию: 1.0.</param>
/// <param name="GlobalEmissiveIntensity">Глобальная emissive intensity; читается, если Magic1 > 3. Значение по умолчанию: 0.0.</param>
/// <param name="Stages">Стадии материала в порядке чтения из файла.</param>
/// <param name="Animations">Анимации материала в порядке чтения из файла.</param>
public record MaterialFile(
string FileName,
int Version,
int Magic1,
int MaterialRenderingType,
bool SupportsBumpMapping,
int IsParticleEffect,
BlendMode SourceBlendMode,
BlendMode DestBlendMode,
float GlobalAlphaMultiplier,
float GlobalEmissiveIntensity,
List<MaterialStage> Stages,
List<MaterialAnimation> Animations);
/// <summary>
/// Blend modes for material rendering. These control how source and destination colors are combined.
/// Formula: FinalColor = (SourceColor * SourceBlend) [operation] (DestColor * DestBlend)
/// Maps to Direct3D D3DBLEND values.
/// Режимы blend для материала. Управляют смешиванием source и destination цветов.
/// Формула: FinalColor = SourceColor * SourceBlend [operation] DestColor * DestBlend.
/// Соответствуют значениям Direct3D D3DBLEND.
/// </summary>
public enum BlendMode : byte
{
/// <summary>Blend factor is (0, 0, 0, 0) - results in black/transparent</summary>
/// <summary>Blend factor = (0, 0, 0, 0).</summary>
Zero = 1,
/// <summary>Blend factor is (1, 1, 1, 1) - uses full color value</summary>
/// <summary>Blend factor = (1, 1, 1, 1).</summary>
One = 2,
/// <summary>Blend factor is (Rs, Gs, Bs, As) - uses source color</summary>
/// <summary>Blend factor = (Rs, Gs, Bs, As).</summary>
SrcColor = 3,
/// <summary>Blend factor is (1-Rs, 1-Gs, 1-Bs, 1-As) - uses inverted source color</summary>
/// <summary>Blend factor = (1-Rs, 1-Gs, 1-Bs, 1-As).</summary>
InvSrcColor = 4,
/// <summary>Blend factor is (As, As, As, As) - uses source alpha for all channels (standard transparency)</summary>
/// <summary>Blend factor = (As, As, As, As).</summary>
SrcAlpha = 5,
/// <summary>Blend factor is (1-As, 1-As, 1-As, 1-As) - uses inverted source alpha</summary>
/// <summary>Blend factor = (1-As, 1-As, 1-As, 1-As).</summary>
InvSrcAlpha = 6,
/// <summary>Blend factor is (Ad, Ad, Ad, Ad) - uses destination alpha</summary>
/// <summary>Blend factor = (Ad, Ad, Ad, Ad).</summary>
DestAlpha = 7,
/// <summary>Blend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad) - uses inverted destination alpha</summary>
/// <summary>Blend factor = (1-Ad, 1-Ad, 1-Ad, 1-Ad).</summary>
InvDestAlpha = 8,
/// <summary>Blend factor is (Rd, Gd, Bd, Ad) - uses destination color</summary>
/// <summary>Blend factor = (Rd, Gd, Bd, Ad).</summary>
DestColor = 9,
/// <summary>Blend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad) - uses inverted destination color</summary>
/// <summary>Blend factor = (1-Rd, 1-Gd, 1-Bd, 1-Ad).</summary>
InvDestColor = 10,
/// <summary>Blend factor is (f, f, f, 1) where f = min(As, 1-Ad) - saturates source alpha</summary>
/// <summary>Blend factor = (f, f, f, 1), где f = min(As, 1-Ad).</summary>
SrcAlphaSat = 11,
/// <summary>Blend factor is (As, As, As, As) for both source and dest - obsolete in D3D9+</summary>
/// <summary>Blend factor = (As, As, As, As) для source и destination; устарело в D3D9+.</summary>
BothSrcAlpha = 12,
/// <summary>Blend factor is (1-As, 1-As, 1-As, 1-As) for both source and dest - obsolete in D3D9+</summary>
/// <summary>Blend factor = (1-As, 1-As, 1-As, 1-As) для source и destination; устарело в D3D9+.</summary>
BothInvSrcAlpha = 13,
/// <summary>Unknown or uninitialized blend mode (0xFF default value)</summary>
/// <summary>Неизвестный или неинициализированный режим blend; значение по умолчанию 0xFF.</summary>
Unknown = 0xFF
}
public class MaterialStage
{
// === FILE READ ORDER (34 bytes per stage) ===
// This matches the order bytes are read from the file (decompiled.c lines 159-217)
// NOT the C struct memory layout (which is Diffuse, Ambient, Specular, Emissive)
// 1. Ambient Color (4 bytes, read first from file)
public float AmbientR;
public float AmbientG;
public float AmbientB;
public float AmbientA; // Scaled by 0.01 when read from file
// 2. Diffuse Color (4 bytes, read second from file)
public float DiffuseR;
public float DiffuseG;
public float DiffuseB;
public float DiffuseA;
/// <summary>Стадия материала. Порядок полей соответствует порядку чтения из файла, а не C struct layout.</summary>
/// <param name="AmbientR">Ambient R, читается первым цветовым блоком.</param>
/// <param name="AmbientG">Ambient G, читается первым цветовым блоком.</param>
/// <param name="AmbientB">Ambient B, читается первым цветовым блоком.</param>
/// <param name="AmbientA">Ambient A, читается первым цветовым блоком и масштабируется на 0.01.</param>
/// <param name="DiffuseR">Diffuse R, читается вторым цветовым блоком.</param>
/// <param name="DiffuseG">Diffuse G, читается вторым цветовым блоком.</param>
/// <param name="DiffuseB">Diffuse B, читается вторым цветовым блоком.</param>
/// <param name="DiffuseA">Diffuse A, читается вторым цветовым блоком.</param>
/// <param name="SpecularR">Specular R, читается третьим цветовым блоком.</param>
/// <param name="SpecularG">Specular G, читается третьим цветовым блоком.</param>
/// <param name="SpecularB">Specular B, читается третьим цветовым блоком.</param>
/// <param name="SpecularA">Specular A, читается третьим цветовым блоком.</param>
/// <param name="EmissiveR">Emissive R, читается четвертым цветовым блоком.</param>
/// <param name="EmissiveG">Emissive G, читается четвертым цветовым блоком.</param>
/// <param name="EmissiveB">Emissive B, читается четвертым цветовым блоком.</param>
/// <param name="EmissiveA">Emissive A, читается четвертым цветовым блоком.</param>
/// <param name="Power">Power, один байт -> float.</param>
/// <param name="TextureStageIndex">Индекс texture stage. 255 = не задано/default, 0..47 = ссылка на texture stage.</param>
/// <param name="TextureName">Имя текстуры, строка из 16 байт.</param>
public record MaterialStage(
float AmbientR,
float AmbientG,
float AmbientB,
float AmbientA,
float DiffuseR,
float DiffuseG,
float DiffuseB,
float DiffuseA,
float SpecularR,
float SpecularG,
float SpecularB,
float SpecularA,
float EmissiveR,
float EmissiveG,
float EmissiveB,
float EmissiveA,
float Power,
int TextureStageIndex,
string TextureName);
// 3. Specular Color (4 bytes, read third from file)
public float SpecularR;
public float SpecularG;
public float SpecularB;
public float SpecularA;
// 4. Emissive Color (4 bytes, read fourth from file)
public float EmissiveR;
public float EmissiveG;
public float EmissiveB;
public float EmissiveA;
// 5. Power (1 byte → float, read fifth from file)
public float Power;
// 6. Texture Stage Index (1 byte, read sixth from file)
// 255 = not set/default, 0-47 = reference to specific texture stage
public int TextureStageIndex;
// 7. Texture Name (16 bytes, read seventh from file)
public string TextureName { get; set; }
}
public class MaterialAnimation
{
// === File Read Order ===
// Combined field (4 bytes): bits 3-31 = Target, bits 0-2 = LoopMode
public AnimationTarget Target;
public AnimationLoopMode LoopMode;
// Key count (2 bytes), then keys
public List<AnimKey> Keys { get; set; } = new();
// Cached description for UI (computed once during parsing)
public string TargetDescription { get; set; } = string.Empty;
}
/// <summary>Анимация материала.</summary>
/// <param name="Target">Целевые компоненты анимации: биты 3..31 combined field.</param>
/// <param name="LoopMode">Режим повтора: биты 0..2 combined field.</param>
/// <param name="Keys">Ключи анимации.</param>
/// <param name="TargetDescription">Кэшированное описание target для UI.</param>
public record MaterialAnimation(
AnimationTarget Target,
AnimationLoopMode LoopMode,
List<AnimKey> Keys,
string TargetDescription);
[Flags]
public enum AnimationTarget : int
{
// NOTE: This is a BITSET (flags enum). Multiple flags can be combined.
// When a flag is SET, that component is INTERPOLATED between stages.
// When a flag is NOT SET, that component is COPIED from the source stage (no interpolation).
// If ALL flags are 0, the ENTIRE stage is copied without any interpolation.
// Это bitset: несколько флагов могут комбинироваться.
// Установленный флаг означает интерполяцию компонента между стадиями.
// Неустановленный флаг означает копирование компонента из исходной стадии.
// Если все флаги равны 0, вся стадия копируется без интерполяции.
Ambient = 1, // 0x01 - Interpolates Ambient RGB (Interpolate.c lines 23-37)
Diffuse = 2, // 0x02 - Interpolates Diffuse RGB (Interpolate.c lines 7-21)
Specular = 4, // 0x04 - Interpolates Specular RGB (Interpolate.c lines 39-53)
Emissive = 8, // 0x08 - Interpolates Emissive RGB (Interpolate.c lines 55-69)
Power = 16 // 0x10 - Interpolates Ambient.A and sets Power (Interpolate.c lines 71-76)
Ambient = 1, // 0x01: интерполирует Ambient RGB.
Diffuse = 2, // 0x02: интерполирует Diffuse RGB.
Specular = 4, // 0x04: интерполирует Specular RGB.
Emissive = 8, // 0x08: интерполирует Emissive RGB.
Power = 16 // 0x10: интерполирует Ambient.A и задает Power.
}
@@ -158,10 +154,8 @@ public enum AnimationLoopMode : int
Random = 3
}
public struct AnimKey
{
// === File Read Order (6 bytes per key) ===
public ushort StageIndex; // Read first
public ushort DurationMs; // Read second
public ushort InterpolationCurve; // Read third - Always 0 (linear interpolation) in all 1848 keys
}
/// <summary>Ключ анимации материала (length = 6).</summary>
/// <param name="StageIndex">[0x00..0x02] Индекс стадии материала.</param>
/// <param name="DurationMs">[0x02..0x04] Длительность в миллисекундах.</param>
/// <param name="InterpolationCurve">[0x04..0x06] Кривая интерполяции. В исследованных данных всегда 0.</param>
public readonly record struct AnimKey(ushort StageIndex, ushort DurationMs, ushort InterpolationCurve);