// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System.Xml; using FlaxEditor.Content; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; #pragma warning disable 1591 namespace FlaxEditor.Windows.Assets { /// /// Editor window to view/modify asset. /// /// /// public abstract class ModelBaseWindow : AssetEditorWindowBase where TAsset : ModelBase where TWindow : ModelBaseWindow { protected abstract class PropertiesProxyBase { [HideInEditor] public TWindow Window; [HideInEditor] public TAsset Asset; public virtual void OnLoad(TWindow window) { Window = window; Asset = window.Asset; } public virtual void OnSave() { } public virtual void OnClean() { Window = null; Asset = null; } } protected abstract class ProxyEditorBase : GenericEditor { internal override void RefreshInternal() { // Skip updates when model is not loaded var proxy = (PropertiesProxyBase)Values[0]; if (proxy.Asset == null || !proxy.Asset.IsLoaded) return; base.RefreshInternal(); } } protected class Tab : GUI.Tabs.Tab { public CustomEditorPresenter Presenter; public PropertiesProxyBase Proxy; public Tab(string text, TWindow window, bool modifiesAsset = true) : base(text) { var scrollPanel = new Panel(ScrollBars.Vertical) { AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, Parent = this }; Presenter = new CustomEditorPresenter(null); Presenter.Panel.Parent = scrollPanel; if (modifiesAsset) Presenter.Modified += window.MarkAsEdited; } /// public override void OnDestroy() { Presenter.Deselect(); Presenter = null; Proxy = null; base.OnDestroy(); } } protected sealed class UVsLayoutPreviewControl : RenderToTextureControl { private int _channel = -1; private int _lod, _mesh = -1; private int _highlightIndex = -1; private int _isolateIndex = -1; public ModelBaseWindow Window; public UVsLayoutPreviewControl() { Offsets = new Margin(4); AutomaticInvalidate = false; } public int Channel { set { if (_channel == value) return; _channel = value; Visible = _channel != -1; if (Visible) Invalidate(); } } public int LOD { set { if (_lod != value) { _lod = value; Invalidate(); } } } public int Mesh { set { if (_mesh != value) { _mesh = value; Invalidate(); } } } public int HighlightIndex { set { if (_highlightIndex != value) { _highlightIndex = value; Invalidate(); } } } public int IsolateIndex { set { if (_isolateIndex != value) { _isolateIndex = value; Invalidate(); } } } private void DrawMeshUVs(int meshIndex, ref MeshDataCache.MeshData meshData) { var uvScale = Size; if (meshData.IndexBuffer == null || meshData.VertexAccessor == null) return; var linesColor = _highlightIndex != -1 && _highlightIndex == meshIndex ? Style.Current.BackgroundSelected : Color.White; var texCoordStream = meshData.VertexAccessor.TexCoord(_channel); if (!texCoordStream.IsValid) return; for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) { // Cache triangle indices uint i0 = meshData.IndexBuffer[i + 0]; uint i1 = meshData.IndexBuffer[i + 1]; uint i2 = meshData.IndexBuffer[i + 2]; // Cache triangle uvs positions and transform positions to output target Float2 uv0 = texCoordStream.GetFloat2((int)i0) * uvScale; Float2 uv1 = texCoordStream.GetFloat2((int)i1) * uvScale; Float2 uv2 = texCoordStream.GetFloat2((int)i2) * uvScale; // Don't draw too small triangles float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2); if (area > 10.0f) { // Draw triangle Render2D.DrawLine(uv0, uv1, linesColor); Render2D.DrawLine(uv1, uv2, linesColor); Render2D.DrawLine(uv2, uv0, linesColor); } } } /// public override void DrawSelf() { base.DrawSelf(); var size = Size; if (_channel < 0 || size.MaxValue < 5.0f) return; if (Window._meshData == null) Window._meshData = new MeshDataCache(); if (!Window._meshData.RequestMeshData(Window._asset)) { Invalidate(); Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); return; } Render2D.PushClip(new Rectangle(Float2.Zero, size)); var meshDatas = Window._meshData.MeshDatas; var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1); var lod = meshDatas[lodIndex]; var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1); if (mesh == -1) { for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++) { if (_isolateIndex != -1 && _isolateIndex != meshIndex) continue; DrawMeshUVs(meshIndex, ref lod[meshIndex]); } } else { DrawMeshUVs(mesh, ref lod[mesh]); } Render2D.PopClip(); } protected override void OnSizeChanged() { Height = Width; base.OnSizeChanged(); } protected override void OnVisibleChanged() { base.OnVisibleChanged(); Parent.PerformLayout(); Height = Width; } } protected readonly SplitPanel _split; protected readonly Tabs _tabs; protected readonly ToolStripButton _saveButton; protected bool _refreshOnLODsLoaded; protected bool _skipEffectsGuiEvents; protected int _isolateIndex = -1; protected int _highlightIndex = -1; protected MeshDataCache _meshData; /// protected ModelBaseWindow(Editor editor, AssetItem item) : base(editor, item) { var inputOptions = Editor.Options.Options.Input; // Toolstrip _saveButton = _toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save", ref inputOptions.Save); // Split Panel _split = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.None) { AnchorPreset = AnchorPresets.StretchAll, Offsets = new Margin(0, 0, _toolstrip.Bottom, 0), SplitterValue = 0.59f, Parent = this }; // Properties tabs _tabs = new Tabs { AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, TabsSize = new Float2(60, 20), TabsTextHorizontalAlignment = TextAlignment.Center, UseScroll = true, Parent = _split.Panel2 }; } /// protected override void UpdateToolstrip() { _saveButton.Enabled = IsEdited; base.UpdateToolstrip(); } /// protected override void UnlinkItem() { _meshData?.WaitForMeshDataRequestEnd(); foreach (var child in _tabs.Children) { if (child is Tab tab && tab.Proxy.Window != null) tab.Proxy.OnClean(); } base.UnlinkItem(); } /// protected override void OnAssetLoaded() { foreach (var child in _tabs.Children) { if (child is Tab tab) { tab.Proxy.OnLoad((TWindow)this); tab.Presenter.BuildLayout(); } } ClearEditedFlag(); base.OnAssetLoaded(); } /// public override void OnItemReimported(ContentItem item) { // Discard any old mesh data cache _meshData?.Dispose(); // Refresh the properties (will get new data in OnAssetLoaded) foreach (var child in _tabs.Children) { if (child is Tab tab) { tab.Proxy.OnClean(); tab.Presenter.BuildLayout(); } } ClearEditedFlag(); _refreshOnLODsLoaded = true; base.OnItemReimported(item); } /// public override void OnDestroy() { // Free mesh memory _meshData?.Dispose(); _meshData = null; base.OnDestroy(); } /// public override bool UseLayoutData => true; /// public override void OnLayoutSerialize(XmlWriter writer) { LayoutSerializeSplitter(writer, "Split", _split); } /// public override void OnLayoutDeserialize(XmlElement node) { LayoutDeserializeSplitter(node, "Split", _split); } /// public override void OnLayoutDeserialize() { _split.SplitterValue = 0.59f; } } }