**Refactor meshes format to support custom vertex layouts and new flexible api to access mesh data**
#3044 #2667
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user