diff --git a/DarkUI/DarkUI.WPF.Demo/MainWindow.xaml b/DarkUI/DarkUI.WPF.Demo/MainWindow.xaml
index c4afe2ee1..d0d0bfa4f 100644
--- a/DarkUI/DarkUI.WPF.Demo/MainWindow.xaml
+++ b/DarkUI/DarkUI.WPF.Demo/MainWindow.xaml
@@ -119,6 +119,7 @@
+
@@ -944,6 +945,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/// A custom control for displaying material map previews with a checker pattern background.
+/// Displays an image preview with a standardized checker pattern background for transparency visualization.
+///
+public class MaterialMapPreview : Control
+{
+ #region Dependency Properties
+
+ ///
+ /// The image source to display in the preview.
+ ///
+ public static readonly DependencyProperty ImageSourceProperty =
+ DependencyProperty.Register(
+ nameof(ImageSource),
+ typeof(ImageSource),
+ typeof(MaterialMapPreview),
+ new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
+
+ ///
+ /// The stretch mode for the image.
+ ///
+ public static readonly DependencyProperty StretchProperty =
+ DependencyProperty.Register(
+ nameof(Stretch),
+ typeof(Stretch),
+ typeof(MaterialMapPreview),
+ new FrameworkPropertyMetadata(Stretch.Uniform, FrameworkPropertyMetadataOptions.AffectsRender));
+
+ ///
+ /// The stretch direction for the image.
+ ///
+ public static readonly DependencyProperty StretchDirectionProperty =
+ DependencyProperty.Register(
+ nameof(StretchDirection),
+ typeof(StretchDirection),
+ typeof(MaterialMapPreview),
+ new FrameworkPropertyMetadata(StretchDirection.Both, FrameworkPropertyMetadataOptions.AffectsRender));
+
+ #endregion Dependency Properties
+
+ #region Properties
+
+ ///
+ /// Gets or sets the image source to display in the preview.
+ ///
+ public ImageSource? ImageSource
+ {
+ get => (ImageSource?)GetValue(ImageSourceProperty);
+ set => SetValue(ImageSourceProperty, value);
+ }
+
+ ///
+ /// Gets or sets the stretch mode for the image.
+ ///
+ public Stretch Stretch
+ {
+ get => (Stretch)GetValue(StretchProperty);
+ set => SetValue(StretchProperty, value);
+ }
+
+ ///
+ /// Gets or sets the stretch direction for the image.
+ ///
+ public StretchDirection StretchDirection
+ {
+ get => (StretchDirection)GetValue(StretchDirectionProperty);
+ set => SetValue(StretchDirectionProperty, value);
+ }
+
+ #endregion Properties
+
+ #region Constructor
+
+ static MaterialMapPreview()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(MaterialMapPreview), new FrameworkPropertyMetadata(typeof(MaterialMapPreview)));
+ }
+
+ #endregion Constructor
+}
diff --git a/DarkUI/DarkUI.WPF/Generic.xaml b/DarkUI/DarkUI.WPF/Generic.xaml
index 11820d9ed..4499e6707 100644
--- a/DarkUI/DarkUI.WPF/Generic.xaml
+++ b/DarkUI/DarkUI.WPF/Generic.xaml
@@ -13,6 +13,7 @@
+
diff --git a/DarkUI/DarkUI.WPF/Styles/MaterialMapPreview.xaml b/DarkUI/DarkUI.WPF/Styles/MaterialMapPreview.xaml
new file mode 100644
index 000000000..b10093f35
--- /dev/null
+++ b/DarkUI/DarkUI.WPF/Styles/MaterialMapPreview.xaml
@@ -0,0 +1,66 @@
+
+
+
+
\ No newline at end of file
diff --git a/TombEditor/ToolWindows/TexturePanel.cs b/TombEditor/ToolWindows/TexturePanel.cs
index 48c904f04..c05c87196 100644
--- a/TombEditor/ToolWindows/TexturePanel.cs
+++ b/TombEditor/ToolWindows/TexturePanel.cs
@@ -7,8 +7,11 @@
using TombEditor.Controls.ContextMenus;
using TombEditor.Forms;
using TombLib.Forms;
+using TombLib.Forms.ViewModels;
+using TombLib.Forms.Views;
using TombLib.LevelData;
using TombLib.Utils;
+using TombLib.WPF;
namespace TombEditor.ToolWindows
{
@@ -140,7 +143,7 @@ private void UpdateUI()
butAnimationRanges.Enabled =
butMaterialEditor.Enabled = comboCurrentTexture.SelectedItem != null;
- butTextureSounds.Enabled = comboCurrentTexture.SelectedItem != null &&
+ butTextureSounds.Enabled = comboCurrentTexture.SelectedItem != null &&
_editor.Level.Settings.GameVersion.Native() >= TRVersion.Game.TR3;
butBumpMaps.Enabled = comboCurrentTexture.SelectedItem != null &&
@@ -281,11 +284,15 @@ protected override void OnMouseUp(MouseEventArgs e)
private void butMaterialEditor_Click(object sender, EventArgs e)
{
var list = comboCurrentTexture.Items.Cast();
- using (var form = new FormMaterialEditor(list, _editor.Configuration, comboCurrentTexture.SelectedItem as Texture))
- {
- if (form.ShowDialog() == DialogResult.OK && form.MaterialChanged)
- _editor.SendMessage("Material settings for selected texture were saved to " + form.MaterialFileName + ".", PopupType.Info);
- }
+
+ var viewModel = new MaterialEditorWindowViewModel(list.ToList(), comboCurrentTexture.SelectedItem as Texture);
+
+ var window = new MaterialEditorWindow { DataContext = viewModel };
+ window.SetOwner(this);
+ window.ShowDialog();
+
+ if (viewModel.DialogResult == true)
+ _editor.SendMessage("Material settings for selected texture were saved to " + viewModel.MaterialFileName + ".", PopupType.Info);
}
}
}
diff --git a/TombLib/TombLib.Forms/ViewModels/MaterialEditorWindowViewModel.cs b/TombLib/TombLib.Forms/ViewModels/MaterialEditorWindowViewModel.cs
new file mode 100644
index 000000000..a389d32d7
--- /dev/null
+++ b/TombLib/TombLib.Forms/ViewModels/MaterialEditorWindowViewModel.cs
@@ -0,0 +1,465 @@
+#nullable enable
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using MvvmDialogs;
+using MvvmDialogs.FrameworkDialogs.OpenFile;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using TombLib.LevelData;
+using TombLib.Utils;
+using TombLib.WPF.Services;
+using TombLib.WPF.Services.Abstract;
+
+namespace TombLib.Forms.ViewModels;
+
+public partial class MaterialEditorWindowViewModel : ObservableObject, IModalDialogViewModel
+{
+ #region Properties and Fields
+
+ [ObservableProperty] private bool? _dialogResult;
+
+ /* Texture selection */
+
+ [ObservableProperty] private bool _isTextureSelectionVisible = true;
+ [ObservableProperty] private ObservableCollection _textureList = [];
+ [ObservableProperty] private string? _selectedTexture;
+
+ /* Material data */
+
+ [ObservableProperty] private MaterialData _materialData = new();
+ [ObservableProperty] private string _materialFileName = string.Empty;
+ private string _texturePath = string.Empty;
+
+ /* Material type */
+
+ [ObservableProperty] private ObservableCollection _materialTypes = [];
+ [ObservableProperty] private string _selectedMaterialType = "Default";
+
+ /* Map paths */
+
+ [ObservableProperty] private string _colorMapPath = string.Empty;
+ [ObservableProperty] private string _normalMapPath = string.Empty;
+ [ObservableProperty] private string _specularMapPath = string.Empty;
+ [ObservableProperty] private string _ambientOcclusionMapPath = string.Empty;
+ [ObservableProperty] private string _emissiveMapPath = string.Empty;
+ [ObservableProperty] private string _roughnessMapPath = string.Empty;
+
+ /* Map previews */
+
+ [ObservableProperty] private BitmapImage? _colorMapPreview;
+ [ObservableProperty] private BitmapImage? _normalMapPreview;
+ [ObservableProperty] private BitmapImage? _specularMapPreview;
+ [ObservableProperty] private BitmapImage? _ambientOcclusionMapPreview;
+ [ObservableProperty] private BitmapImage? _emissiveMapPreview;
+ [ObservableProperty] private BitmapImage? _roughnessMapPreview;
+
+ /* Map background brushes (for error indication) */
+
+ [ObservableProperty] private Brush _normalMapBackgroundBrush = Brushes.Transparent;
+ [ObservableProperty] private Brush _specularMapBackgroundBrush = Brushes.Transparent;
+ [ObservableProperty] private Brush _ambientOcclusionMapBackgroundBrush = Brushes.Transparent;
+ [ObservableProperty] private Brush _emissiveMapBackgroundBrush = Brushes.Transparent;
+ [ObservableProperty] private Brush _roughnessMapBackgroundBrush = Brushes.Transparent;
+
+ /* Material parameters */
+
+ [ObservableProperty] private float _normalMapStrength = 1.0f;
+ [ObservableProperty] private float _specularIntensity = 1.0f;
+
+ /* "Has" map flags for button enabling */
+
+ [ObservableProperty] private bool _hasNormalMap;
+ [ObservableProperty] private bool _hasSpecularMap;
+ [ObservableProperty] private bool _hasAmbientOcclusionMap;
+ [ObservableProperty] private bool _hasEmissiveMap;
+ [ObservableProperty] private bool _hasRoughnessMap;
+
+ private readonly Brush _correctColor = Brushes.Transparent;
+ private readonly Brush _wrongColor = new SolidColorBrush(Color.FromRgb(139, 69, 69));
+
+ private bool _saveXml = false;
+ private bool _loading = false;
+
+ /* Services */
+
+ private readonly IDialogService _dialogService;
+ private readonly IMessageService _messageService;
+
+ #endregion Properties and Fields
+
+ public MaterialEditorWindowViewModel(Texture texture) : this([texture], texture)
+ { }
+
+ public MaterialEditorWindowViewModel(
+ IEnumerable textureList,
+ Texture? selectedTexture = null,
+ IDialogService? dialogService = null,
+ IMessageService? messageService = null)
+ {
+ // Services
+ _dialogService = ServiceLocator.ResolveService(dialogService);
+ _messageService = ServiceLocator.ResolveService(messageService);
+
+ // Initialize material types
+ foreach (MaterialType matType in Enum.GetValues())
+ MaterialTypes.Add(matType.ToString().SplitCamelcase());
+
+ // Setup texture list
+ if (textureList is not null)
+ {
+ foreach (var tex in textureList)
+ TextureList.Add(tex.AbsolutePath);
+
+ SelectedTexture = selectedTexture is not null && textureList.Contains(selectedTexture)
+ ? selectedTexture.AbsolutePath
+ : TextureList.FirstOrDefault();
+ }
+ else
+ {
+ IsTextureSelectionVisible = false;
+ }
+
+ // Load initial material data
+ if (SelectedTexture is not null)
+ LoadMaterialForTexture(SelectedTexture);
+ }
+
+ partial void OnSelectedTextureChanged(string? value)
+ {
+ if (value is null || !_loading)
+ return;
+
+ if (_saveXml)
+ {
+ var result = _messageService.ShowConfirmation("Save changes to current material?", "Confirm changes");
+
+ if (result)
+ SaveMaterialProperties();
+
+ _saveXml = false;
+ }
+
+ LoadMaterialForTexture(value);
+ }
+
+ partial void OnSelectedMaterialTypeChanged(string value)
+ {
+ if (_loading)
+ return;
+
+ var materialType = (MaterialType)MaterialTypes.IndexOf(value);
+ MaterialData.Type = materialType;
+
+ LoadMaterialProperties();
+ _saveXml = true;
+ }
+
+ partial void OnNormalMapStrengthChanged(float value)
+ {
+ if (_loading)
+ return;
+
+ _saveXml = true;
+ }
+
+ partial void OnSpecularIntensityChanged(float value)
+ {
+ if (_loading)
+ return;
+
+ _saveXml = true;
+ }
+
+ private void LoadMaterialForTexture(string texturePath)
+ {
+ _texturePath = texturePath;
+
+ try
+ {
+ MaterialData = MaterialData.TrySidecarLoadOrLoadExisting(texturePath);
+ LoadMaterialInUI();
+ }
+ catch
+ {
+ _messageService.ShowError("There was an error while loading the selected material. Using default.");
+
+ MaterialData = new MaterialData() { ColorMap = texturePath };
+ _saveXml = true;
+
+ LoadMaterialInUI();
+ }
+ }
+
+ private void LoadMaterialInUI()
+ {
+ if (MaterialData is null)
+ return;
+
+ _loading = true;
+
+ // Set texture paths and previews
+ SetTexturePath(MaterialData.ColorMap, SetColorMapPath, SetColorMapPreview);
+ SetTexturePath(MaterialData.NormalMap, SetNormalMapPath, SetNormalMapPreview);
+ SetTexturePath(MaterialData.SpecularMap, SetSpecularMapPath, SetSpecularMapPreview);
+ SetTexturePath(MaterialData.AmbientOcclusionMap, SetAmbientOcclusionMapPath, SetAmbientOcclusionMapPreview);
+ SetTexturePath(MaterialData.EmissiveMap, SetEmissiveMapPath, SetEmissiveMapPreview);
+ SetTexturePath(MaterialData.RoughnessMap, SetRoughnessMapPath, SetRoughnessMapPreview);
+
+ // Update background colors based on file existence
+ UpdateMapBackgroundBrush(MaterialData.NormalMap, MaterialData.IsNormalMapFound, SetNormalMapBackgroundBrush);
+ UpdateMapBackgroundBrush(MaterialData.SpecularMap, MaterialData.IsSpecularMapFound, SetSpecularMapBackgroundBrush);
+ UpdateMapBackgroundBrush(MaterialData.AmbientOcclusionMap, MaterialData.IsAmbientOcclusionMapFound, SetAmbientOcclusionMapBackgroundBrush);
+ UpdateMapBackgroundBrush(MaterialData.EmissiveMap, MaterialData.IsEmissiveMapFound, SetEmissiveMapBackgroundBrush);
+ UpdateMapBackgroundBrush(MaterialData.RoughnessMap, MaterialData.IsRoughnessMapFound, SetRoughnessMapBackgroundBrush);
+
+ // Update has map flags
+ HasNormalMap = !string.IsNullOrEmpty(MaterialData.NormalMap);
+ HasSpecularMap = !string.IsNullOrEmpty(MaterialData.SpecularMap);
+ HasAmbientOcclusionMap = !string.IsNullOrEmpty(MaterialData.AmbientOcclusionMap);
+ HasEmissiveMap = !string.IsNullOrEmpty(MaterialData.EmissiveMap);
+ HasRoughnessMap = !string.IsNullOrEmpty(MaterialData.RoughnessMap);
+
+ // Set material type
+ SelectedMaterialType = MaterialTypes[(int)MaterialData.Type];
+
+ // Load material parameters
+ LoadMaterialProperties();
+
+ _loading = false;
+ }
+
+ private void LoadMaterialProperties()
+ {
+ switch (MaterialData.Type)
+ {
+ case MaterialType.Default:
+ NormalMapStrength = MaterialData.Parameters0.X;
+ SpecularIntensity = MaterialData.Parameters0.Y;
+ break;
+ }
+ }
+
+ private static void SetTexturePath(string? texturePath, Action setPath, Action setPreview)
+ {
+ // Set the path property
+ setPath(texturePath ?? string.Empty);
+
+ // Load texture preview
+ LoadTexturePreview(texturePath, setPreview);
+ }
+
+ private void UpdateMapBackgroundBrush(string? mapPath, bool isFound, Action setBrush)
+ {
+ if (string.IsNullOrEmpty(mapPath))
+ {
+ setBrush(_correctColor);
+ }
+ else
+ {
+ var brush = isFound ? _correctColor : _wrongColor;
+ setBrush(brush);
+ }
+ }
+
+ private static void LoadTexturePreview(string? path, Action setPreview)
+ {
+ BitmapImage? preview = null;
+
+ if (!string.IsNullOrEmpty(path) && File.Exists(path))
+ {
+ try
+ {
+ preview = new BitmapImage();
+ preview.BeginInit();
+ preview.UriSource = new Uri(path, UriKind.Absolute);
+ preview.CacheOption = BitmapCacheOption.OnLoad;
+ preview.EndInit();
+ preview.Freeze();
+ }
+ catch
+ {
+ preview = null;
+ }
+ }
+
+ setPreview(preview);
+ }
+
+ private void SetColorMapPath(string value) => ColorMapPath = value;
+ private void SetNormalMapPath(string value) => NormalMapPath = value;
+ private void SetSpecularMapPath(string value) => SpecularMapPath = value;
+ private void SetAmbientOcclusionMapPath(string value) => AmbientOcclusionMapPath = value;
+ private void SetEmissiveMapPath(string value) => EmissiveMapPath = value;
+ private void SetRoughnessMapPath(string value) => RoughnessMapPath = value;
+
+ private void SetColorMapPreview(BitmapImage? value) => ColorMapPreview = value;
+ private void SetNormalMapPreview(BitmapImage? value) => NormalMapPreview = value;
+ private void SetSpecularMapPreview(BitmapImage? value) => SpecularMapPreview = value;
+ private void SetAmbientOcclusionMapPreview(BitmapImage? value) => AmbientOcclusionMapPreview = value;
+ private void SetEmissiveMapPreview(BitmapImage? value) => EmissiveMapPreview = value;
+ private void SetRoughnessMapPreview(BitmapImage? value) => RoughnessMapPreview = value;
+
+ private void SetNormalMapBackgroundBrush(Brush value) => NormalMapBackgroundBrush = value;
+ private void SetSpecularMapBackgroundBrush(Brush value) => SpecularMapBackgroundBrush = value;
+ private void SetAmbientOcclusionMapBackgroundBrush(Brush value) => AmbientOcclusionMapBackgroundBrush = value;
+ private void SetEmissiveMapBackgroundBrush(Brush value) => EmissiveMapBackgroundBrush = value;
+ private void SetRoughnessMapBackgroundBrush(Brush value) => RoughnessMapBackgroundBrush = value;
+
+ private void BrowseTexture(Action setPath, Action setPreview, Action setBrush, Action setHasFlag)
+ {
+ var texturePath = BrowseForTexture();
+
+ if (!string.IsNullOrEmpty(texturePath))
+ {
+ SetTexturePath(texturePath, setPath, setPreview);
+ UpdateMapBackgroundBrush(texturePath, File.Exists(texturePath), setBrush);
+ setHasFlag(true);
+
+ _saveXml = true;
+ }
+ }
+
+ private void ClearTexture(Action setPath, Action setPreview, Action setBrush, Action setHasFlag)
+ {
+ SetTexturePath(string.Empty, setPath, setPreview);
+ UpdateMapBackgroundBrush(string.Empty, true, setBrush);
+ setHasFlag(false);
+
+ _saveXml = true;
+ }
+
+ private string? BrowseForTexture()
+ {
+ var settings = new OpenFileDialogSettings
+ {
+ Title = "Browse Texture",
+ Filter = ImageC.FileExtensions.GetFilter()
+ };
+
+ return _dialogService.ShowOpenFileDialog(this, settings) == true
+ ? settings.FileName
+ : null;
+ }
+
+ private void SaveMaterialProperties()
+ {
+ if (!_saveXml)
+ return;
+
+ string externalMaterialDataPath = Path.Combine(
+ Path.GetDirectoryName(_texturePath) ?? string.Empty,
+ Path.GetFileNameWithoutExtension(_texturePath) + ".xml");
+
+ var materialData = new MaterialData
+ {
+ Type = (MaterialType)MaterialTypes.IndexOf(SelectedMaterialType),
+ ColorMap = _texturePath,
+ NormalMap = NormalMapPath,
+ SpecularMap = SpecularMapPath,
+ EmissiveMap = EmissiveMapPath,
+ AmbientOcclusionMap = AmbientOcclusionMapPath,
+ RoughnessMap = RoughnessMapPath
+ };
+
+ switch (materialData.Type)
+ {
+ case MaterialType.Default:
+ materialData.Parameters0 = new Vector4(
+ NormalMapStrength,
+ SpecularIntensity,
+ 0.0f,
+ 0.0f);
+ break;
+ }
+
+ try
+ {
+ if (File.Exists(externalMaterialDataPath))
+ File.Delete(externalMaterialDataPath);
+
+ MaterialData.SaveToXml(externalMaterialDataPath, materialData);
+ MaterialFileName = externalMaterialDataPath;
+ }
+ catch (Exception)
+ {
+ _messageService.ShowError($"An error occurred while saving XML material file to '{externalMaterialDataPath}'.");
+ }
+ }
+
+ #region Commands
+
+ [RelayCommand]
+ private void BrowseNormalMap()
+ => BrowseTexture(SetNormalMapPath, SetNormalMapPreview, SetNormalMapBackgroundBrush,
+ value => HasNormalMap = value);
+
+ [RelayCommand]
+ private void ClearNormalMap()
+ => ClearTexture(SetNormalMapPath, SetNormalMapPreview, SetNormalMapBackgroundBrush,
+ value => HasNormalMap = value);
+
+ [RelayCommand]
+ private void BrowseSpecularMap()
+ => BrowseTexture(SetSpecularMapPath, SetSpecularMapPreview, SetSpecularMapBackgroundBrush,
+ value => HasSpecularMap = value);
+
+ [RelayCommand]
+ private void ClearSpecularMap()
+ => ClearTexture(SetSpecularMapPath, SetSpecularMapPreview, SetSpecularMapBackgroundBrush,
+ value => HasSpecularMap = value);
+
+ [RelayCommand]
+ private void BrowseAmbientOcclusionMap()
+ => BrowseTexture(SetAmbientOcclusionMapPath, SetAmbientOcclusionMapPreview, SetAmbientOcclusionMapBackgroundBrush,
+ value => HasAmbientOcclusionMap = value);
+
+ [RelayCommand]
+ private void ClearAmbientOcclusionMap()
+ => ClearTexture(SetAmbientOcclusionMapPath, SetAmbientOcclusionMapPreview, SetAmbientOcclusionMapBackgroundBrush,
+ value => HasAmbientOcclusionMap = value);
+
+ [RelayCommand]
+ private void BrowseEmissiveMap()
+ => BrowseTexture(SetEmissiveMapPath, SetEmissiveMapPreview, SetEmissiveMapBackgroundBrush,
+ value => HasEmissiveMap = value);
+
+ [RelayCommand]
+ private void ClearEmissiveMap()
+ => ClearTexture(SetEmissiveMapPath, SetEmissiveMapPreview, SetEmissiveMapBackgroundBrush,
+ value => HasEmissiveMap = value);
+
+ [RelayCommand]
+ private void BrowseRoughnessMap()
+ => BrowseTexture(SetRoughnessMapPath, SetRoughnessMapPreview, SetRoughnessMapBackgroundBrush,
+ value => HasRoughnessMap = value);
+
+ [RelayCommand]
+ private void ClearRoughnessMap()
+ => ClearTexture(SetRoughnessMapPath, SetRoughnessMapPreview, SetRoughnessMapBackgroundBrush,
+ value => HasRoughnessMap = value);
+
+ [RelayCommand]
+ private void Confirm()
+ {
+ SaveMaterialProperties();
+
+ DialogResult = true;
+ _dialogService.Close(this);
+ }
+
+ [RelayCommand]
+ private void Cancel()
+ {
+ DialogResult = false;
+ _dialogService.Close(this);
+ }
+
+ #endregion Commands
+}
diff --git a/TombLib/TombLib.Forms/Views/MaterialEditorWindow.xaml b/TombLib/TombLib.Forms/Views/MaterialEditorWindow.xaml
new file mode 100644
index 000000000..245558fc3
--- /dev/null
+++ b/TombLib/TombLib.Forms/Views/MaterialEditorWindow.xaml
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TombLib/TombLib.Forms/Views/MaterialEditorWindow.xaml.cs b/TombLib/TombLib.Forms/Views/MaterialEditorWindow.xaml.cs
new file mode 100644
index 000000000..1e73caf54
--- /dev/null
+++ b/TombLib/TombLib.Forms/Views/MaterialEditorWindow.xaml.cs
@@ -0,0 +1,9 @@
+using System.Windows;
+
+namespace TombLib.Forms.Views;
+
+public partial class MaterialEditorWindow : Window
+{
+ public MaterialEditorWindow()
+ => InitializeComponent();
+}
\ No newline at end of file
diff --git a/TombLib/TombLib.WPF/Resources/Localization/EN/TombLib.json b/TombLib/TombLib.WPF/Resources/Localization/EN/TombLib.json
index 736bdd0ec..7924328c3 100644
--- a/TombLib/TombLib.WPF/Resources/Localization/EN/TombLib.json
+++ b/TombLib/TombLib.WPF/Resources/Localization/EN/TombLib.json
@@ -44,5 +44,21 @@
"InputBoxWindow": {
"EnterValue": "Please enter a value:",
"InvalidNameMessage": "Invalid name, please choose another one."
+ },
+
+ "MaterialEditorWindow": {
+ "Title": "Material Editor",
+ "TextureSelection": "Texture Selection",
+ "Texture": "Texture:",
+ "ColorMap": "Color Map",
+ "NormalMap": "Normal Map",
+ "SpecularMap": "Specular Map",
+ "AmbientOcclusionMap": "Ambient Occlusion Map",
+ "EmissiveMap": "Emissive Map",
+ "RoughnessMap": "Roughness Map",
+ "Browse": "Browse",
+ "Clear": "Clear",
+ "MaterialParameters": "Material Parameters",
+ "MaterialType": "Material Type:"
}
}