Files
parkan-playground/MaterialLib/MaterialParser.cs
T

177 lines
6.2 KiB
C#
Raw Permalink Normal View History

2025-12-04 03:50:29 +03:00
using System.Buffers.Binary;
using System.Text;
using Common;
2025-12-04 03:50:29 +03:00
namespace MaterialLib;
public static class MaterialParser
{
2026-06-06 21:26:56 +03:00
public static MaterialFile ReadFromStream(Stream fs, string fileName, uint elementCount, int magic1)
2025-12-04 03:50:29 +03:00
{
2026-05-09 20:36:43 +03:00
var materialRenderingType = elementCount >> 2 & 0xf;
var supportsBumpMapping = (elementCount & 2) != 0;
var isParticleEffect = elementCount & 40;
2025-12-04 03:50:29 +03:00
// Reading content
var stageCount = fs.ReadUInt16LittleEndian();
var animCount = fs.ReadUInt16LittleEndian();
uint magic = (uint)magic1;
2026-05-09 20:36:43 +03:00
// Значения по умолчанию из 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
2025-12-04 03:50:29 +03:00
if (magic >= 2)
{
2026-05-09 20:36:43 +03:00
sourceBlendMode = (BlendMode)fs.ReadByte();
destBlendMode = (BlendMode)fs.ReadByte();
2025-12-04 03:50:29 +03:00
}
if (magic > 2)
{
2026-05-09 20:36:43 +03:00
globalAlphaMultiplier = fs.ReadFloatLittleEndian();
2025-12-04 03:50:29 +03:00
}
if (magic > 3)
{
2026-05-09 20:36:43 +03:00
globalEmissiveIntensity = fs.ReadFloatLittleEndian();
2025-12-04 03:50:29 +03:00
}
2026-05-09 20:36:43 +03:00
// Стадии материала.
2025-12-04 03:50:29 +03:00
const float Inv255 = 1.0f / 255.0f;
const float Field7Mult = 0.01f;
Span<byte> textureNameBuffer = stackalloc byte[16];
2026-05-09 20:36:43 +03:00
List<MaterialStage> stages = [];
2025-12-04 03:50:29 +03:00
for (int i = 0; i < stageCount; i++)
{
2026-05-09 20:36:43 +03:00
// Порядок чтения соответствует файлу, а не C struct layout.
2025-12-04 03:50:29 +03:00
2026-05-09 20:36:43 +03:00
// 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
// Diffuse: 4 байта.
var diffuseR = fs.ReadByte() * Inv255;
var diffuseG = fs.ReadByte() * Inv255;
var diffuseB = fs.ReadByte() * Inv255;
var diffuseA = 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;
// Emissive: 4 байта.
var emissiveR = fs.ReadByte() * Inv255;
var emissiveG = fs.ReadByte() * Inv255;
var emissiveB = fs.ReadByte() * Inv255;
var emissiveA = fs.ReadByte() * Inv255;
// Power: 1 байт -> float.
var power = (float)fs.ReadByte();
2025-12-04 03:50:29 +03:00
2026-05-09 20:36:43 +03:00
// Texture stage index: 1 байт.
var textureStageIndex = fs.ReadByte();
2025-12-04 03:50:29 +03:00
2026-05-09 20:36:43 +03:00
// Texture name: 16 байт.
2025-12-04 03:50:29 +03:00
textureNameBuffer.Clear();
fs.ReadExactly(textureNameBuffer);
2026-06-07 15:55:56 +03:00
var terminator = textureNameBuffer.IndexOf((byte)0);
if (terminator == -1)
{
terminator = textureNameBuffer.Length - 1;
}
var textureName = Encoding.ASCII.GetString(textureNameBuffer[..terminator]);
2026-05-09 20:36:43 +03:00
stages.Add(new MaterialStage(
ambientR,
ambientG,
ambientB,
ambientA,
diffuseR,
diffuseG,
diffuseB,
diffuseA,
specularR,
specularG,
specularB,
specularA,
emissiveR,
emissiveG,
emissiveB,
emissiveA,
power,
textureStageIndex,
textureName));
2025-12-04 03:50:29 +03:00
}
2026-05-09 20:36:43 +03:00
// Анимации.
List<MaterialAnimation> animations = [];
2025-12-04 03:50:29 +03:00
for (int i = 0; i < animCount; i++)
{
uint typeAndParams = fs.ReadUInt32LittleEndian();
2026-05-09 20:36:43 +03:00
var target = (AnimationTarget)(typeAndParams >> 3);
var loopMode = (AnimationLoopMode)(typeAndParams & 7);
2025-12-04 03:50:29 +03:00
ushort keyCount = fs.ReadUInt16LittleEndian();
2026-05-09 20:36:43 +03:00
List<AnimKey> keys = [];
2025-12-04 03:50:29 +03:00
for (int k = 0; k < keyCount; k++)
{
2026-05-09 20:36:43 +03:00
keys.Add(new AnimKey(
fs.ReadUInt16LittleEndian(),
fs.ReadUInt16LittleEndian(),
fs.ReadUInt16LittleEndian()));
2025-12-04 03:50:29 +03:00
}
2026-05-09 20:36:43 +03:00
// Описание вычисляется один раз при парсинге, чтобы UI не выделял память каждый кадр.
var targetDescription = ComputeTargetDescription(target);
2025-12-04 03:50:29 +03:00
2026-05-09 20:36:43 +03:00
animations.Add(new MaterialAnimation(target, loopMode, keys, targetDescription));
2025-12-04 03:50:29 +03:00
}
2026-05-09 20:36:43 +03:00
return new MaterialFile(
fileName,
elementCount,
magic1,
materialRenderingType,
supportsBumpMapping,
isParticleEffect,
sourceBlendMode,
destBlendMode,
globalAlphaMultiplier,
globalEmissiveIntensity,
stages,
animations);
2025-12-04 03:50:29 +03:00
}
private static string ComputeTargetDescription(AnimationTarget target)
{
// Precompute the description once during parsing
if ((int)target == 0)
2026-05-09 20:36:43 +03:00
return "Без интерполяции - вся стадия копируется как есть (Flags: 0x0)";
2025-12-04 03:50:29 +03:00
var parts = new List<string>();
if ((target & AnimationTarget.Ambient) != 0)
parts.Add("Ambient RGB");
if ((target & AnimationTarget.Diffuse) != 0)
parts.Add("Diffuse RGB");
if ((target & AnimationTarget.Specular) != 0)
parts.Add("Specular RGB");
if ((target & AnimationTarget.Emissive) != 0)
parts.Add("Emissive RGB");
if ((target & AnimationTarget.Power) != 0)
parts.Add("Ambient.A + Power");
2026-05-09 20:36:43 +03:00
return $"Интерполируется: {string.Join(", ", parts)} | Остальные компоненты копируются (Flags: 0x{(int)target:X})";
2025-12-04 03:50:29 +03:00
}
}