Merge model and skinned model windows code into shared base class
This commit is contained in:
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user