**Refactor meshes format to support custom vertex layouts and new flexible api to access mesh data**

#3044 #2667
This commit is contained in:
Wojtek Figat
2025-01-06 22:47:19 +01:00
parent 29bfef677f
commit db4d7d2a05
65 changed files with 4428 additions and 3106 deletions

View File

@@ -80,19 +80,23 @@ namespace FlaxEditor.SceneGraph.Actors
// Get vertex data for each mesh
var meshes = model.LODs[0].Meshes;
var meshesData = new SkinnedMesh.Vertex0[meshes.Length][];
var bonesVertices = new List<SkinnedMesh.Vertex0>[bones.Length];
var bonesVertices = new List<Float3>[bones.Length];
var indicesLimit = new Int4(bones.Length - 1);
for (int i = 0; i < meshes.Length; i++)
{
meshesData[i] = meshes[i].DownloadVertexBuffer0();
var meshData = meshes[i].DownloadVertexBuffer0();
for (int j = 0; j < meshData.Length; j++)
var assessor = new MeshAccessor();
if (assessor.LoadMesh(meshes[i]))
return;
var positionStream = assessor.Position();
var blendIndicesStream = assessor.BlendIndices();
var blendWeightsStream = assessor.BlendWeights();
if (!positionStream.IsValid || !blendIndicesStream.IsValid || !blendWeightsStream.IsValid)
continue;
var count = positionStream.Count;
for (int j = 0; j < count; j++)
{
ref var v = ref meshData[j];
var weights = (Float4)v.BlendWeights;
var indices = Int4.Min((Int4)v.BlendIndices, indicesLimit);
var weights = blendWeightsStream.GetFloat4(j);
var indices = Int4.Min((Int4)blendIndicesStream.GetFloat4(j), indicesLimit);
// Find the bone with the highest influence on the vertex
var maxWeightIndex = 0;
@@ -104,17 +108,18 @@ namespace FlaxEditor.SceneGraph.Actors
var maxWeightBone = indices[maxWeightIndex];
// Skin vertex position with the current pose
Float3.Transform(ref v.Position, ref skinningMatrices[indices[0]], out Float3 pos0);
Float3.Transform(ref v.Position, ref skinningMatrices[indices[1]], out Float3 pos1);
Float3.Transform(ref v.Position, ref skinningMatrices[indices[2]], out Float3 pos2);
Float3.Transform(ref v.Position, ref skinningMatrices[indices[3]], out Float3 pos3);
v.Position = pos0 * weights[0] + pos1 * weights[1] + pos2 * weights[2] + pos3 * weights[3];
var position = positionStream.GetFloat3(j);
Float3.Transform(ref position, ref skinningMatrices[indices[0]], out Float3 pos0);
Float3.Transform(ref position, ref skinningMatrices[indices[1]], out Float3 pos1);
Float3.Transform(ref position, ref skinningMatrices[indices[2]], out Float3 pos2);
Float3.Transform(ref position, ref skinningMatrices[indices[3]], out Float3 pos3);
position = pos0 * weights[0] + pos1 * weights[1] + pos2 * weights[2] + pos3 * weights[3];
// Add vertex to the bone list
ref var boneVertices = ref bonesVertices[maxWeightBone];
if (boneVertices == null)
boneVertices = new List<SkinnedMesh.Vertex0>();
boneVertices.Add(v);
boneVertices = new List<Float3>();
boneVertices.Add(position);
}
}
@@ -128,10 +133,10 @@ namespace FlaxEditor.SceneGraph.Actors
continue; // Skip not used bones
// Compute bounds of the vertices using this bone (in local space of the actor)
Float3 boneBoundsMin = boneVertices[0].Position, boneBoundsMax = boneVertices[0].Position;
Float3 boneBoundsMin = boneVertices[0], boneBoundsMax = boneVertices[0];
for (int i = 1; i < boneVertices.Count; i++)
{
var pos = boneVertices[i].Position;
var pos = boneVertices[i];
boneBoundsMin = Float3.Min(boneBoundsMin, pos);
boneBoundsMax = Float3.Max(boneBoundsMax, pos);
}
@@ -165,10 +170,10 @@ namespace FlaxEditor.SceneGraph.Actors
var boneBounds = BoundingBox.Zero;
if (boneVertices != null)
{
boneBounds = new BoundingBox(boneVertices[0].Position, boneVertices[0].Position);
boneBounds = new BoundingBox(boneVertices[0], boneVertices[0]);
for (int i = 1; i < boneVertices.Count; i++)
{
var pos = boneVertices[i].Position;
var pos = boneVertices[i];
boneBounds.Minimum = Float3.Min(boneBounds.Minimum, pos);
boneBounds.Minimum = Float3.Max(boneBounds.Maximum, pos);
}
@@ -263,7 +268,7 @@ namespace FlaxEditor.SceneGraph.Actors
var boneLocalBounds = BoundingBox.Zero;
for (int i = 0; i < boneVertices.Count; i++)
{
var pos = boneTransform.WorldToLocal(boneVertices[i].Position);
var pos = boneTransform.WorldToLocal(boneVertices[i]);
Vector3.Min(ref boneLocalBounds.Minimum, ref pos, out boneLocalBounds.Minimum);
Vector3.Max(ref boneLocalBounds.Maximum, ref pos, out boneLocalBounds.Maximum);
}
@@ -360,20 +365,20 @@ namespace FlaxEditor.SceneGraph.Actors
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
}
private static unsafe Matrix CalculateCovarianceMatrix(List<SkinnedMesh.Vertex0> vertices)
private static unsafe Matrix CalculateCovarianceMatrix(List<Float3> vertices)
{
// [Reference: https://en.wikipedia.org/wiki/Covariance_matrix]
// Calculate average point
var avg = Float3.Zero;
for (int i = 0; i < vertices.Count; i++)
avg += vertices[i].Position;
avg += vertices[i];
avg /= vertices.Count;
// Calculate distance to average for every point
var errors = new Float3[vertices.Count];
for (int i = 0; i < vertices.Count; i++)
errors[i] = vertices[i].Position - avg;
errors[i] = vertices[i] - avg;
var covariance = Matrix.Identity;
var cj = stackalloc float[3];
@@ -393,15 +398,9 @@ namespace FlaxEditor.SceneGraph.Actors
var row = new Float4(cj[0], cj[1], cj[2], 0.0f);
switch (j)
{
case 0:
covariance.Row1 = row;
break;
case 1:
covariance.Row2 = row;
break;
case 2:
covariance.Row3 = row;
break;
case 0: covariance.Row1 = row; break;
case 1: covariance.Row2 = row; break;
case 2: covariance.Row3 = row; break;
}
}
return covariance;

View File

@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
[HideInEditor]
public sealed class StaticModelNode : ActorNode
{
private Dictionary<IntPtr, Mesh.Vertex[]> _vertices;
private Dictionary<IntPtr, Float3[]> _vertices;
/// <inheritdoc />
public StaticModelNode(Actor actor)
@@ -53,14 +53,17 @@ namespace FlaxEditor.SceneGraph.Actors
var key = FlaxEngine.Object.GetUnmanagedPtr(mesh);
if (!_vertices.TryGetValue(key, out var verts))
{
verts = mesh.DownloadVertexBuffer();
var accessor = new MeshAccessor();
if (accessor.LoadMesh(mesh))
continue;
verts = accessor.Positions;
if (verts == null)
continue;
_vertices.Add(key, verts);
}
for (int i = 0; i < verts.Length; i++)
{
var v = verts[i].Position;
ref var v = ref verts[i];
var distance = Float3.DistanceSquared(ref pointLocal, ref v);
if (distance <= minDistance)
{

View File

@@ -414,10 +414,13 @@ namespace FlaxEditor.Tools
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
{
var meshData = lodData[meshIndex];
for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++)
var colors = meshData.VertexAccessor.Colors;
if (colors == null)
continue;
var vertexCount = colors.Length;
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
{
ref var v = ref meshData.VertexBuffer[vertexIndex];
_selectedModel.SetVertexColor(lodIndex, meshIndex, vertexIndex, v.Color);
_selectedModel.SetVertexColor(lodIndex, meshIndex, vertexIndex, colors[vertexIndex]);
}
}
}
@@ -430,10 +433,13 @@ namespace FlaxEditor.Tools
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
{
var meshData = lodData[meshIndex];
for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++)
var positionStream = meshData.VertexAccessor.Position();
if (!positionStream.IsValid)
continue;
var vertexCount = positionStream.Count;
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
{
ref var v = ref meshData.VertexBuffer[vertexIndex];
var pos = instanceTransform.LocalToWorld(v.Position);
var pos = instanceTransform.LocalToWorld(positionStream.GetFloat3(vertexIndex));
var dst = Vector3.Distance(ref pos, ref brushSphere.Center);
if (dst > brushSphere.Radius)
continue;
@@ -590,12 +596,13 @@ namespace FlaxEditor.Tools
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
{
var meshData = lodData[meshIndex];
if (meshData.VertexBuffer == null)
var positionStream = meshData.VertexAccessor.Position();
if (!positionStream.IsValid)
continue;
for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++)
var vertexCount = positionStream.Count;
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
{
ref var v = ref meshData.VertexBuffer[vertexIndex];
var pos = instanceTransform.LocalToWorld(v.Position);
var pos = instanceTransform.LocalToWorld(positionStream.GetFloat3(vertexIndex));
if (brushSphere.Contains(ref pos) == ContainmentType.Disjoint)
continue;
Matrix transform = modelScaleMatrix * Matrix.Translation(pos - viewOrigin);

View File

@@ -296,10 +296,17 @@ namespace FlaxEditor.Viewport.Previews
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
{
var meshData = lod[meshIndex];
for (int i = 0; i < meshData.VertexBuffer.Length; i++)
var positionStream = meshData.VertexAccessor.Position();
var normalStream = meshData.VertexAccessor.Normal();
if (positionStream.IsValid && normalStream.IsValid)
{
ref var v = ref meshData.VertexBuffer[i];
DebugDraw.DrawLine(v.Position, v.Position + v.Normal * 4.0f, Color.Blue);
var count = positionStream.Count;
for (int i = 0; i < count; i++)
{
var position = positionStream.GetFloat3(i);
var normal = normalStream.GetFloat3(i) * 2.0f - 1.0f;
DebugDraw.DrawLine(position, position + normal * 4.0f, Color.Blue);
}
}
}
}
@@ -313,10 +320,17 @@ namespace FlaxEditor.Viewport.Previews
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
{
var meshData = lod[meshIndex];
for (int i = 0; i < meshData.VertexBuffer.Length; i++)
var positionStream = meshData.VertexAccessor.Position();
var tangentStream = meshData.VertexAccessor.Tangent();
if (positionStream.IsValid && tangentStream.IsValid)
{
ref var v = ref meshData.VertexBuffer[i];
DebugDraw.DrawLine(v.Position, v.Position + v.Tangent * 4.0f, Color.Red);
var count = positionStream.Count;
for (int i = 0; i < count; i++)
{
var position = positionStream.GetFloat3(i);
var tangent = tangentStream.GetFloat3(i) * 2.0f - 1.0f;
DebugDraw.DrawLine(position, position + tangent * 4.0f, Color.Red);
}
}
}
}
@@ -330,10 +344,21 @@ namespace FlaxEditor.Viewport.Previews
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
{
var meshData = lod[meshIndex];
for (int i = 0; i < meshData.VertexBuffer.Length; i++)
var positionStream = meshData.VertexAccessor.Position();
var normalStream = meshData.VertexAccessor.Normal();
var tangentStream = meshData.VertexAccessor.Tangent();
if (positionStream.IsValid && normalStream.IsValid && tangentStream.IsValid)
{
ref var v = ref meshData.VertexBuffer[i];
DebugDraw.DrawLine(v.Position, v.Position + v.Bitangent * 4.0f, Color.Green);
var count = positionStream.Count;
for (int i = 0; i < count; i++)
{
var position = positionStream.GetFloat3(i);
var normal = normalStream.GetFloat3(i) * 2.0f - 1.0f;
var tangent = tangentStream.GetFloat4(i);
var bitangentSign = tangent.W > Mathf.Epsilon ? -1.0f : +1.0f;
var bitangent = Float3.Cross(normal, new Float3(tangent) * 2.0f - 1.0f) * bitangentSign;
DebugDraw.DrawLine(position, position + bitangent * 4.0f, Color.Green);
}
}
}
}

View File

@@ -8,6 +8,7 @@ using FlaxEditor.GUI;
using FlaxEditor.GUI.Tabs;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
#pragma warning disable 1591
@@ -92,6 +93,170 @@ namespace FlaxEditor.Windows.Assets
}
}
protected sealed class UVsLayoutPreviewControl : RenderToTextureControl
{
private int _channel = -1;
private int _lod, _mesh = -1;
private int _highlightIndex = -1;
private int _isolateIndex = -1;
public ModelBaseWindow<TAsset, TWindow> Window;
public UVsLayoutPreviewControl()
{
Offsets = new Margin(4);
AutomaticInvalidate = false;
}
public int Channel
{
set
{
if (_channel == value)
return;
_channel = value;
Visible = _channel != -1;
if (Visible)
Invalidate();
}
}
public int LOD
{
set
{
if (_lod != value)
{
_lod = value;
Invalidate();
}
}
}
public int Mesh
{
set
{
if (_mesh != value)
{
_mesh = value;
Invalidate();
}
}
}
public int HighlightIndex
{
set
{
if (_highlightIndex != value)
{
_highlightIndex = value;
Invalidate();
}
}
}
public int IsolateIndex
{
set
{
if (_isolateIndex != value)
{
_isolateIndex = value;
Invalidate();
}
}
}
private void DrawMeshUVs(int meshIndex, ref MeshDataCache.MeshData meshData)
{
var uvScale = Size;
if (meshData.IndexBuffer == null || meshData.VertexAccessor == null)
return;
var linesColor = _highlightIndex != -1 && _highlightIndex == meshIndex ? Style.Current.BackgroundSelected : Color.White;
var texCoordStream = meshData.VertexAccessor.TexCoord(_channel);
if (!texCoordStream.IsValid)
return;
for (int i = 0; i < meshData.IndexBuffer.Length; i += 3)
{
// Cache triangle indices
uint i0 = meshData.IndexBuffer[i + 0];
uint i1 = meshData.IndexBuffer[i + 1];
uint i2 = meshData.IndexBuffer[i + 2];
// Cache triangle uvs positions and transform positions to output target
Float2 uv0 = texCoordStream.GetFloat2((int)i0) * uvScale;
Float2 uv1 = texCoordStream.GetFloat2((int)i1) * uvScale;
Float2 uv2 = texCoordStream.GetFloat2((int)i2) * uvScale;
// Don't draw too small triangles
float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2);
if (area > 10.0f)
{
// Draw triangle
Render2D.DrawLine(uv0, uv1, linesColor);
Render2D.DrawLine(uv1, uv2, linesColor);
Render2D.DrawLine(uv2, uv0, linesColor);
}
}
}
/// <inheritdoc />
public override void DrawSelf()
{
base.DrawSelf();
var size = Size;
if (_channel < 0 || size.MaxValue < 5.0f)
return;
if (Window._meshData == null)
Window._meshData = new MeshDataCache();
if (!Window._meshData.RequestMeshData(Window._asset))
{
Invalidate();
Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center);
return;
}
Render2D.PushClip(new Rectangle(Float2.Zero, size));
var meshDatas = Window._meshData.MeshDatas;
var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1);
var lod = meshDatas[lodIndex];
var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1);
if (mesh == -1)
{
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
{
if (_isolateIndex != -1 && _isolateIndex != meshIndex)
continue;
DrawMeshUVs(meshIndex, ref lod[meshIndex]);
}
}
else
{
DrawMeshUVs(mesh, ref lod[mesh]);
}
Render2D.PopClip();
}
protected override void OnSizeChanged()
{
Height = Width;
base.OnSizeChanged();
}
protected override void OnVisibleChanged()
{
base.OnVisibleChanged();
Parent.PerformLayout();
Height = Width;
}
}
protected readonly SplitPanel _split;
protected readonly Tabs _tabs;
protected readonly ToolStripButton _saveButton;
@@ -101,6 +266,8 @@ namespace FlaxEditor.Windows.Assets
protected int _isolateIndex = -1;
protected int _highlightIndex = -1;
protected MeshDataCache _meshData;
/// <inheritdoc />
protected ModelBaseWindow(Editor editor, AssetItem item)
: base(editor, item)
@@ -142,6 +309,7 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
protected override void UnlinkItem()
{
_meshData?.WaitForMeshDataRequestEnd();
foreach (var child in _tabs.Children)
{
if (child is Tab tab && tab.Proxy.Window != null)
@@ -170,6 +338,9 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
public override void OnItemReimported(ContentItem item)
{
// Discard any old mesh data cache
_meshData?.Dispose();
// Refresh the properties (will get new data in OnAssetLoaded)
foreach (var child in _tabs.Children)
{
@@ -185,6 +356,16 @@ namespace FlaxEditor.Windows.Assets
base.OnItemReimported(item);
}
/// <inheritdoc />
public override void OnDestroy()
{
// Free mesh memory
_meshData?.Dispose();
_meshData = null;
base.OnDestroy();
}
/// <inheritdoc />
public override bool UseLayoutData => true;

View File

@@ -15,7 +15,6 @@ using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using FlaxEngine.Tools;
using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Windows.Assets
@@ -495,7 +494,7 @@ namespace FlaxEditor.Windows.Assets
base.Initialize(layout);
_uvsPreview = layout.Custom<UVsLayoutPreviewControl>().CustomControl;
_uvsPreview.Proxy = proxy;
_uvsPreview.Window = proxy.Window;
}
/// <inheritdoc />
@@ -505,11 +504,17 @@ namespace FlaxEditor.Windows.Assets
if (_uvsPreview != null)
{
_uvsPreview.Channel = _uvsPreview.Proxy._uvChannel;
_uvsPreview.LOD = _uvsPreview.Proxy.LOD;
_uvsPreview.Mesh = _uvsPreview.Proxy.Mesh;
_uvsPreview.HighlightIndex = _uvsPreview.Proxy.Window._highlightIndex;
_uvsPreview.IsolateIndex = _uvsPreview.Proxy.Window._isolateIndex;
var proxy = (UVsPropertiesProxy)Values[0];
switch (proxy._uvChannel)
{
case UVChannel.TexCoord: _uvsPreview.Channel = 0; break;
case UVChannel.LightmapUVs: _uvsPreview.Channel = 1; break;
default: _uvsPreview.Channel = -1; break;
}
_uvsPreview.LOD = proxy.LOD;
_uvsPreview.Mesh = proxy.Mesh;
_uvsPreview.HighlightIndex = proxy.Window._highlightIndex;
_uvsPreview.IsolateIndex = proxy.Window._isolateIndex;
}
}
@@ -520,196 +525,6 @@ namespace FlaxEditor.Windows.Assets
base.Deinitialize();
}
}
private sealed class UVsLayoutPreviewControl : RenderToTextureControl
{
private UVChannel _channel;
private int _lod, _mesh = -1;
private int _highlightIndex = -1;
private int _isolateIndex = -1;
public UVsPropertiesProxy Proxy;
public UVsLayoutPreviewControl()
{
Offsets = new Margin(4);
AutomaticInvalidate = false;
}
public UVChannel Channel
{
set
{
if (_channel == value)
return;
_channel = value;
Visible = _channel != UVChannel.None;
if (Visible)
Invalidate();
}
}
public int LOD
{
set
{
if (_lod != value)
{
_lod = value;
Invalidate();
}
}
}
public int Mesh
{
set
{
if (_mesh != value)
{
_mesh = value;
Invalidate();
}
}
}
public int HighlightIndex
{
set
{
if (_highlightIndex != value)
{
_highlightIndex = value;
Invalidate();
}
}
}
public int IsolateIndex
{
set
{
if (_isolateIndex != value)
{
_isolateIndex = value;
Invalidate();
}
}
}
private void DrawMeshUVs(int meshIndex, MeshDataCache.MeshData meshData)
{
var uvScale = Size;
if (meshData.IndexBuffer == null || meshData.VertexBuffer == null)
return;
var linesColor = _highlightIndex != -1 && _highlightIndex == meshIndex ? Style.Current.BackgroundSelected : Color.White;
switch (_channel)
{
case UVChannel.TexCoord:
for (int i = 0; i < meshData.IndexBuffer.Length; i += 3)
{
// Cache triangle indices
uint i0 = meshData.IndexBuffer[i + 0];
uint i1 = meshData.IndexBuffer[i + 1];
uint i2 = meshData.IndexBuffer[i + 2];
// Cache triangle uvs positions and transform positions to output target
Float2 uv0 = meshData.VertexBuffer[i0].TexCoord * uvScale;
Float2 uv1 = meshData.VertexBuffer[i1].TexCoord * uvScale;
Float2 uv2 = meshData.VertexBuffer[i2].TexCoord * uvScale;
// Don't draw too small triangles
float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2);
if (area > 10.0f)
{
// Draw triangle
Render2D.DrawLine(uv0, uv1, linesColor);
Render2D.DrawLine(uv1, uv2, linesColor);
Render2D.DrawLine(uv2, uv0, linesColor);
}
}
break;
case UVChannel.LightmapUVs:
for (int i = 0; i < meshData.IndexBuffer.Length; i += 3)
{
// Cache triangle indices
uint i0 = meshData.IndexBuffer[i + 0];
uint i1 = meshData.IndexBuffer[i + 1];
uint i2 = meshData.IndexBuffer[i + 2];
// Cache triangle uvs positions and transform positions to output target
Float2 uv0 = meshData.VertexBuffer[i0].LightmapUVs * uvScale;
Float2 uv1 = meshData.VertexBuffer[i1].LightmapUVs * uvScale;
Float2 uv2 = meshData.VertexBuffer[i2].LightmapUVs * uvScale;
// Don't draw too small triangles
float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2);
if (area > 3.0f)
{
// Draw triangle
Render2D.DrawLine(uv0, uv1, linesColor);
Render2D.DrawLine(uv1, uv2, linesColor);
Render2D.DrawLine(uv2, uv0, linesColor);
}
}
break;
}
}
/// <inheritdoc />
public override void DrawSelf()
{
base.DrawSelf();
var size = Size;
if (_channel == UVChannel.None || size.MaxValue < 5.0f)
return;
if (Proxy.Window._meshData == null)
Proxy.Window._meshData = new MeshDataCache();
if (!Proxy.Window._meshData.RequestMeshData(Proxy.Window._asset))
{
Invalidate();
Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center);
return;
}
Render2D.PushClip(new Rectangle(Float2.Zero, size));
var meshDatas = Proxy.Window._meshData.MeshDatas;
var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1);
var lod = meshDatas[lodIndex];
var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1);
if (mesh == -1)
{
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
{
if (_isolateIndex != -1 && _isolateIndex != meshIndex)
continue;
DrawMeshUVs(meshIndex, lod[meshIndex]);
}
}
else
{
DrawMeshUVs(mesh, lod[mesh]);
}
Render2D.PopClip();
}
protected override void OnSizeChanged()
{
Height = Width;
base.OnSizeChanged();
}
protected override void OnVisibleChanged()
{
base.OnVisibleChanged();
Parent.PerformLayout();
Height = Width;
}
}
}
[CustomEditor(typeof(ProxyEditor))]
@@ -802,7 +617,6 @@ namespace FlaxEditor.Windows.Assets
private readonly ModelPreview _preview;
private StaticModel _highlightActor;
private MeshDataCache _meshData;
private ModelImportSettings _importSettings = new ModelImportSettings();
private ModelSdfOptions _sdfOptions;
private ToolStripButton _showCurrentLODButton;
@@ -941,7 +755,6 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
protected override void UnlinkItem()
{
_meshData?.WaitForMeshDataRequestEnd();
_preview.Model = null;
_highlightActor.Model = null;
@@ -970,21 +783,9 @@ namespace FlaxEditor.Windows.Assets
base.OnAssetLoaded();
}
/// <inheritdoc />
public override void OnItemReimported(ContentItem item)
{
// Discard any old mesh data cache
_meshData?.Dispose();
base.OnItemReimported(item);
}
/// <inheritdoc />
public override void OnDestroy()
{
_meshData?.Dispose();
_meshData = null;
base.OnDestroy();
Object.Destroy(ref _highlightActor);

View File

@@ -5,8 +5,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors;
@@ -182,8 +180,6 @@ namespace FlaxEditor.Windows.Assets
proxy._highlightCheckBoxes.Clear();
var lods = proxy.Asset.LODs;
var loadedLODs = proxy.Asset.LoadedLODs;
var nodes = proxy.Asset.Nodes;
var bones = proxy.Asset.Bones;
// General properties
{
@@ -286,8 +282,6 @@ namespace FlaxEditor.Windows.Assets
var proxy = (SkeletonPropertiesProxy)Values[0];
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
return;
var lods = proxy.Asset.LODs;
var loadedLODs = proxy.Asset.LoadedLODs;
var nodes = proxy.Asset.Nodes;
var bones = proxy.Asset.Bones;
@@ -523,7 +517,7 @@ namespace FlaxEditor.Windows.Assets
if (_uvChannel == value)
return;
_uvChannel = value;
Window.RequestMeshData();
Window._meshData?.RequestMeshData(Window._asset);
}
}
@@ -558,7 +552,7 @@ namespace FlaxEditor.Windows.Assets
base.Initialize(layout);
_uvsPreview = layout.Custom<UVsLayoutPreviewControl>().CustomControl;
_uvsPreview.Proxy = proxy;
_uvsPreview.Window = proxy.Window;
}
/// <inheritdoc />
@@ -568,11 +562,12 @@ namespace FlaxEditor.Windows.Assets
if (_uvsPreview != null)
{
_uvsPreview.Channel = _uvsPreview.Proxy._uvChannel;
_uvsPreview.LOD = _uvsPreview.Proxy.LOD;
_uvsPreview.Mesh = _uvsPreview.Proxy.Mesh;
_uvsPreview.HighlightIndex = _uvsPreview.Proxy.Window._highlightIndex;
_uvsPreview.IsolateIndex = _uvsPreview.Proxy.Window._isolateIndex;
var proxy = (UVsPropertiesProxy)Values[0];
_uvsPreview.Channel = proxy._uvChannel == UVChannel.TexCoord ? 0 : -1;
_uvsPreview.LOD = proxy.LOD;
_uvsPreview.Mesh = proxy.Mesh;
_uvsPreview.HighlightIndex = proxy.Window._highlightIndex;
_uvsPreview.IsolateIndex = proxy.Window._isolateIndex;
}
}
@@ -583,171 +578,6 @@ namespace FlaxEditor.Windows.Assets
base.Deinitialize();
}
}
private sealed class UVsLayoutPreviewControl : RenderToTextureControl
{
private UVChannel _channel;
private int _lod, _mesh = -1;
private int _highlightIndex = -1;
private int _isolateIndex = -1;
public UVsPropertiesProxy Proxy;
public UVsLayoutPreviewControl()
{
Offsets = new Margin(4);
AutomaticInvalidate = false;
}
public UVChannel Channel
{
set
{
if (_channel == value)
return;
_channel = value;
Visible = _channel != UVChannel.None;
if (Visible)
Invalidate();
}
}
public int LOD
{
set
{
if (_lod != value)
{
_lod = value;
Invalidate();
}
}
}
public int Mesh
{
set
{
if (_mesh != value)
{
_mesh = value;
Invalidate();
}
}
}
public int HighlightIndex
{
set
{
if (_highlightIndex != value)
{
_highlightIndex = value;
Invalidate();
}
}
}
public int IsolateIndex
{
set
{
if (_isolateIndex != value)
{
_isolateIndex = value;
Invalidate();
}
}
}
private void DrawMeshUVs(int meshIndex, MeshData meshData)
{
var uvScale = Size;
var linesColor = _highlightIndex != -1 && _highlightIndex == meshIndex ? Style.Current.BackgroundSelected : Color.White;
switch (_channel)
{
case UVChannel.TexCoord:
for (int i = 0; i < meshData.IndexBuffer.Length; i += 3)
{
// Cache triangle indices
uint i0 = meshData.IndexBuffer[i + 0];
uint i1 = meshData.IndexBuffer[i + 1];
uint i2 = meshData.IndexBuffer[i + 2];
// Cache triangle uvs positions and transform positions to output target
Float2 uv0 = meshData.VertexBuffer[i0].TexCoord * uvScale;
Float2 uv1 = meshData.VertexBuffer[i1].TexCoord * uvScale;
Float2 uv2 = meshData.VertexBuffer[i2].TexCoord * uvScale;
// Don't draw too small triangles
float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2);
if (area > 3.0f)
{
// Draw triangle
Render2D.DrawLine(uv0, uv1, linesColor);
Render2D.DrawLine(uv1, uv2, linesColor);
Render2D.DrawLine(uv2, uv0, linesColor);
}
}
break;
}
}
/// <inheritdoc />
protected override void DrawChildren()
{
base.DrawChildren();
var size = Size;
if (_channel == UVChannel.None || size.MaxValue < 5.0f)
return;
if (!Proxy.Window.RequestMeshData())
{
Invalidate();
Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center);
return;
}
Render2D.PushClip(new Rectangle(Float2.Zero, size));
var meshDatas = Proxy.Window._meshDatas;
if (meshDatas.Length != 0)
{
var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1);
var lod = meshDatas[lodIndex];
var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1);
if (mesh == -1)
{
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
{
if (_isolateIndex != -1 && _isolateIndex != meshIndex)
continue;
DrawMeshUVs(meshIndex, lod[meshIndex]);
}
}
else
{
DrawMeshUVs(mesh, lod[mesh]);
}
}
Render2D.PopClip();
}
protected override void OnSizeChanged()
{
Height = Width;
base.OnSizeChanged();
}
protected override void OnVisibleChanged()
{
base.OnVisibleChanged();
Parent.PerformLayout();
Height = Width;
}
}
}
[CustomEditor(typeof(ProxyEditor))]
@@ -1159,21 +989,11 @@ namespace FlaxEditor.Windows.Assets
}
}
private struct MeshData
{
public uint[] IndexBuffer;
public SkinnedMesh.Vertex[] VertexBuffer;
}
private Preview _preview;
private AnimatedModel _highlightActor;
private ToolStripButton _showNodesButton;
private ToolStripButton _showCurrentLODButton;
private MeshData[][] _meshDatas;
private bool _meshDatasInProgress;
private bool _meshDatasCancel;
/// <inheritdoc />
public SkinnedModelWindow(Editor editor, AssetItem item)
: base(editor, item)
@@ -1256,67 +1076,6 @@ namespace FlaxEditor.Windows.Assets
}
}
private bool RequestMeshData()
{
if (_meshDatasInProgress)
return false;
if (_meshDatas != null)
return true;
_meshDatasInProgress = true;
_meshDatasCancel = false;
Task.Run(new Action(DownloadMeshData));
return false;
}
private void WaitForMeshDataRequestEnd()
{
if (_meshDatasInProgress)
{
_meshDatasCancel = true;
for (int i = 0; i < 500 && _meshDatasInProgress; i++)
Thread.Sleep(10);
}
}
private void DownloadMeshData()
{
try
{
if (!_asset)
{
_meshDatasInProgress = false;
return;
}
var lods = _asset.LODs;
_meshDatas = new MeshData[lods.Length][];
for (int lodIndex = 0; lodIndex < lods.Length && !_meshDatasCancel; lodIndex++)
{
var lod = lods[lodIndex];
var meshes = lod.Meshes;
_meshDatas[lodIndex] = new MeshData[meshes.Length];
for (int meshIndex = 0; meshIndex < meshes.Length && !_meshDatasCancel; meshIndex++)
{
var mesh = meshes[meshIndex];
_meshDatas[lodIndex][meshIndex] = new MeshData
{
IndexBuffer = mesh.DownloadIndexBuffer(),
VertexBuffer = mesh.DownloadVertexBuffer()
};
}
}
}
catch (Exception ex)
{
Editor.LogWarning("Failed to get mesh data. " + ex.Message);
Editor.LogWarning(ex);
}
finally
{
_meshDatasInProgress = false;
}
}
/// <inheritdoc />
public override void Update(float deltaTime)
@@ -1373,7 +1132,6 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
protected override void UnlinkItem()
{
WaitForMeshDataRequestEnd();
_preview.SkinnedModel = null;
_highlightActor.SkinnedModel = null;
@@ -1404,22 +1162,9 @@ namespace FlaxEditor.Windows.Assets
base.OnAssetLoaded();
}
/// <inheritdoc />
public override void OnItemReimported(ContentItem item)
{
// Discard any old mesh data cache
WaitForMeshDataRequestEnd();
_meshDatas = null;
_meshDatasInProgress = false;
base.OnItemReimported(item);
}
/// <inheritdoc />
public override void OnDestroy()
{
WaitForMeshDataRequestEnd();
base.OnDestroy();
Object.Destroy(ref _highlightActor);