Merge model and skinned model windows code into shared base class

This commit is contained in:
Wojtek Figat
2025-01-12 01:04:56 +01:00
parent 1b97e49ed9
commit 506efb7538
10 changed files with 701 additions and 1016 deletions

View File

@@ -39,14 +39,9 @@ CustomEditorsUtilService CustomEditorsUtilServiceInstance;
struct Entry
{
MClass* DefaultEditor;
MClass* CustomEditor;
Entry()
{
DefaultEditor = nullptr;
CustomEditor = nullptr;
}
MClass* DefaultEditor = nullptr;
MClass* CustomEditor = nullptr;
MType* CustomEditorType = nullptr;
};
Dictionary<MType*, Entry> Cache(512);
@@ -63,11 +58,11 @@ MTypeObject* CustomEditorsUtil::GetCustomEditor(MTypeObject* refType)
Entry result;
if (Cache.TryGet(type, result))
{
if (result.CustomEditorType)
return INTERNAL_TYPE_GET_OBJECT(result.CustomEditorType);
MClass* editor = result.CustomEditor ? result.CustomEditor : result.DefaultEditor;
if (editor)
{
return MUtils::GetType(editor);
}
}
return nullptr;
}
@@ -157,7 +152,7 @@ void OnAssemblyLoaded(MAssembly* assembly)
else if (typeClass)
{
auto& entry = Cache[mclass->GetType()];
entry.CustomEditor = typeClass;
entry.CustomEditorType = type;
//LOG(Info, "Custom Editor {0} for type {1}", String(typeClass->GetFullName()), String(mclass->GetFullName()));
}

View File

@@ -1,11 +1,15 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Tabs;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -93,6 +97,306 @@ namespace FlaxEditor.Windows.Assets
}
}
protected class MeshesPropertiesProxyBase : PropertiesProxyBase
{
private readonly List<ComboBox> _materialSlotComboBoxes = new List<ComboBox>();
private readonly List<CheckBox> _isolateCheckBoxes = new List<CheckBox>();
private readonly List<CheckBox> _highlightCheckBoxes = new List<CheckBox>();
public override void OnLoad(TWindow window)
{
base.OnLoad(window);
Window._isolateIndex = -1;
Window._highlightIndex = -1;
}
public override void OnClean()
{
Window._isolateIndex = -1;
Window._highlightIndex = -1;
base.OnClean();
}
/// <summary>
/// Updates the highlight/isolate effects on UI.
/// </summary>
public void UpdateEffectsOnUI()
{
Window._skipEffectsGuiEvents = true;
for (int i = 0; i < _isolateCheckBoxes.Count; i++)
{
var checkBox = _isolateCheckBoxes[i];
checkBox.Checked = Window._isolateIndex == ((MeshBase)checkBox.Tag).MaterialSlotIndex;
}
for (int i = 0; i < _highlightCheckBoxes.Count; i++)
{
var checkBox = _highlightCheckBoxes[i];
checkBox.Checked = Window._highlightIndex == ((MeshBase)checkBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
/// <summary>
/// Updates the material slots UI parts. Should be called after material slot rename.
/// </summary>
public void UpdateMaterialSlotsUI()
{
Window._skipEffectsGuiEvents = true;
// Generate material slots labels (with index prefix)
var slots = Asset.MaterialSlots;
var slotsLabels = new string[slots.Length];
for (int i = 0; i < slots.Length; i++)
{
slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name);
}
// Update comboboxes
for (int i = 0; i < _materialSlotComboBoxes.Count; i++)
{
var comboBox = _materialSlotComboBoxes[i];
comboBox.SetItems(slotsLabels);
comboBox.SelectedIndex = ((MeshBase)comboBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
/// <summary>
/// Sets the material slot index to the mesh.
/// </summary>
/// <param name="mesh">The mesh.</param>
/// <param name="newSlotIndex">New index of the material slot to use.</param>
public void SetMaterialSlot(MeshBase mesh, int newSlotIndex)
{
if (Window._skipEffectsGuiEvents)
return;
mesh.MaterialSlotIndex = newSlotIndex == -1 ? 0 : newSlotIndex;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
Window.MarkAsEdited();
}
/// <summary>
/// Sets the material slot to isolate.
/// </summary>
/// <param name="mesh">The mesh.</param>
public void SetIsolate(MeshBase mesh)
{
if (Window._skipEffectsGuiEvents)
return;
Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
}
/// <summary>
/// Sets the material slot index to highlight.
/// </summary>
/// <param name="mesh">The mesh.</param>
public void SetHighlight(MeshBase mesh)
{
if (Window._skipEffectsGuiEvents)
return;
Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
}
protected virtual void OnGeneral(LayoutElementsContainer layout)
{
}
protected class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (MeshesPropertiesProxyBase)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
proxy._materialSlotComboBoxes.Clear();
proxy._isolateCheckBoxes.Clear();
proxy._highlightCheckBoxes.Clear();
var countLODs = proxy.Asset.LODsCount;
var loadedLODs = proxy.Asset.LoadedLODs;
// General properties
{
var group = layout.Group("General");
var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature.");
minScreenSize.ValueBox.MinValue = 0.0f;
minScreenSize.ValueBox.MaxValue = 1.0f;
minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize;
minScreenSize.ValueBox.ValueChanged += () =>
{
proxy.Asset.MinScreenSize = minScreenSize.ValueBox.Value;
proxy.Window.MarkAsEdited();
};
}
proxy.OnGeneral(layout);
// Group per LOD
for (int lodIndex = 0; lodIndex < countLODs; lodIndex++)
{
var group = layout.Group("LOD " + lodIndex);
if (lodIndex < countLODs - loadedLODs)
{
group.Label("Loading LOD...");
continue;
}
var lod = proxy.Asset.GetLOD(lodIndex);
proxy.Asset.GetMeshes(out var meshes, lodIndex);
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
{
var mesh = meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu();
group.Label("Size: " + lod.Box.Size).AddCopyContextMenu();
var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.");
screenSize.ValueBox.MinValue = 0.0f;
screenSize.ValueBox.MaxValue = 10.0f;
screenSize.ValueBox.Value = lod.ScreenSize;
screenSize.ValueBox.ValueChanged += () =>
{
lod.ScreenSize = screenSize.ValueBox.Value;
proxy.Window.MarkAsEdited();
};
// Every mesh properties
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
{
var mesh = meshes[meshIndex];
group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, vert: {mesh.VertexCount:N0})").AddCopyContextMenu();
// Material Slot
var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering");
materialSlot.ComboBox.Tag = mesh;
materialSlot.ComboBox.SelectedIndexChanged += comboBox => proxy.SetMaterialSlot((MeshBase)comboBox.Tag, comboBox.SelectedIndex);
proxy._materialSlotComboBoxes.Add(materialSlot.ComboBox);
// Isolate
var isolate = group.Checkbox("Isolate", "Shows only this mesh (and meshes using the same material slot)");
isolate.CheckBox.Tag = mesh;
isolate.CheckBox.StateChanged += (box) => proxy.SetIsolate(box.Checked ? (MeshBase)box.Tag : null);
proxy._isolateCheckBoxes.Add(isolate.CheckBox);
// Highlight
var highlight = group.Checkbox("Highlight", "Highlights this mesh with a tint color (and meshes using the same material slot)");
highlight.CheckBox.Tag = mesh;
highlight.CheckBox.StateChanged += (box) => proxy.SetHighlight(box.Checked ? (MeshBase)box.Tag : null);
proxy._highlightCheckBoxes.Add(highlight.CheckBox);
}
}
// Refresh UI
proxy.UpdateMaterialSlotsUI();
}
}
}
protected class MaterialsPropertiesProxyBase : PropertiesProxyBase
{
[Collection(CanReorderItems = true, NotNullItems = true, OverrideEditorTypeName = "FlaxEditor.CustomEditors.Editors.GenericEditor", Spacing = 10)]
[EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)]
public MaterialSlot[] MaterialSlots
{
get => Asset != null ? Asset.MaterialSlots : null;
set
{
if (Asset != null)
{
if (Asset.MaterialSlots.Length != value.Length)
{
MaterialBase[] materials = new MaterialBase[value.Length];
string[] names = new string[value.Length];
ShadowsCastingMode[] shadowsModes = new ShadowsCastingMode[value.Length];
for (int i = 0; i < value.Length; i++)
{
if (value[i] != null)
{
materials[i] = value[i].Material;
names[i] = value[i].Name;
shadowsModes[i] = value[i].ShadowsMode;
}
else
{
materials[i] = null;
names[i] = "Material " + i;
shadowsModes[i] = ShadowsCastingMode.All;
}
}
Asset.SetupMaterialSlots(value.Length);
var slots = Asset.MaterialSlots;
for (int i = 0; i < slots.Length; i++)
{
slots[i].Material = materials[i];
slots[i].Name = names[i];
slots[i].ShadowsMode = shadowsModes[i];
}
UpdateMaterialSlotsUI();
}
}
}
}
private readonly List<ComboBox> _materialSlotComboBoxes = new List<ComboBox>();
/// <summary>
/// Updates the material slots UI parts. Should be called after material slot rename.
/// </summary>
public void UpdateMaterialSlotsUI()
{
Window._skipEffectsGuiEvents = true;
// Generate material slots labels (with index prefix)
var slots = Asset.MaterialSlots;
var slotsLabels = new string[slots.Length];
for (int i = 0; i < slots.Length; i++)
{
slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name);
}
// Update comboboxes
for (int i = 0; i < _materialSlotComboBoxes.Count; i++)
{
var comboBox = _materialSlotComboBoxes[i];
comboBox.SetItems(slotsLabels);
comboBox.SelectedIndex = ((Mesh)comboBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
protected class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (MaterialsPropertiesProxyBase)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
base.Initialize(layout);
}
}
}
protected class UVsPropertiesProxyBase : PropertiesProxyBase
{
public enum UVChannel
@@ -375,10 +679,52 @@ namespace FlaxEditor.Windows.Assets
}
}
protected class ImportPropertiesProxyBase : PropertiesProxyBase
{
private ModelImportSettings ImportSettings;
/// <inheritdoc />
public override void OnLoad(TWindow window)
{
base.OnLoad(window);
ImportSettings = window._importSettings;
}
public void Reimport()
{
Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)Window.Item, ImportSettings, true);
}
protected class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (ImportPropertiesProxyBase)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
// Import Settings
{
var group = layout.Group("Import Settings");
var importSettingsField = typeof(ImportPropertiesProxyBase).GetField(nameof(ImportSettings), BindingFlags.NonPublic | BindingFlags.Instance);
var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings };
group.Object(importSettingsValues);
layout.Space(5);
var reimportButton = group.Button("Reimport");
reimportButton.Button.Clicked += () => ((ImportPropertiesProxyBase)Values[0]).Reimport();
}
}
}
}
protected readonly SplitPanel _split;
protected readonly Tabs _tabs;
protected readonly ToolStripButton _saveButton;
protected ModelImportSettings _importSettings = new ModelImportSettings();
protected bool _refreshOnLODsLoaded;
protected bool _skipEffectsGuiEvents;
protected int _isolateIndex = -1;
@@ -416,6 +762,11 @@ namespace FlaxEditor.Windows.Assets
};
}
/// <summary>
/// Updates the highlight/isolate effects on a model asset.
/// </summary>
protected abstract void UpdateEffectsOnAsset();
/// <inheritdoc />
protected override void UpdateToolstrip()
{
@@ -430,8 +781,8 @@ namespace FlaxEditor.Windows.Assets
_meshData?.WaitForMeshDataRequestEnd();
foreach (var child in _tabs.Children)
{
if (child is Tab tab && tab.Proxy.Window != null)
tab.Proxy.OnClean();
if (child is Tab tab && tab.Proxy?.Window != null)
tab.Proxy?.OnClean();
}
base.UnlinkItem();
@@ -440,11 +791,14 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
protected override void OnAssetLoaded()
{
_refreshOnLODsLoaded = true;
Editor.TryRestoreImportOptions(ref _importSettings.Settings, Item.Path);
UpdateEffectsOnAsset();
foreach (var child in _tabs.Children)
{
if (child is Tab tab)
{
tab.Proxy.OnLoad((TWindow)this);
tab.Proxy?.OnLoad((TWindow)this);
tab.Presenter.BuildLayout();
}
}

View File

@@ -1,14 +1,11 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
@@ -55,386 +52,113 @@ namespace FlaxEditor.Windows.Assets
}
}
[CustomEditor(typeof(ProxyEditor))]
private sealed class MeshesPropertiesProxy : PropertiesProxyBase
[CustomEditor(typeof(MeshesPropertiesProxy.ProxyEditor))]
private sealed class MeshesPropertiesProxy : MeshesPropertiesProxyBase
{
private readonly List<ComboBox> _materialSlotComboBoxes = new List<ComboBox>();
private readonly List<CheckBox> _isolateCheckBoxes = new List<CheckBox>();
private readonly List<CheckBox> _highlightCheckBoxes = new List<CheckBox>();
private CustomEditors.Elements.IntegerValueElement _sdfModelLodIndex;
private CustomEditorPresenter _presenter;
public override void OnLoad(ModelWindow window)
protected override void OnGeneral(LayoutElementsContainer layout)
{
base.OnLoad(window);
base.OnGeneral(layout);
Window._isolateIndex = -1;
Window._highlightIndex = -1;
}
_presenter = layout.Presenter;
public override void OnClean()
{
Window._isolateIndex = -1;
Window._highlightIndex = -1;
base.OnClean();
}
private void UpdateEffectsOnUI()
{
Window._skipEffectsGuiEvents = true;
for (int i = 0; i < _isolateCheckBoxes.Count; i++)
// SDF
{
var checkBox = _isolateCheckBoxes[i];
checkBox.Checked = Window._isolateIndex == ((Mesh)checkBox.Tag).MaterialSlotIndex;
}
var group = layout.Group("SDF");
var sdfOptions = Window._sdfOptions;
for (int i = 0; i < _highlightCheckBoxes.Count; i++)
{
var checkBox = _highlightCheckBoxes[i];
checkBox.Checked = Window._highlightIndex == ((Mesh)checkBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
private void UpdateMaterialSlotsUI()
{
Window._skipEffectsGuiEvents = true;
// Generate material slots labels (with index prefix)
var slots = Asset.MaterialSlots;
var slotsLabels = new string[slots.Length];
for (int i = 0; i < slots.Length; i++)
{
slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name);
}
// Update comboboxes
for (int i = 0; i < _materialSlotComboBoxes.Count; i++)
{
var comboBox = _materialSlotComboBoxes[i];
comboBox.SetItems(slotsLabels);
comboBox.SelectedIndex = ((Mesh)comboBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
private void SetMaterialSlot(Mesh mesh, int newSlotIndex)
{
if (Window._skipEffectsGuiEvents)
return;
mesh.MaterialSlotIndex = newSlotIndex == -1 ? 0 : newSlotIndex;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
Window.MarkAsEdited();
}
private void SetIsolate(Mesh mesh)
{
if (Window._skipEffectsGuiEvents)
return;
Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
}
private void SetHighlight(Mesh mesh)
{
if (Window._skipEffectsGuiEvents)
return;
Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
}
private class ProxyEditor : ProxyEditorBase
{
private CustomEditors.Elements.IntegerValueElement _sdfModelLodIndex;
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (MeshesPropertiesProxy)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
proxy._materialSlotComboBoxes.Clear();
proxy._isolateCheckBoxes.Clear();
proxy._highlightCheckBoxes.Clear();
var lods = proxy.Asset.LODs;
var loadedLODs = proxy.Asset.LoadedLODs;
// General properties
var sdf = Asset.SDF;
if (sdf.Texture != null)
{
var group = layout.Group("General");
var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature.");
minScreenSize.ValueBox.MinValue = 0.0f;
minScreenSize.ValueBox.MaxValue = 1.0f;
minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize;
minScreenSize.ValueBox.BoxValueChanged += b =>
{
proxy.Asset.MinScreenSize = b.Value;
proxy.Window.MarkAsEdited();
};
var size = sdf.Texture.Size3;
group.Label($"SDF Texture {size.X}x{size.Y}x{size.Z} ({Utilities.Utils.FormatBytesCount(sdf.Texture.MemoryUsage)})").AddCopyContextMenu();
}
else
{
group.Label("No SDF");
}
// SDF
var resolution = group.FloatValue("Resolution Scale", Window.Editor.CodeDocs.GetTooltip(typeof(ModelTool.Options), nameof(ModelImportSettings.Settings.SDFResolution)));
resolution.ValueBox.MinValue = 0.0001f;
resolution.ValueBox.MaxValue = 100.0f;
resolution.ValueBox.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f;
resolution.ValueBox.BoxValueChanged += b => { Window._importSettings.Settings.SDFResolution = b.Value; };
Window._importSettings.Settings.SDFResolution = sdf.ResolutionScale;
var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage).");
gpu.CheckBox.Checked = sdfOptions.GPU;
var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
var backfacesThreshold = backfacesThresholdProp.FloatValue();
var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last();
backfacesThreshold.ValueBox.MinValue = 0.001f;
backfacesThreshold.ValueBox.MaxValue = 1.0f;
backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold;
backfacesThreshold.ValueBox.BoxValueChanged += b => { Window._sdfOptions.BackfacesThreshold = b.Value; };
// Toggle Backfaces Threshold visibility (CPU-only option)
gpu.CheckBox.StateChanged += c =>
{
var group = layout.Group("SDF");
var sdfOptions = proxy.Window._sdfOptions;
Window._sdfOptions.GPU = c.Checked;
backfacesThresholdLabel.Visible = !c.Checked;
backfacesThreshold.ValueBox.Visible = !c.Checked;
};
backfacesThresholdLabel.Visible = !gpu.CheckBox.Checked;
backfacesThreshold.ValueBox.Visible = !gpu.CheckBox.Checked;
var sdf = proxy.Asset.SDF;
if (sdf.Texture != null)
{
var size = sdf.Texture.Size3;
group.Label($"SDF Texture {size.X}x{size.Y}x{size.Z} ({Utilities.Utils.FormatBytesCount(sdf.Texture.MemoryUsage)})").AddCopyContextMenu();
}
else
{
group.Label("No SDF");
}
var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building.");
lodIndex.IntValue.MinValue = 0;
lodIndex.IntValue.MaxValue = Asset.LODsCount - 1;
lodIndex.IntValue.Value = sdf.Texture != null ? sdf.LOD : 6;
_sdfModelLodIndex = lodIndex;
var resolution = group.FloatValue("Resolution Scale", proxy.Window.Editor.CodeDocs.GetTooltip(typeof(ModelTool.Options), nameof(ModelImportSettings.Settings.SDFResolution)));
resolution.ValueBox.MinValue = 0.0001f;
resolution.ValueBox.MaxValue = 100.0f;
resolution.ValueBox.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f;
resolution.ValueBox.BoxValueChanged += b => { proxy.Window._importSettings.Settings.SDFResolution = b.Value; };
proxy.Window._importSettings.Settings.SDFResolution = sdf.ResolutionScale;
var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage).");
gpu.CheckBox.Checked = sdfOptions.GPU;
var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
var backfacesThreshold = backfacesThresholdProp.FloatValue();
var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last();
backfacesThreshold.ValueBox.MinValue = 0.001f;
backfacesThreshold.ValueBox.MaxValue = 1.0f;
backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold;
backfacesThreshold.ValueBox.BoxValueChanged += b => { proxy.Window._sdfOptions.BackfacesThreshold = b.Value; };
// Toggle Backfaces Threshold visibility (CPU-only option)
gpu.CheckBox.StateChanged += c =>
{
proxy.Window._sdfOptions.GPU = c.Checked;
backfacesThresholdLabel.Visible = !c.Checked;
backfacesThreshold.ValueBox.Visible = !c.Checked;
};
backfacesThresholdLabel.Visible = !gpu.CheckBox.Checked;
backfacesThreshold.ValueBox.Visible = !gpu.CheckBox.Checked;
var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building.");
lodIndex.IntValue.MinValue = 0;
lodIndex.IntValue.MaxValue = lods.Length - 1;
lodIndex.IntValue.Value = sdf.Texture != null ? sdf.LOD : 6;
_sdfModelLodIndex = lodIndex;
var buttons = group.CustomContainer<UniformGridPanel>();
var gridControl = buttons.CustomControl;
gridControl.ClipChildren = false;
gridControl.Height = Button.DefaultHeight;
gridControl.SlotsHorizontally = 2;
gridControl.SlotsVertically = 1;
var rebuildButton = buttons.Button("Rebuild", "Rebuilds the model SDF.").Button;
rebuildButton.Clicked += OnRebuildSDF;
var removeButton = buttons.Button("Remove", "Removes the model SDF data from the asset.").Button;
removeButton.Enabled = sdf.Texture != null;
removeButton.Clicked += OnRemoveSDF;
}
// Group per LOD
for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++)
{
var group = layout.Group("LOD " + lodIndex);
if (lodIndex < lods.Length - loadedLODs)
{
group.Label("Loading LOD...");
continue;
}
var lod = lods[lodIndex];
var meshes = lod.Meshes;
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
{
var mesh = meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu();
group.Label("Size: " + lod.Box.Size).AddCopyContextMenu();
var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.");
screenSize.ValueBox.MinValue = 0.0f;
screenSize.ValueBox.MaxValue = 10.0f;
screenSize.ValueBox.Value = lod.ScreenSize;
screenSize.ValueBox.BoxValueChanged += b =>
{
lod.ScreenSize = b.Value;
proxy.Window.MarkAsEdited();
};
// Every mesh properties
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
{
var mesh = meshes[meshIndex];
group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu();
// Material Slot
var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering");
materialSlot.ComboBox.Tag = mesh;
materialSlot.ComboBox.SelectedIndexChanged += comboBox => proxy.SetMaterialSlot((Mesh)comboBox.Tag, comboBox.SelectedIndex);
proxy._materialSlotComboBoxes.Add(materialSlot.ComboBox);
// Isolate
var isolate = group.Checkbox("Isolate", "Shows only this mesh (and meshes using the same material slot)");
isolate.CheckBox.Tag = mesh;
isolate.CheckBox.StateChanged += (box) => proxy.SetIsolate(box.Checked ? (Mesh)box.Tag : null);
proxy._isolateCheckBoxes.Add(isolate.CheckBox);
// Highlight
var highlight = group.Checkbox("Highlight", "Highlights this mesh with a tint color (and meshes using the same material slot)");
highlight.CheckBox.Tag = mesh;
highlight.CheckBox.StateChanged += (box) => proxy.SetHighlight(box.Checked ? (Mesh)box.Tag : null);
proxy._highlightCheckBoxes.Add(highlight.CheckBox);
}
}
// Refresh UI
proxy.UpdateMaterialSlotsUI();
var buttons = group.CustomContainer<UniformGridPanel>();
var gridControl = buttons.CustomControl;
gridControl.ClipChildren = false;
gridControl.Height = Button.DefaultHeight;
gridControl.SlotsHorizontally = 2;
gridControl.SlotsVertically = 1;
var rebuildButton = buttons.Button("Rebuild", "Rebuilds the model SDF.").Button;
rebuildButton.Clicked += OnRebuildSDF;
var removeButton = buttons.Button("Remove", "Removes the model SDF data from the asset.").Button;
removeButton.Enabled = sdf.Texture != null;
removeButton.Clicked += OnRemoveSDF;
}
}
private void OnRebuildSDF()
private void OnRebuildSDF()
{
Window.Enabled = false;
Task.Run(() =>
{
var proxy = (MeshesPropertiesProxy)Values[0];
proxy.Window.Enabled = false;
Task.Run(() =>
var sdfOptions = Window._sdfOptions;
bool failed = Asset.GenerateSDF(Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, sdfOptions.BackfacesThreshold, sdfOptions.GPU);
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
var sdfOptions = proxy.Window._sdfOptions;
bool failed = proxy.Asset.GenerateSDF(proxy.Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, sdfOptions.BackfacesThreshold, sdfOptions.GPU);
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
proxy.Window.Enabled = true;
if (!failed)
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
Window.Enabled = true;
if (!failed)
Window.MarkAsEdited();
_presenter.BuildLayoutOnUpdate();
// Save some SDF options locally in the project cache
proxy.Window.Editor.ProjectCache.SetCustomData(JsonSerializer.GetStringID(proxy.Window.Item.ID) + ".SDF", JsonSerializer.Serialize(sdfOptions));
});
// Save some SDF options locally in the project cache
Window.Editor.ProjectCache.SetCustomData(JsonSerializer.GetStringID(Window.Item.ID) + ".SDF", JsonSerializer.Serialize(sdfOptions));
});
}
});
}
private void OnRemoveSDF()
{
var proxy = (MeshesPropertiesProxy)Values[0];
proxy.Asset.SetSDF(new ModelBase.SDFData());
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
}
internal override void RefreshInternal()
{
// Skip updates when model is not loaded
var proxy = (MeshesPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
return;
base.RefreshInternal();
}
private void OnRemoveSDF()
{
Asset.SetSDF(new ModelBase.SDFData());
Window.MarkAsEdited();
_presenter.BuildLayoutOnUpdate();
}
}
[CustomEditor(typeof(ProxyEditor))]
private sealed class MaterialsPropertiesProxy : PropertiesProxyBase
private sealed class MaterialsPropertiesProxy : MaterialsPropertiesProxyBase
{
[Collection(CanReorderItems = true, NotNullItems = true, OverrideEditorTypeName = "FlaxEditor.CustomEditors.Editors.GenericEditor", Spacing = 10)]
[EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)]
public MaterialSlot[] MaterialSlots
{
get => Asset != null ? Asset.MaterialSlots : null;
set
{
if (Asset != null)
{
if (Asset.MaterialSlots.Length != value.Length)
{
MaterialBase[] materials = new MaterialBase[value.Length];
string[] names = new string[value.Length];
ShadowsCastingMode[] shadowsModes = new ShadowsCastingMode[value.Length];
for (int i = 0; i < value.Length; i++)
{
if (value[i] != null)
{
materials[i] = value[i].Material;
names[i] = value[i].Name;
shadowsModes[i] = value[i].ShadowsMode;
}
else
{
materials[i] = null;
names[i] = "Material " + i;
shadowsModes[i] = ShadowsCastingMode.All;
}
}
Asset.SetupMaterialSlots(value.Length);
var slots = Asset.MaterialSlots;
for (int i = 0; i < slots.Length; i++)
{
slots[i].Material = materials[i];
slots[i].Name = names[i];
slots[i].ShadowsMode = shadowsModes[i];
}
UpdateMaterialSlotsUI();
}
}
}
}
private readonly List<ComboBox> _materialSlotComboBoxes = new List<ComboBox>();
/// <summary>
/// Updates the material slots UI parts. Should be called after material slot rename.
/// </summary>
public void UpdateMaterialSlotsUI()
{
Window._skipEffectsGuiEvents = true;
// Generate material slots labels (with index prefix)
var slots = Asset.MaterialSlots;
var slotsLabels = new string[slots.Length];
for (int i = 0; i < slots.Length; i++)
{
slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name);
}
// Update comboboxes
for (int i = 0; i < _materialSlotComboBoxes.Count; i++)
{
var comboBox = _materialSlotComboBoxes[i];
comboBox.SetItems(slotsLabels);
comboBox.SelectedIndex = ((Mesh)comboBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
private class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (MaterialsPropertiesProxy)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
base.Initialize(layout);
}
}
}
[CustomEditor(typeof(ProxyEditor))]
@@ -443,45 +167,8 @@ namespace FlaxEditor.Windows.Assets
}
[CustomEditor(typeof(ProxyEditor))]
private sealed class ImportPropertiesProxy : PropertiesProxyBase
private sealed class ImportPropertiesProxy : ImportPropertiesProxyBase
{
private ModelImportSettings ImportSettings;
/// <inheritdoc />
public override void OnLoad(ModelWindow window)
{
base.OnLoad(window);
ImportSettings = window._importSettings;
}
public void Reimport()
{
Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)Window.Item, ImportSettings, true);
}
private class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (ImportPropertiesProxy)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
// Import Settings
{
var group = layout.Group("Import Settings");
var importSettingsField = typeof(ImportPropertiesProxy).GetField(nameof(ImportSettings), BindingFlags.NonPublic | BindingFlags.Instance);
var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings };
group.Object(importSettingsValues);
layout.Space(5);
var reimportButton = group.Button("Reimport");
reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport();
}
}
}
}
private class MeshesTab : Tab
@@ -532,7 +219,6 @@ namespace FlaxEditor.Windows.Assets
private readonly ModelPreview _preview;
private StaticModel _highlightActor;
private ModelImportSettings _importSettings = new ModelImportSettings();
private ModelSdfOptions _sdfOptions;
private ToolStripButton _showCurrentLODButton;
@@ -579,10 +265,8 @@ namespace FlaxEditor.Windows.Assets
_preview.Task.AddCustomActor(_highlightActor);
}
/// <summary>
/// Updates the highlight/isolate effects on a model asset.
/// </summary>
private void UpdateEffectsOnAsset()
/// <inheritdoc />
protected override void UpdateEffectsOnAsset()
{
var entries = _preview.PreviewActor.Entries;
if (entries != null)
@@ -688,12 +372,7 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
protected override void OnAssetLoaded()
{
_refreshOnLODsLoaded = true;
_preview.ViewportCamera.SetArcBallView(Asset.GetBox());
Editor.TryRestoreImportOptions(ref _importSettings.Settings, Item.Path);
UpdateEffectsOnAsset();
// TODO: disable streaming for this model
base.OnAssetLoaded();
}

View File

@@ -4,9 +4,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
@@ -54,220 +52,8 @@ namespace FlaxEditor.Windows.Assets
}
[CustomEditor(typeof(ProxyEditor))]
private sealed class MeshesPropertiesProxy : PropertiesProxyBase
private sealed class MeshesPropertiesProxy : MeshesPropertiesProxyBase
{
private readonly List<ComboBox> _materialSlotComboBoxes = new List<ComboBox>();
private readonly List<CheckBox> _isolateCheckBoxes = new List<CheckBox>();
private readonly List<CheckBox> _highlightCheckBoxes = new List<CheckBox>();
public override void OnLoad(SkinnedModelWindow window)
{
base.OnLoad(window);
Window._isolateIndex = -1;
Window._highlightIndex = -1;
}
public override void OnClean()
{
Window._isolateIndex = -1;
Window._highlightIndex = -1;
base.OnClean();
}
/// <summary>
/// Updates the highlight/isolate effects on UI.
/// </summary>
public void UpdateEffectsOnUI()
{
Window._skipEffectsGuiEvents = true;
for (int i = 0; i < _isolateCheckBoxes.Count; i++)
{
var checkBox = _isolateCheckBoxes[i];
checkBox.Checked = Window._isolateIndex == ((SkinnedMesh)checkBox.Tag).MaterialSlotIndex;
}
for (int i = 0; i < _highlightCheckBoxes.Count; i++)
{
var checkBox = _highlightCheckBoxes[i];
checkBox.Checked = Window._highlightIndex == ((SkinnedMesh)checkBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
/// <summary>
/// Updates the material slots UI parts. Should be called after material slot rename.
/// </summary>
public void UpdateMaterialSlotsUI()
{
Window._skipEffectsGuiEvents = true;
// Generate material slots labels (with index prefix)
var slots = Asset.MaterialSlots;
var slotsLabels = new string[slots.Length];
for (int i = 0; i < slots.Length; i++)
{
slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name);
}
// Update comboboxes
for (int i = 0; i < _materialSlotComboBoxes.Count; i++)
{
var comboBox = _materialSlotComboBoxes[i];
comboBox.SetItems(slotsLabels);
comboBox.SelectedIndex = ((SkinnedMesh)comboBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
/// <summary>
/// Sets the material slot index to the mesh.
/// </summary>
/// <param name="mesh">The mesh.</param>
/// <param name="newSlotIndex">New index of the material slot to use.</param>
public void SetMaterialSlot(SkinnedMesh mesh, int newSlotIndex)
{
if (Window._skipEffectsGuiEvents)
return;
mesh.MaterialSlotIndex = newSlotIndex == -1 ? 0 : newSlotIndex;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
Window.MarkAsEdited();
}
/// <summary>
/// Sets the material slot to isolate.
/// </summary>
/// <param name="mesh">The mesh.</param>
public void SetIsolate(SkinnedMesh mesh)
{
if (Window._skipEffectsGuiEvents)
return;
Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
}
/// <summary>
/// Sets the material slot index to highlight.
/// </summary>
/// <param name="mesh">The mesh.</param>
public void SetHighlight(SkinnedMesh mesh)
{
if (Window._skipEffectsGuiEvents)
return;
Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
}
private class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (MeshesPropertiesProxy)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
proxy._materialSlotComboBoxes.Clear();
proxy._isolateCheckBoxes.Clear();
proxy._highlightCheckBoxes.Clear();
var lods = proxy.Asset.LODs;
var loadedLODs = proxy.Asset.LoadedLODs;
// General properties
{
var group = layout.Group("General");
var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature.");
minScreenSize.ValueBox.MinValue = 0.0f;
minScreenSize.ValueBox.MaxValue = 1.0f;
minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize;
minScreenSize.ValueBox.ValueChanged += () =>
{
proxy.Asset.MinScreenSize = minScreenSize.ValueBox.Value;
proxy.Window.MarkAsEdited();
};
}
// Group per LOD
for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++)
{
var group = layout.Group("LOD " + lodIndex);
if (lodIndex < lods.Length - loadedLODs)
{
group.Label("Loading LOD...");
continue;
}
var lod = lods[lodIndex];
var meshes = lod.Meshes;
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
{
var mesh = meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu();
group.Label("Size: " + lod.Box.Size);
var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.");
screenSize.ValueBox.MinValue = 0.0f;
screenSize.ValueBox.MaxValue = 10.0f;
screenSize.ValueBox.Value = lod.ScreenSize;
screenSize.ValueBox.ValueChanged += () =>
{
lod.ScreenSize = screenSize.ValueBox.Value;
proxy.Window.MarkAsEdited();
};
// Every mesh properties
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
{
var mesh = meshes[meshIndex];
group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu();
// Material Slot
var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering");
materialSlot.ComboBox.Tag = mesh;
materialSlot.ComboBox.SelectedIndexChanged += comboBox => proxy.SetMaterialSlot((SkinnedMesh)comboBox.Tag, comboBox.SelectedIndex);
proxy._materialSlotComboBoxes.Add(materialSlot.ComboBox);
// Isolate
var isolate = group.Checkbox("Isolate", "Shows only this mesh (and meshes using the same material slot)");
isolate.CheckBox.Tag = mesh;
isolate.CheckBox.StateChanged += (box) => proxy.SetIsolate(box.Checked ? (SkinnedMesh)box.Tag : null);
proxy._isolateCheckBoxes.Add(isolate.CheckBox);
// Highlight
var highlight = group.Checkbox("Highlight", "Highlights this mesh with a tint color (and meshes using the same material slot)");
highlight.CheckBox.Tag = mesh;
highlight.CheckBox.StateChanged += (box) => proxy.SetHighlight(box.Checked ? (SkinnedMesh)box.Tag : null);
proxy._highlightCheckBoxes.Add(highlight.CheckBox);
}
}
// Refresh UI
proxy.UpdateMaterialSlotsUI();
}
internal override void RefreshInternal()
{
// Skip updates when model is not loaded
var proxy = (MeshesPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
return;
base.RefreshInternal();
}
}
}
[CustomEditor(typeof(ProxyEditor))]
@@ -393,107 +179,12 @@ namespace FlaxEditor.Windows.Assets
}
}
}
internal override void RefreshInternal()
{
// Skip updates when model is not loaded
var proxy = (MeshesPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
return;
base.RefreshInternal();
}
}
}
[CustomEditor(typeof(ProxyEditor))]
private sealed class MaterialsPropertiesProxy : PropertiesProxyBase
private sealed class MaterialsPropertiesProxy : MaterialsPropertiesProxyBase
{
[Collection(CanReorderItems = true, NotNullItems = true, OverrideEditorTypeName = "FlaxEditor.CustomEditors.Editors.GenericEditor", Spacing = 10)]
[EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)]
public MaterialSlot[] MaterialSlots
{
get => Asset != null ? Asset.MaterialSlots : null;
set
{
if (Asset != null)
{
if (Asset.MaterialSlots.Length != value.Length)
{
MaterialBase[] materials = new MaterialBase[value.Length];
string[] names = new string[value.Length];
ShadowsCastingMode[] shadowsModes = new ShadowsCastingMode[value.Length];
for (int i = 0; i < value.Length; i++)
{
if (value[i] != null)
{
materials[i] = value[i].Material;
names[i] = value[i].Name;
shadowsModes[i] = value[i].ShadowsMode;
}
else
{
materials[i] = null;
names[i] = "Material " + i;
shadowsModes[i] = ShadowsCastingMode.All;
}
}
Asset.SetupMaterialSlots(value.Length);
var slots = Asset.MaterialSlots;
for (int i = 0; i < slots.Length; i++)
{
slots[i].Material = materials[i];
slots[i].Name = names[i];
slots[i].ShadowsMode = shadowsModes[i];
}
UpdateMaterialSlotsUI();
}
}
}
}
private readonly List<ComboBox> _materialSlotComboBoxes = new List<ComboBox>();
/// <summary>
/// Updates the material slots UI parts. Should be called after material slot rename.
/// </summary>
public void UpdateMaterialSlotsUI()
{
Window._skipEffectsGuiEvents = true;
// Generate material slots labels (with index prefix)
var slots = Asset.MaterialSlots;
var slotsLabels = new string[slots.Length];
for (int i = 0; i < slots.Length; i++)
{
slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name);
}
// Update comboboxes
for (int i = 0; i < _materialSlotComboBoxes.Count; i++)
{
var comboBox = _materialSlotComboBoxes[i];
comboBox.SetItems(slotsLabels);
comboBox.SelectedIndex = ((SkinnedMesh)comboBox.Tag).MaterialSlotIndex;
}
Window._skipEffectsGuiEvents = false;
}
private class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (MaterialsPropertiesProxy)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
base.Initialize(layout);
}
}
}
[CustomEditor(typeof(ProxyEditor))]
@@ -788,45 +479,8 @@ namespace FlaxEditor.Windows.Assets
}
[CustomEditor(typeof(ProxyEditor))]
private sealed class ImportPropertiesProxy : PropertiesProxyBase
private sealed class ImportPropertiesProxy : ImportPropertiesProxyBase
{
private ModelImportSettings ImportSettings = new ModelImportSettings();
/// <inheritdoc />
public override void OnLoad(SkinnedModelWindow window)
{
base.OnLoad(window);
Editor.TryRestoreImportOptions(ref ImportSettings.Settings, window.Item.Path);
}
public void Reimport()
{
Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)Window.Item, ImportSettings, true);
}
private class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (ImportPropertiesProxy)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
// Import Settings
{
var group = layout.Group("Import Settings");
var importSettingsField = typeof(ImportPropertiesProxy).GetField("ImportSettings", BindingFlags.NonPublic | BindingFlags.Instance);
var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings };
group.Object(importSettingsValues);
layout.Space(5);
var reimportButton = group.Button("Reimport");
reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport();
}
}
}
}
private class MeshesTab : Tab
@@ -960,10 +614,8 @@ namespace FlaxEditor.Windows.Assets
_preview.Task.AddCustomActor(_highlightActor);
}
/// <summary>
/// Updates the highlight/isolate effects on a model asset.
/// </summary>
private void UpdateEffectsOnAsset()
/// <inheritdoc />
protected override void UpdateEffectsOnAsset()
{
var entries = _preview.PreviewActor.Entries;
if (entries != null)
@@ -1071,11 +723,7 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
protected override void OnAssetLoaded()
{
_refreshOnLODsLoaded = true;
_preview.ViewportCamera.SetArcBallView(_preview.GetBounds());
UpdateEffectsOnAsset();
// TODO: disable streaming for this model
// Reset any root motion
_preview.PreviewActor.ResetLocalTransform();

View File

@@ -7,7 +7,6 @@
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Content/Upgraders/ModelAssetUpgrader.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
@@ -529,6 +528,18 @@ int32 Model::GetLODsCount() const
return LODs.Count();
}
const ModelLODBase* Model::GetLOD(int32 lodIndex) const
{
CHECK_RETURN(LODs.IsValidIndex(lodIndex), nullptr);
return &LODs.Get()[lodIndex];
}
ModelLODBase* Model::GetLOD(int32 lodIndex)
{
CHECK_RETURN(LODs.IsValidIndex(lodIndex), nullptr);
return &LODs.Get()[lodIndex];
}
const MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex) const
{
auto& lod = LODs[lodIndex];
@@ -543,18 +554,12 @@ MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex)
void Model::GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex) const
{
auto& lod = LODs[lodIndex];
meshes.Resize(lod.Meshes.Count());
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
meshes[meshIndex] = &lod.Meshes[meshIndex];
LODs[lodIndex].GetMeshes(meshes);
}
void Model::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
{
auto& lod = LODs[lodIndex];
meshes.Resize(lod.Meshes.Count());
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
meshes[meshIndex] = &lod.Meshes[meshIndex];
LODs[lodIndex].GetMeshes(meshes);
}
void Model::InitAsVirtual()
@@ -672,12 +677,6 @@ AssetChunksFlag Model::getChunksToPreload() const
return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15);
}
bool ModelLOD::HasAnyMeshInitialized() const
{
// Note: we initialize all meshes at once so the last one can be used to check it.
return Meshes.HasItems() && Meshes.Last().IsInitialized();
}
bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh)
{
bool result = false;
@@ -722,57 +721,31 @@ bool ModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& dist
return result;
}
BoundingBox ModelLOD::GetBox(const Matrix& world) const
int32 ModelLOD::GetMeshesCount() const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
return Meshes.Count();
}
BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
const MeshBase* ModelLOD::GetMesh(int32 index) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
BoundingBox box = mesh.GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
return Meshes.Get() + index;
}
BoundingBox ModelLOD::GetBox() const
MeshBase* ModelLOD::GetMesh(int32 index)
{
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
Meshes[meshIndex].GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
min = Vector3::Min(min, corners[i]);
max = Vector3::Max(max, corners[i]);
}
}
return BoundingBox(min, max);
return Meshes.Get() + index;
}
void ModelLOD::GetMeshes(Array<MeshBase*>& meshes)
{
meshes.Resize(Meshes.Count());
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
meshes[meshIndex] = &Meshes.Get()[meshIndex];
}
void ModelLOD::GetMeshes(Array<const MeshBase*>& meshes) const
{
meshes.Resize(Meshes.Count());
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
meshes[meshIndex] = &Meshes.Get()[meshIndex];
}

View File

@@ -8,16 +8,15 @@
/// <summary>
/// Represents single Level Of Detail for the model. Contains a collection of the meshes.
/// </summary>
API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject
API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ModelLODBase
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, ScriptingObject);
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, ModelLODBase);
friend Model;
friend Mesh;
private:
Model* _model = nullptr;
int32 _lodIndex = 0;
uint32 _verticesCount = 0;
Model* _model = nullptr;
void Link(Model* model, int32 lodIndex)
{
@@ -27,30 +26,11 @@ private:
}
public:
/// <summary>
/// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.
/// </summary>
API_FIELD() float ScreenSize = 1.0f;
/// <summary>
/// The meshes array.
/// </summary>
API_FIELD(ReadOnly) Array<Mesh> Meshes;
/// <summary>
/// Determines whether any mesh has been initialized.
/// </summary>
/// <returns>True if any mesh has been initialized, otherwise false.</returns>
bool HasAnyMeshInitialized() const;
/// <summary>
/// Gets the model LOD index.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const
{
return _lodIndex;
}
/// <summary>
/// Gets the vertex count for this model LOD level.
/// </summary>
@@ -82,26 +62,6 @@ public:
/// <returns>True whether the two objects intersected</returns>
bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh);
/// <summary>
/// Get model bounding box in transformed world matrix.
/// </summary>
/// <param name="world">World matrix</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Matrix& world) const;
/// <summary>
/// Get model bounding box in transformed world.
/// </summary>
/// <param name="transform">The instance transformation.</param>
/// <param name="deformation">The meshes deformation container (optional).</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const;
/// <summary>
/// Gets the bounding box combined for all meshes in this model LOD.
/// </summary>
API_PROPERTY() BoundingBox GetBox() const;
/// <summary>
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
/// </summary>
@@ -152,6 +112,14 @@ public:
for (int32 i = 0; i < Meshes.Count(); i++)
Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor);
}
public:
// [ModelLODBase]
int32 GetMeshesCount() const override;
const MeshBase* GetMesh(int32 index) const override;
MeshBase* GetMesh(int32 index) override;
void GetMeshes(Array<MeshBase*>& meshes) override;
void GetMeshes(Array<const MeshBase*>& meshes) const override;
};
/// <summary>
@@ -312,6 +280,8 @@ public:
// [ModelBase]
void SetupMaterialSlots(int32 slotsCount) override;
int32 GetLODsCount() const override;
const ModelLODBase* GetLOD(int32 lodIndex) const override;
ModelLODBase* GetLOD(int32 lodIndex) override;
const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override;
MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override;
void GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex = 0) const override;

View File

@@ -7,6 +7,7 @@
#include "Engine/Graphics/Config.h"
#include "Engine/Graphics/GPUBuffer.h"
#include "Engine/Graphics/Models/MeshBase.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
#include "Engine/Threading/ThreadPoolTask.h"
@@ -102,6 +103,69 @@ public:
}
};
bool ModelLODBase::HasAnyMeshInitialized() const
{
// Note: we initialize all meshes at once so the last one can be used to check it.
const int32 meshCount = GetMeshesCount();
return meshCount != 0 && GetMesh(meshCount - 1)->IsInitialized();
}
BoundingBox ModelLODBase::GetBox() const
{
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
const int32 meshCount = GetMeshesCount();
for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++)
{
GetMesh(meshIndex)->GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
min = Vector3::Min(min, corners[i]);
max = Vector3::Max(max, corners[i]);
}
}
return BoundingBox(min, max);
}
BoundingBox ModelLODBase::GetBox(const Matrix& world) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
const int32 meshCount = GetMeshesCount();
for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++)
{
GetMesh(meshIndex)->GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox ModelLODBase::GetBox(const Transform& transform, const MeshDeformation* deformation) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
const int32 meshCount = GetMeshesCount();
for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++)
{
BoundingBox box = GetMesh(meshIndex)->GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
ModelBase::~ModelBase()
{
ASSERT(_streamingTask == nullptr);

View File

@@ -22,6 +22,88 @@ class MeshBase;
class StreamModelLODTask;
struct RenderContextBatch;
/// <summary>
/// Base class for mesh LOD objects. Contains a collection of the meshes.
/// </summary>
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelLODBase : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE_MINIMAL(ModelLODBase);
protected:
int32 _lodIndex = 0;
explicit ModelLODBase(const SpawnParams& params)
: ScriptingObject(params)
{
}
public:
/// <summary>
/// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.
/// </summary>
API_FIELD() float ScreenSize = 1.0f;
/// <summary>
/// Gets the model LOD index.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const
{
return _lodIndex;
}
/// <summary>
/// Determines whether any mesh has been initialized.
/// </summary>
bool HasAnyMeshInitialized() const;
public:
/// <summary>
/// Gets the bounding box combined for all meshes in this model LOD.
/// </summary>
API_PROPERTY() BoundingBox GetBox() const;
/// <summary>
/// Get model bounding box in transformed world matrix.
/// </summary>
/// <param name="world">World matrix</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Matrix& world) const;
/// <summary>
/// Get model bounding box in transformed world.
/// </summary>
/// <param name="transform">The instance transformation.</param>
/// <param name="deformation">The meshes deformation container (optional).</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Transform& transform, const class MeshDeformation* deformation = nullptr) const;
public:
/// <summary>
/// Gets the amount of meshes in this LOD.
/// </summary>
virtual int32 GetMeshesCount() const = 0;
/// <summary>
/// Gets the specific mesh in this LOD.
/// </summary>
virtual const MeshBase* GetMesh(int32 index) const = 0;
/// <summary>
/// Gets the specific mesh in this LOD.
/// </summary>
API_FUNCTION(Sealed) virtual MeshBase* GetMesh(int32 index) = 0;
/// <summary>
/// Gets the meshes in this LOD.
/// </summary>
virtual void GetMeshes(Array<const MeshBase*>& meshes) const = 0;
/// <summary>
/// Gets the meshes in this LOD.
/// </summary>
API_FUNCTION(Sealed) virtual void GetMeshes(Array<MeshBase*>& meshes) = 0;
};
/// <summary>
/// Base class for asset types that can contain a model resource.
/// </summary>
@@ -180,6 +262,16 @@ public:
/// </summary>
API_PROPERTY(Sealed) virtual int32 GetLODsCount() const = 0;
/// <summary>
/// Gets the mesh for a particular LOD index.
/// </summary>
virtual const ModelLODBase* GetLOD(int32 lodIndex) const = 0;
/// <summary>
/// Gets the mesh for a particular LOD index.
/// </summary>
API_FUNCTION(Sealed) virtual ModelLODBase* GetLOD(int32 lodIndex) = 0;
/// <summary>
/// Gets the mesh for a particular LOD index.
/// </summary>

View File

@@ -880,6 +880,18 @@ int32 SkinnedModel::GetLODsCount() const
return LODs.Count();
}
const ModelLODBase* SkinnedModel::GetLOD(int32 lodIndex) const
{
CHECK_RETURN(LODs.IsValidIndex(lodIndex), nullptr);
return &LODs.Get()[lodIndex];
}
ModelLODBase* SkinnedModel::GetLOD(int32 lodIndex)
{
CHECK_RETURN(LODs.IsValidIndex(lodIndex), nullptr);
return &LODs.Get()[lodIndex];
}
const MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex) const
{
auto& lod = LODs[lodIndex];
@@ -894,18 +906,12 @@ MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex)
void SkinnedModel::GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex) const
{
auto& lod = LODs[lodIndex];
meshes.Resize(lod.Meshes.Count());
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
meshes[meshIndex] = &lod.Meshes[meshIndex];
LODs[lodIndex].GetMeshes(meshes);
}
void SkinnedModel::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
{
auto& lod = LODs[lodIndex];
meshes.Resize(lod.Meshes.Count());
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
meshes[meshIndex] = &lod.Meshes[meshIndex];
LODs[lodIndex].GetMeshes(meshes);
}
void SkinnedModel::InitAsVirtual()
@@ -977,12 +983,6 @@ AssetChunksFlag SkinnedModel::getChunksToPreload() const
return GET_CHUNK_FLAG(0);
}
bool SkinnedModelLOD::HasAnyMeshInitialized() const
{
// Note: we initialize all meshes at once so the last one can be used to check it.
return Meshes.HasItems() && Meshes.Last().IsInitialized();
}
bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh)
{
// Check all meshes
@@ -1033,78 +1033,31 @@ bool SkinnedModelLOD::Intersects(const Ray& ray, const Transform& transform, Rea
return result;
}
BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const
int32 SkinnedModelLOD::GetMeshesCount() const
{
// Find minimum and maximum points of all the meshes
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
{
const auto& mesh = Meshes[j];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
return Meshes.Count();
}
BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
const MeshBase* SkinnedModelLOD::GetMesh(int32 index) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
return Meshes.Get() + index;
}
MeshBase* SkinnedModelLOD::GetMesh(int32 index)
{
return Meshes.Get() + index;
}
void SkinnedModelLOD::GetMeshes(Array<MeshBase*>& meshes)
{
meshes.Resize(Meshes.Count());
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
BoundingBox box = mesh.GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
meshes[meshIndex] = &Meshes.Get()[meshIndex];
}
BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const
void SkinnedModelLOD::GetMeshes(Array<const MeshBase*>& meshes) const
{
// Find minimum and maximum points of the mesh
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
const auto& mesh = Meshes[meshIndex];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox() const
{
// Find minimum and maximum points of the mesh in given world
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
{
Meshes[j].GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
min = Vector3::Min(min, corners[i]);
max = Vector3::Max(max, corners[i]);
}
}
return BoundingBox(min, max);
meshes.Resize(Meshes.Count());
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
meshes[meshIndex] = &Meshes.Get()[meshIndex];
}

View File

@@ -10,38 +10,19 @@
/// <summary>
/// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes.
/// </summary>
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ModelLODBase
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ScriptingObject);
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ModelLODBase);
friend SkinnedModel;
private:
SkinnedModel* _model = nullptr;
int32 _lodIndex = 0;
public:
/// <summary>
/// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.
/// </summary>
API_FIELD() float ScreenSize = 1.0f;
/// <summary>
/// The meshes array.
/// </summary>
API_FIELD(ReadOnly) Array<SkinnedMesh> Meshes;
/// <summary>
/// Gets the model LOD index.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const
{
return _lodIndex;
}
/// <summary>
/// Determines whether any mesh has been initialized.
/// </summary>
bool HasAnyMeshInitialized() const;
public:
/// <summary>
/// Determines if there is an intersection between the Model and a Ray in given world using given instance
@@ -65,34 +46,6 @@ public:
/// <returns>True whether the two objects intersected</returns>
bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh);
/// <summary>
/// Get model bounding box in transformed world for given instance buffer
/// </summary>
/// <param name="world">World matrix</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Matrix& world) const;
/// <summary>
/// Get model bounding box in transformed world.
/// </summary>
/// <param name="transform">The instance transformation.</param>
/// <param name="deformation">The meshes deformation container (optional).</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const;
/// <summary>
/// Get model bounding box in transformed world for given instance buffer for only one mesh
/// </summary>
/// <param name="world">World matrix</param>
/// <param name="meshIndex">esh index</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Matrix& world, int32 meshIndex) const;
/// <summary>
/// Gets the bounding box combined for all meshes in this model LOD.
/// </summary>
API_PROPERTY() BoundingBox GetBox() const;
/// <summary>
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
/// </summary>
@@ -100,9 +53,7 @@ public:
FORCE_INLINE void Render(GPUContext* context)
{
for (int32 i = 0; i < Meshes.Count(); i++)
{
Meshes.Get()[i].Render(context);
}
}
/// <summary>
@@ -114,9 +65,7 @@ public:
FORCE_INLINE void Draw(const RenderContext& renderContext, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const
{
for (int32 i = 0; i < Meshes.Count(); i++)
{
Meshes.Get()[i].Draw(renderContext, info, lodDitherFactor);
}
}
/// <summary>
@@ -128,10 +77,16 @@ public:
FORCE_INLINE void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const
{
for (int32 i = 0; i < Meshes.Count(); i++)
{
Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor);
}
}
public:
// [ModelLODBase]
int32 GetMeshesCount() const override;
const MeshBase* GetMesh(int32 index) const override;
MeshBase* GetMesh(int32 index) override;
void GetMeshes(Array<MeshBase*>& meshes) override;
void GetMeshes(Array<const MeshBase*>& meshes) const override;
};
/// <summary>
@@ -384,6 +339,8 @@ public:
uint64 GetMemoryUsage() const override;
void SetupMaterialSlots(int32 slotsCount) override;
int32 GetLODsCount() const override;
const ModelLODBase* GetLOD(int32 lodIndex) const override;
ModelLODBase* GetLOD(int32 lodIndex) override;
const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override;
MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override;
void GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex = 0) const override;