Files
FlaxEngine/Source/Editor/Windows/Assets/CollisionDataWindow.cs

340 lines
12 KiB
C#

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Xml;
using FlaxEditor.Content;
using FlaxEditor.Content.Create;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Windows.Assets
{
/// <summary>
/// Editor window to view/modify <see cref="CollisionData"/> asset.
/// </summary>
/// <seealso cref="CollisionData" />
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed class CollisionDataWindow : AssetEditorWindowBase<CollisionData>
{
[Flags]
private enum MaterialSlotsMask : uint
{
// @formatter:off
Slot0=1u<<0,Slot1=1u<<1,Slot2=1u<<2,Slot3=1u<<3,Slot4=1u<<4,Slot5=1u<<5,Slot6=1u<<6,Slot7=1u<<7,Slot8=1u<<8,Slot9=1u<<9,Slot10=1u<<10,Slot11=1u<<11,Slot12=1u<<12,Slot13=1u<<13,Slot14=1u<<14,Slot15=1u<<15,Slot16=1u<<16,Slot17=1u<<17,Slot18=1u<<18,Slot19=1u<<19,Slot20=1u<<20,Slot21=1u<<21,Slot22=1u<<22,Slot23=1u<<23,Slot24=1u<<24,Slot25=1u<<25,Slot26=1u<<26,Slot27=1u<<27,Slot28=1u<<28,Slot29=1u<<29,Slot30=1u<<30,Slot31=1u<<31,
// @formatter:on
All = uint.MaxValue,
}
/// <summary>
/// The asset properties proxy object.
/// </summary>
[CustomEditor(typeof(Editor))]
private sealed class PropertiesProxy
{
private CollisionDataWindow Window;
private CollisionData Asset;
private bool _isCooking;
[EditorOrder(0), EditorDisplay("General"), Tooltip("Type of the collision data to use")]
public CollisionDataType Type;
[EditorOrder(10), EditorDisplay("General"), Tooltip("Source model asset to use for collision data generation")]
public ModelBase Model;
[EditorOrder(20), Limit(0, 5), EditorDisplay("General", "Model LOD Index"), Tooltip("Source model LOD index to use for collision data generation (will be clamped to the actual model LODs collection size)")]
public int ModelLodIndex;
[EditorOrder(30), EditorDisplay("General"), Tooltip("The source model material slots mask. One bit per-slot. Can be used to exclude particular material slots from collision cooking.")]
public MaterialSlotsMask MaterialSlotsMask = MaterialSlotsMask.All;
[EditorOrder(100), EditorDisplay("Convex Mesh", "Convex Flags"), Tooltip("Convex mesh generation flags")]
public ConvexMeshGenerationFlags ConvexFlags;
[EditorOrder(110), Limit(8, 255), EditorDisplay("Convex Mesh", "Vertex Limit"), Tooltip("Convex mesh vertex count limit")]
public int ConvexVertexLimit;
public class Editor : GenericEditor
{
private ButtonElement _cookButton;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
layout.Space(10);
_cookButton = layout.Button("Cook");
_cookButton.Button.Clicked += OnCookButtonClicked;
}
/// <inheritdoc />
public override void Refresh()
{
if (_cookButton != null && Values.Count == 1)
{
var p = (PropertiesProxy)Values[0];
if (p._isCooking)
{
_cookButton.Button.Enabled = false;
_cookButton.Button.Text = "Cooking...";
}
else
{
_cookButton.Button.Enabled = p.Type != CollisionDataType.None && p.Model != null;
_cookButton.Button.Text = "Cook";
}
}
base.Refresh();
}
private void OnCookButtonClicked()
{
((PropertiesProxy)Values[0]).Cook();
}
}
private class CookData : CreateFileEntry
{
public PropertiesProxy Proxy;
public CollisionDataType Type;
public ModelBase Model;
public int ModelLodIndex;
public uint MaterialSlotsMask;
public ConvexMeshGenerationFlags ConvexFlags;
public int ConvexVertexLimit;
public CookData(string resultUrl)
: base("Collision Data", resultUrl)
{
}
/// <inheritdoc />
public override bool Create()
{
bool failed = FlaxEditor.Editor.CookMeshCollision(ResultUrl, Type, Model, ModelLodIndex, MaterialSlotsMask, ConvexFlags, ConvexVertexLimit);
Proxy._isCooking = false;
Proxy.Window.UpdateWiresModel();
return failed;
}
}
public void Cook()
{
_isCooking = true;
Window.Editor.ContentImporting.Create(new CookData(Asset.Path)
{
Proxy = this,
Type = Type,
Model = Model,
ModelLodIndex = ModelLodIndex,
MaterialSlotsMask = (uint)MaterialSlotsMask,
ConvexFlags = ConvexFlags,
ConvexVertexLimit = ConvexVertexLimit,
});
}
public void OnLoad(CollisionDataWindow window)
{
// Link
Window = window;
Asset = window.Asset;
// Setup cooking parameters
var options = Asset.Options;
Type = options.Type;
if (Type == CollisionDataType.None)
Type = CollisionDataType.ConvexMesh;
Model = FlaxEngine.Content.LoadAsync<ModelBase>(options.Model);
ModelLodIndex = options.ModelLodIndex;
ConvexFlags = options.ConvexFlags;
ConvexVertexLimit = options.ConvexVertexLimit;
}
public void OnClean()
{
// Unlink
Window = null;
Asset = null;
Model = null;
}
}
private readonly SplitPanel _split;
private readonly ModelBasePreview _preview;
private readonly CustomEditorPresenter _propertiesPresenter;
private readonly PropertiesProxy _properties;
private Model _collisionWiresModel;
private StaticModel _collisionWiresShowActor;
private bool _updateWireMesh;
/// <inheritdoc />
public CollisionDataWindow(Editor editor, AssetItem item)
: base(editor, item)
{
// Toolstrip
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more");
// Split Panel
_split = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0, 0, _toolstrip.Bottom, 0),
SplitterValue = 0.7f,
Parent = this
};
// Model preview
_preview = new ModelBasePreview(true)
{
ViewportCamera = new FPSCamera(),
Parent = _split.Panel1
};
_preview.Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom;
// Asset properties
_propertiesPresenter = new CustomEditorPresenter(null);
_propertiesPresenter.Panel.Parent = _split.Panel2;
_properties = new PropertiesProxy();
_propertiesPresenter.Select(_properties);
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
// Sync helper actor size with actual preview model (preview scales model for better usage experience)
if (_collisionWiresShowActor && _collisionWiresShowActor.IsActive)
{
_collisionWiresShowActor.Transform = _preview.StaticModel.Transform;
}
base.Update(deltaTime);
}
/// <summary>
/// Updates the collision data debug model.
/// </summary>
private void UpdateWiresModel()
{
// Don't update on a importer/worker thread
if (Platform.CurrentThreadID != Globals.MainThreadID)
{
_updateWireMesh = true;
return;
}
if (_collisionWiresModel == null)
{
_collisionWiresModel = FlaxEngine.Content.CreateVirtualAsset<Model>();
_collisionWiresModel.SetupLODs(new[] { 1 });
}
Editor.Internal_GetCollisionWires(FlaxEngine.Object.GetUnmanagedPtr(Asset), out var triangles, out var indices, out var _, out var _);
if (triangles != null && indices != null)
_collisionWiresModel.LODs[0].Meshes[0].UpdateMesh(triangles, indices);
else
Editor.LogWarning("Failed to get collision wires for " + Asset);
if (_collisionWiresShowActor == null)
{
_collisionWiresShowActor = new StaticModel();
_preview.Task.AddCustomActor(_collisionWiresShowActor);
}
_collisionWiresShowActor.Model = _collisionWiresModel;
_collisionWiresShowActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial));
_preview.Asset = FlaxEngine.Content.LoadAsync<ModelBase>(_asset.Options.Model);
}
/// <inheritdoc />
protected override void UnlinkItem()
{
_properties.OnClean();
_preview.Asset = null;
base.UnlinkItem();
}
/// <inheritdoc />
protected override void OnAssetLinked()
{
_preview.Asset = null;
base.OnAssetLinked();
}
/// <inheritdoc />
protected override void OnAssetLoaded()
{
_properties.OnLoad(this);
_propertiesPresenter.BuildLayout();
ClearEditedFlag();
UpdateWiresModel();
base.OnAssetLoaded();
}
/// <inheritdoc />
public override void OnItemReimported(ContentItem item)
{
// Refresh the properties (will get new data in OnAssetLoaded)
_properties.OnClean();
_propertiesPresenter.BuildLayout();
ClearEditedFlag();
base.OnItemReimported(item);
}
/// <inheritdoc />
public override void OnUpdate()
{
if (_updateWireMesh)
{
_updateWireMesh = false;
UpdateWiresModel();
}
base.OnUpdate();
}
/// <inheritdoc />
public override void OnDestroy()
{
if (IsDisposing)
return;
base.OnDestroy();
Object.Destroy(ref _collisionWiresShowActor);
Object.Destroy(ref _collisionWiresModel);
}
/// <inheritdoc />
public override bool UseLayoutData => true;
/// <inheritdoc />
public override void OnLayoutSerialize(XmlWriter writer)
{
LayoutSerializeSplitter(writer, "Split", _split);
}
/// <inheritdoc />
public override void OnLayoutDeserialize(XmlElement node)
{
LayoutDeserializeSplitter(node, "Split", _split);
}
/// <inheritdoc />
public override void OnLayoutDeserialize()
{
_split.SplitterValue = 0.7f;
}
}
}