mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2026-08-15 02:57:49 +04:00
fpopov thanks
This commit is contained in:
+107
-113
@@ -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) & 0xF. 0 = standard, 1 = special, 2 = particle.</param>
|
||||
/// <param name="SupportsBumpMapping">Производное от Version: (Version & 2) != 0.</param>
|
||||
/// <param name="IsParticleEffect">Производное от Version: Version & 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);
|
||||
|
||||
@@ -7,17 +7,9 @@ public static class MaterialParser
|
||||
{
|
||||
public static MaterialFile ReadFromStream(Stream fs, string fileName, int elementCount, int magic1)
|
||||
{
|
||||
var file = new MaterialFile
|
||||
{
|
||||
FileName = fileName,
|
||||
Version = elementCount,
|
||||
Magic1 = magic1
|
||||
};
|
||||
|
||||
// Derived fields
|
||||
file.MaterialRenderingType = elementCount >> 2 & 0xf;
|
||||
file.SupportsBumpMapping = (elementCount & 2) != 0;
|
||||
file.IsParticleEffect = elementCount & 40;
|
||||
var materialRenderingType = elementCount >> 2 & 0xf;
|
||||
var supportsBumpMapping = (elementCount & 2) != 0;
|
||||
var isParticleEffect = elementCount & 40;
|
||||
|
||||
// Reading content
|
||||
var stageCount = fs.ReadUInt16LittleEndian();
|
||||
@@ -25,113 +17,140 @@ public static class MaterialParser
|
||||
|
||||
uint magic = (uint)magic1;
|
||||
|
||||
// Defaults found in C code
|
||||
file.GlobalAlphaMultiplier = 1.0f; // field8_0x15c
|
||||
file.GlobalEmissiveIntensity = 0.0f; // field9_0x160
|
||||
file.SourceBlendMode = BlendMode.Unknown; // field6_0x154
|
||||
file.DestBlendMode = BlendMode.Unknown; // field7_0x158
|
||||
// Значения по умолчанию из C-кода.
|
||||
var globalAlphaMultiplier = 1.0f; // field8_0x15c
|
||||
var globalEmissiveIntensity = 0.0f; // field9_0x160
|
||||
var sourceBlendMode = BlendMode.Unknown; // field6_0x154
|
||||
var destBlendMode = BlendMode.Unknown; // field7_0x158
|
||||
|
||||
if (magic >= 2)
|
||||
{
|
||||
file.SourceBlendMode = (BlendMode)fs.ReadByte();
|
||||
file.DestBlendMode = (BlendMode)fs.ReadByte();
|
||||
sourceBlendMode = (BlendMode)fs.ReadByte();
|
||||
destBlendMode = (BlendMode)fs.ReadByte();
|
||||
}
|
||||
|
||||
if (magic > 2)
|
||||
{
|
||||
file.GlobalAlphaMultiplier = fs.ReadFloatLittleEndian();
|
||||
globalAlphaMultiplier = fs.ReadFloatLittleEndian();
|
||||
}
|
||||
|
||||
if (magic > 3)
|
||||
{
|
||||
file.GlobalEmissiveIntensity = fs.ReadFloatLittleEndian();
|
||||
globalEmissiveIntensity = fs.ReadFloatLittleEndian();
|
||||
}
|
||||
|
||||
// --- 2. Material Stages ---
|
||||
// Стадии материала.
|
||||
const float Inv255 = 1.0f / 255.0f;
|
||||
const float Field7Mult = 0.01f;
|
||||
Span<byte> textureNameBuffer = stackalloc byte[16];
|
||||
List<MaterialStage> stages = [];
|
||||
|
||||
for (int i = 0; i < stageCount; i++)
|
||||
{
|
||||
var stage = new MaterialStage();
|
||||
|
||||
// === FILE READ ORDER (matches decompiled.c lines 159-217) ===
|
||||
// Порядок чтения соответствует файлу, а не C struct layout.
|
||||
|
||||
// 1. Ambient (4 bytes, A scaled by 0.01) - Lines 159-168
|
||||
stage.AmbientR = fs.ReadByte() * Inv255;
|
||||
stage.AmbientG = fs.ReadByte() * Inv255;
|
||||
stage.AmbientB = fs.ReadByte() * Inv255;
|
||||
stage.AmbientA = fs.ReadByte() * Field7Mult; // 0.01 scaling
|
||||
// Ambient: 4 байта, A масштабируется на 0.01.
|
||||
var ambientR = fs.ReadByte() * Inv255;
|
||||
var ambientG = fs.ReadByte() * Inv255;
|
||||
var ambientB = fs.ReadByte() * Inv255;
|
||||
var ambientA = fs.ReadByte() * Field7Mult; // 0.01 scaling
|
||||
|
||||
// 2. Diffuse (4 bytes) - Lines 171-180
|
||||
stage.DiffuseR = fs.ReadByte() * Inv255;
|
||||
stage.DiffuseG = fs.ReadByte() * Inv255;
|
||||
stage.DiffuseB = fs.ReadByte() * Inv255;
|
||||
stage.DiffuseA = fs.ReadByte() * Inv255;
|
||||
// Diffuse: 4 байта.
|
||||
var diffuseR = fs.ReadByte() * Inv255;
|
||||
var diffuseG = fs.ReadByte() * Inv255;
|
||||
var diffuseB = fs.ReadByte() * Inv255;
|
||||
var diffuseA = fs.ReadByte() * Inv255;
|
||||
|
||||
// 3. Specular (4 bytes) - Lines 183-192
|
||||
stage.SpecularR = fs.ReadByte() * Inv255;
|
||||
stage.SpecularG = fs.ReadByte() * Inv255;
|
||||
stage.SpecularB = fs.ReadByte() * Inv255;
|
||||
stage.SpecularA = fs.ReadByte() * Inv255;
|
||||
// Specular: 4 байта.
|
||||
var specularR = fs.ReadByte() * Inv255;
|
||||
var specularG = fs.ReadByte() * Inv255;
|
||||
var specularB = fs.ReadByte() * Inv255;
|
||||
var specularA = fs.ReadByte() * Inv255;
|
||||
|
||||
// 4. Emissive (4 bytes) - Lines 195-204
|
||||
stage.EmissiveR = fs.ReadByte() * Inv255;
|
||||
stage.EmissiveG = fs.ReadByte() * Inv255;
|
||||
stage.EmissiveB = fs.ReadByte() * Inv255;
|
||||
stage.EmissiveA = fs.ReadByte() * Inv255;
|
||||
// Emissive: 4 байта.
|
||||
var emissiveR = fs.ReadByte() * Inv255;
|
||||
var emissiveG = fs.ReadByte() * Inv255;
|
||||
var emissiveB = fs.ReadByte() * Inv255;
|
||||
var emissiveA = fs.ReadByte() * Inv255;
|
||||
|
||||
// 5. Power (1 byte → float) - Line 207
|
||||
stage.Power = (float)fs.ReadByte();
|
||||
// Power: 1 байт -> float.
|
||||
var power = (float)fs.ReadByte();
|
||||
|
||||
// 6. Texture Stage Index (1 byte) - Line 210
|
||||
stage.TextureStageIndex = fs.ReadByte();
|
||||
// Texture stage index: 1 байт.
|
||||
var textureStageIndex = fs.ReadByte();
|
||||
|
||||
// 7. Texture Name (16 bytes) - Lines 212-217
|
||||
// Texture name: 16 байт.
|
||||
textureNameBuffer.Clear();
|
||||
fs.ReadExactly(textureNameBuffer);
|
||||
stage.TextureName = Encoding.ASCII.GetString(textureNameBuffer).TrimEnd('\0');
|
||||
var textureName = Encoding.ASCII.GetString(textureNameBuffer).TrimEnd('\0');
|
||||
|
||||
file.Stages.Add(stage);
|
||||
stages.Add(new MaterialStage(
|
||||
ambientR,
|
||||
ambientG,
|
||||
ambientB,
|
||||
ambientA,
|
||||
diffuseR,
|
||||
diffuseG,
|
||||
diffuseB,
|
||||
diffuseA,
|
||||
specularR,
|
||||
specularG,
|
||||
specularB,
|
||||
specularA,
|
||||
emissiveR,
|
||||
emissiveG,
|
||||
emissiveB,
|
||||
emissiveA,
|
||||
power,
|
||||
textureStageIndex,
|
||||
textureName));
|
||||
}
|
||||
|
||||
// --- 3. Animations ---
|
||||
// Анимации.
|
||||
List<MaterialAnimation> animations = [];
|
||||
for (int i = 0; i < animCount; i++)
|
||||
{
|
||||
var anim = new MaterialAnimation();
|
||||
|
||||
uint typeAndParams = fs.ReadUInt32LittleEndian();
|
||||
anim.Target = (AnimationTarget)(typeAndParams >> 3);
|
||||
anim.LoopMode = (AnimationLoopMode)(typeAndParams & 7);
|
||||
var target = (AnimationTarget)(typeAndParams >> 3);
|
||||
var loopMode = (AnimationLoopMode)(typeAndParams & 7);
|
||||
|
||||
ushort keyCount = fs.ReadUInt16LittleEndian();
|
||||
List<AnimKey> keys = [];
|
||||
|
||||
for (int k = 0; k < keyCount; k++)
|
||||
{
|
||||
var key = new AnimKey
|
||||
{
|
||||
StageIndex = fs.ReadUInt16LittleEndian(),
|
||||
DurationMs = fs.ReadUInt16LittleEndian(),
|
||||
InterpolationCurve = fs.ReadUInt16LittleEndian()
|
||||
};
|
||||
anim.Keys.Add(key);
|
||||
keys.Add(new AnimKey(
|
||||
fs.ReadUInt16LittleEndian(),
|
||||
fs.ReadUInt16LittleEndian(),
|
||||
fs.ReadUInt16LittleEndian()));
|
||||
}
|
||||
|
||||
// Precompute description for UI to avoid per-frame allocations
|
||||
anim.TargetDescription = ComputeTargetDescription(anim.Target);
|
||||
// Описание вычисляется один раз при парсинге, чтобы UI не выделял память каждый кадр.
|
||||
var targetDescription = ComputeTargetDescription(target);
|
||||
|
||||
file.Animations.Add(anim);
|
||||
animations.Add(new MaterialAnimation(target, loopMode, keys, targetDescription));
|
||||
}
|
||||
|
||||
return file;
|
||||
return new MaterialFile(
|
||||
fileName,
|
||||
elementCount,
|
||||
magic1,
|
||||
materialRenderingType,
|
||||
supportsBumpMapping,
|
||||
isParticleEffect,
|
||||
sourceBlendMode,
|
||||
destBlendMode,
|
||||
globalAlphaMultiplier,
|
||||
globalEmissiveIntensity,
|
||||
stages,
|
||||
animations);
|
||||
}
|
||||
|
||||
private static string ComputeTargetDescription(AnimationTarget target)
|
||||
{
|
||||
// Precompute the description once during parsing
|
||||
if ((int)target == 0)
|
||||
return "No interpolation - entire stage is copied as-is (Flags: 0x0)";
|
||||
return "Без интерполяции - вся стадия копируется как есть (Flags: 0x0)";
|
||||
|
||||
var parts = new List<string>();
|
||||
|
||||
@@ -146,7 +165,7 @@ public static class MaterialParser
|
||||
if ((target & AnimationTarget.Power) != 0)
|
||||
parts.Add("Ambient.A + Power");
|
||||
|
||||
return $"Interpolates: {string.Join(", ", parts)} | Other components copied (Flags: 0x{(int)target:X})";
|
||||
return $"Интерполируется: {string.Join(", ", parts)} | Остальные компоненты копируются (Flags: 0x{(int)target:X})";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user