2025-12-04 03:50:29 +03:00
using System.Numerics ;
using ImGuiNET ;
using MaterialLib ;
using NResUI.Abstractions ;
using NResUI.Models ;
namespace NResUI.ImGuiUI ;
public class MaterialExplorerPanel : IImGuiPanel
{
private readonly MaterialViewModel _viewModel ;
public MaterialExplorerPanel ( MaterialViewModel viewModel )
{
_viewModel = viewModel ;
}
public void OnImGuiRender ( )
{
if ( ImGui . Begin ( "Material Explorer" ) )
{
2025-12-04 03:55:41 +03:00
ImGui . Text ( "Материал это бинарный файл, который можно найти в materials.lib." ) ;
ImGui . Text ( "Е г о можно открыть только из открытого NRes архива, т.к. чтение полагается на флаги из архива" ) ;
2025-12-04 03:50:29 +03:00
if ( ! _viewModel . HasFile )
{
ImGui . Text ( "No Material file opened" ) ;
}
else if ( _viewModel . Error ! = null )
{
ImGui . TextColored ( new Vector4 ( 1 , 0 , 0 , 1 ) , $"Error: {_viewModel.Error}" ) ;
}
else
{
var mat = _viewModel . MaterialFile ! ;
ImGui . Text ( $"File: {_viewModel.FilePath}" ) ;
ImGui . Separator ( ) ;
// === FILE READ SEQUENCE ===
if ( ImGui . CollapsingHeader ( "1. Metadata & Derived Fields" , ImGuiTreeNodeFlags . DefaultOpen ) )
{
ImGui . TextDisabled ( "(Not from file content)" ) ;
ImGui . Text ( $"File Name: {mat.FileName}" ) ;
ImGui . Text ( $"Version (ElementCount): {mat.Version}" ) ;
ImGui . Text ( $"Magic1: {mat.Magic1}" ) ;
ImGui . Separator ( ) ;
ImGui . TextDisabled ( "(Derived from Version)" ) ;
ImGui . Text ( $"Material Rendering Type: {mat.MaterialRenderingType}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( "0=Standard, 1=Special, 2=Particle/Effect" ) ;
}
ImGui . Text ( $"Supports Bump Mapping: {mat.SupportsBumpMapping}" ) ;
ImGui . Text ( $"Is Particle Effect: {mat.IsParticleEffect}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( "0=Normal material, 8=Particle/Effect (e.g., jet engines)" ) ;
}
}
if ( ImGui . CollapsingHeader ( "2. File Header (Read Order)" , ImGuiTreeNodeFlags . DefaultOpen ) )
{
ImGui . Text ( $"Stage Count: {mat.Stages.Count}" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(2 bytes, ushort)" ) ;
ImGui . Text ( $"Animation Count: {mat.Animations.Count}" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(2 bytes, ushort)" ) ;
ImGui . Separator ( ) ;
ImGui . TextDisabled ( $"(Read if Magic1 >= 2)" ) ;
ImGui . Text ( $"Source Blend Mode: {mat.SourceBlendMode}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( GetBlendModeDescription ( mat . SourceBlendMode ) ) ;
}
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(1 byte)" ) ;
ImGui . Text ( $"Dest Blend Mode: {mat.DestBlendMode}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( GetBlendModeDescription ( mat . DestBlendMode ) ) ;
}
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(1 byte)" ) ;
ImGui . Separator ( ) ;
2025-12-04 03:55:41 +03:00
ImGui . TextDisabled ( "(Read if Magic1 > 2)" ) ;
2025-12-04 03:50:29 +03:00
ImGui . Text ( $"Global Alpha Multiplier: {mat.GlobalAlphaMultiplier:F3}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( "Always 1.0 in all 628 materials - global alpha multiplier" ) ;
}
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(4 bytes, float)" ) ;
ImGui . Separator ( ) ;
2025-12-04 03:55:41 +03:00
ImGui . TextDisabled ( "(Read if Magic1 > 3)" ) ;
2025-12-04 03:50:29 +03:00
ImGui . Text ( $"Global Emissive Intensity: {mat.GlobalEmissiveIntensity:F3}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( "0=no glow (99.4%), rare values: 1000, 10000" ) ;
}
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(4 bytes, float)" ) ;
}
if ( ImGui . CollapsingHeader ( $"3. Stages ({mat.Stages.Count}) - 34 bytes each" , ImGuiTreeNodeFlags . DefaultOpen ) )
{
for ( int i = 0 ; i < mat . Stages . Count ; i + + )
{
var stage = mat . Stages [ i ] ;
if ( ImGui . TreeNode ( $"Stage {i}: {stage.TextureName}" ) )
{
ImGui . TextDisabled ( "=== File Read Order (34 bytes) ===" ) ;
// 1. Ambient (4 bytes)
ImGui . Text ( "1. Ambient Color:" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(4 bytes, A scaled by 0.01)" ) ;
var ambient = new Vector4 ( stage . AmbientR , stage . AmbientG , stage . AmbientB , stage . AmbientA ) ;
ImGui . ColorEdit4 ( "##Ambient" , ref ambient , ImGuiColorEditFlags . NoInputs ) ;
ImGui . SameLine ( ) ;
ImGui . Text ( $"RGBA: ({stage.AmbientR:F3}, {stage.AmbientG:F3}, {stage.AmbientB:F3}, {stage.AmbientA:F3})" ) ;
// 2. Diffuse (4 bytes)
ImGui . Text ( "2. Diffuse Color:" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(4 bytes)" ) ;
var diffuse = new Vector4 ( stage . DiffuseR , stage . DiffuseG , stage . DiffuseB , stage . DiffuseA ) ;
ImGui . ColorEdit4 ( "##Diffuse" , ref diffuse , ImGuiColorEditFlags . NoInputs ) ;
ImGui . SameLine ( ) ;
ImGui . Text ( $"RGBA: ({stage.DiffuseR:F3}, {stage.DiffuseG:F3}, {stage.DiffuseB:F3}, {stage.DiffuseA:F3})" ) ;
// 3. Specular (4 bytes)
ImGui . Text ( "3. Specular Color:" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(4 bytes)" ) ;
var specular = new Vector4 ( stage . SpecularR , stage . SpecularG , stage . SpecularB , stage . SpecularA ) ;
ImGui . ColorEdit4 ( "##Specular" , ref specular , ImGuiColorEditFlags . NoInputs ) ;
ImGui . SameLine ( ) ;
ImGui . Text ( $"RGBA: ({stage.SpecularR:F3}, {stage.SpecularG:F3}, {stage.SpecularB:F3}, {stage.SpecularA:F3})" ) ;
// 4. Emissive (4 bytes)
ImGui . Text ( "4. Emissive Color:" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(4 bytes)" ) ;
var emissive = new Vector4 ( stage . EmissiveR , stage . EmissiveG , stage . EmissiveB , stage . EmissiveA ) ;
ImGui . ColorEdit4 ( "##Emissive" , ref emissive , ImGuiColorEditFlags . NoInputs ) ;
ImGui . SameLine ( ) ;
ImGui . Text ( $"RGBA: ({stage.EmissiveR:F3}, {stage.EmissiveG:F3}, {stage.EmissiveB:F3}, {stage.EmissiveA:F3})" ) ;
// 5. Power (1 byte)
ImGui . Text ( $"5. Power: {stage.Power:F3}" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(1 byte -> float)" ) ;
// 6. Texture Stage Index (1 byte)
ImGui . Text ( $"6. Texture Stage Index: {stage.TextureStageIndex}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( "255 = not set/default, 0-47 = reference to specific texture stage" ) ;
}
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(1 byte)" ) ;
// 7. Texture Name (16 bytes)
ImGui . Text ( $"7. Texture Name: {stage.TextureName}" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(16 bytes, ASCII)" ) ;
ImGui . TreePop ( ) ;
}
}
}
if ( ImGui . CollapsingHeader ( $"4. Animations ({mat.Animations.Count})" ) )
{
for ( int i = 0 ; i < mat . Animations . Count ; i + + )
{
var anim = mat . Animations [ i ] ;
if ( ImGui . TreeNode ( $"Anim {i}: {anim.Target} ({anim.LoopMode})" ) )
{
ImGui . TextDisabled ( "=== File Read Order ===" ) ;
// Combined field (4 bytes)
ImGui . Text ( "1. Target & Loop Mode:" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(4 bytes combined)" ) ;
ImGui . Indent ( ) ;
ImGui . Text ( $"Target (bits 3-31): {anim.Target}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( anim . TargetDescription ) ;
}
ImGui . Text ( $"Loop Mode (bits 0-2): {anim.LoopMode}" ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( GetAnimationLoopModeDescription ( anim . LoopMode ) ) ;
}
ImGui . Unindent ( ) ;
// Key count (2 bytes)
ImGui . Text ( $"2. Key Count: {anim.Keys.Count}" ) ;
ImGui . SameLine ( ) ;
ImGui . TextDisabled ( "(2 bytes, ushort)" ) ;
// Keys (6 bytes each)
ImGui . Text ( $"3. Keys ({anim.Keys.Count} × 6 bytes):" ) ;
if ( ImGui . BeginTable ( $"keys_{i}" , 4 , ImGuiTableFlags . Borders | ImGuiTableFlags . RowBg ) )
{
ImGui . TableSetupColumn ( "#" ) ;
ImGui . TableSetupColumn ( "Stage Index (2 bytes)" ) ;
ImGui . TableSetupColumn ( "Duration ms (2 bytes)" ) ;
ImGui . TableSetupColumn ( "Interpolation (2 bytes)" ) ;
ImGui . TableHeadersRow ( ) ;
int keyIdx = 0 ;
foreach ( var key in anim . Keys )
{
ImGui . TableNextRow ( ) ;
ImGui . TableNextColumn ( ) ;
ImGui . Text ( keyIdx . ToString ( ) ) ;
ImGui . TableNextColumn ( ) ;
ImGui . Text ( key . StageIndex . ToString ( ) ) ;
ImGui . TableNextColumn ( ) ;
ImGui . Text ( key . DurationMs . ToString ( ) ) ;
ImGui . TableNextColumn ( ) ;
ImGui . Text ( key . InterpolationCurve . ToString ( ) ) ;
if ( ImGui . IsItemHovered ( ) )
{
ImGui . SetTooltip ( "Always 0 = linear interpolation" ) ;
}
keyIdx + + ;
}
ImGui . EndTable ( ) ;
}
ImGui . TreePop ( ) ;
}
}
}
}
}
ImGui . End ( ) ;
}
private static string GetBlendModeDescription ( BlendMode mode )
{
return mode switch
{
2025-12-04 07:58:32 +03:00
BlendMode . Zero = > "D3DBLEND_ZERO\nBlend factor is (0, 0, 0, 0)\nResults in black/transparent" ,
BlendMode . One = > "D3DBLEND_ONE\nBlend factor is (1, 1, 1, 1)\nUses full color value (no blending)" ,
BlendMode . SrcColor = > "D3DBLEND_SRCCOLOR\nBlend factor is (Rs, Gs, Bs, As)\nUses source color" ,
BlendMode . InvSrcColor = > "D3DBLEND_INVSRCCOLOR\nBlend factor is (1-Rs, 1-Gs, 1-Bs, 1-As)\nUses inverted source color" ,
BlendMode . SrcAlpha = > "D3DBLEND_SRCALPHA\nBlend factor is (As, As, As, As)\nUses source alpha for all channels\n Standard transparency (common with InvSrcAlpha for dest)" ,
BlendMode . InvSrcAlpha = > "D3DBLEND_INVSRCALPHA\nBlend factor is (1-As, 1-As, 1-As, 1-As)\nUses inverted source alpha\n Standard transparency (common with SrcAlpha for source)" ,
BlendMode . DestAlpha = > "D3DBLEND_DESTALPHA\nBlend factor is (Ad, Ad, Ad, Ad)\nUses destination alpha" ,
BlendMode . InvDestAlpha = > "D3DBLEND_INVDESTALPHA\nBlend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad)\nUses inverted destination alpha" ,
BlendMode . DestColor = > "D3DBLEND_DESTCOLOR\nBlend factor is (Rd, Gd, Bd, Ad)\nUses destination color" ,
BlendMode . InvDestColor = > "D3DBLEND_INVDESTCOLOR\nBlend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad)\nUses inverted destination color" ,
BlendMode . SrcAlphaSat = > "D3DBLEND_SRCALPHASAT\nBlend factor is (f, f, f, 1) where f = min(As, 1-Ad)\nSaturates source alpha" ,
BlendMode . BothSrcAlpha = > "D3DBLEND_BOTHSRCALPHA (Obsolete in D3D9+)\nBlend factor is (As, As, As, As) for both source and dest\nSource: (As, As, As, As), Dest: (1-As, 1-As, 1-As, 1-As)" ,
BlendMode . BothInvSrcAlpha = > "D3DBLEND_BOTHINVSRCALPHA (Obsolete in D3D9+)\nBlend factor is (1-As, 1-As, 1-As, 1-As) for both source and dest\nSource: (1-As, 1-As, 1-As, 1-As), Dest: (As, As, As, As)" ,
BlendMode . Unknown = > "Unknown/Default (0xFF)\nUninitialized or opaque rendering" ,
2025-12-04 03:50:29 +03:00
_ = > "Unknown blend mode"
} ;
}
private static string GetAnimationLoopModeDescription ( AnimationLoopMode mode )
{
return mode switch
{
AnimationLoopMode . Loop = > "Loop: Animation repeats continuously from start to end" ,
AnimationLoopMode . PingPong = > "PingPong: Animation plays forward then backward repeatedly" ,
AnimationLoopMode . Clamp = > "Clamp: Animation plays once and holds at the final value" ,
AnimationLoopMode . Random = > "Random: Animation selects random keyframes" ,
_ = > "Unknown loop mode"
} ;
}
}