**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);
|
||||
|
||||
@@ -273,7 +273,7 @@ bool CSGBuilderImpl::buildInner(Scene* scene, BuildData& data)
|
||||
|
||||
// Convert CSG meshes into raw triangles data
|
||||
RawData meshData;
|
||||
Array<RawModelVertex> vertexBuffer;
|
||||
Array<MeshVertex> vertexBuffer;
|
||||
combinedMesh->Triangulate(meshData, vertexBuffer);
|
||||
meshData.RemoveEmptySlots();
|
||||
if (meshData.Slots.HasItems())
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace CSG
|
||||
};
|
||||
}
|
||||
|
||||
void RawData::Slot::AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount)
|
||||
void RawData::Slot::AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const MeshVertex* firstVertex, int32 vertexCount)
|
||||
{
|
||||
auto& surface = Surfaces.AddOne();
|
||||
surface.ScaleInLightmap = scaleInLightmap;
|
||||
@@ -65,7 +65,7 @@ void RawData::Slot::AddSurface(float scaleInLightmap, const Rectangle& lightmapU
|
||||
surface.Vertices.Add(firstVertex, vertexCount);
|
||||
}
|
||||
|
||||
void RawData::AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount)
|
||||
void RawData::AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const MeshVertex* firstVertex, int32 vertexCount)
|
||||
{
|
||||
// Add surface to slot
|
||||
auto slot = GetOrAddSlot(surfaceMaterial);
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace CSG
|
||||
Rectangle LightmapUVsBox;
|
||||
Float2 Size;
|
||||
Rectangle UVsArea;
|
||||
Array<RawModelVertex> Vertices;
|
||||
Array<MeshVertex> Vertices;
|
||||
};
|
||||
|
||||
struct SurfaceTriangle
|
||||
@@ -68,7 +68,7 @@ namespace CSG
|
||||
return Surfaces.IsEmpty();
|
||||
}
|
||||
|
||||
void AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount);
|
||||
void AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const MeshVertex* firstVertex, int32 vertexCount);
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -118,7 +118,7 @@ namespace CSG
|
||||
}
|
||||
|
||||
public:
|
||||
void AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount);
|
||||
void AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const MeshVertex* firstVertex, int32 vertexCount);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the empty slots.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "CSGData.h"
|
||||
|
||||
bool CSG::Mesh::Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const
|
||||
bool CSG::Mesh::Triangulate(RawData& data, Array<MeshVertex>& cacheVB) const
|
||||
{
|
||||
// Reject empty meshes
|
||||
int32 verticesCount = _vertices.Count();
|
||||
@@ -19,7 +19,7 @@ bool CSG::Mesh::Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const
|
||||
// Mesh triangles data
|
||||
cacheVB.Clear();
|
||||
cacheVB.EnsureCapacity(_polygons.Count() * 3);
|
||||
Array<RawModelVertex> surfaceCacheVB(32);
|
||||
Array<MeshVertex> surfaceCacheVB(32);
|
||||
|
||||
// Cache submeshes by material to lay them down
|
||||
// key- brush index, value- direcotry for surfaces (key: surface index, value: list with start vertex per triangle)
|
||||
@@ -29,7 +29,7 @@ bool CSG::Mesh::Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const
|
||||
int32 firstI, secondI, thirdI;
|
||||
int32 triangleIndices[3];
|
||||
Vector2 uvs[3];
|
||||
RawModelVertex rawVertex;
|
||||
MeshVertex rawVertex;
|
||||
for (int32 i = 0; i < _polygons.Count(); i++)
|
||||
{
|
||||
const Polygon& polygon = _polygons[i];
|
||||
@@ -142,7 +142,6 @@ bool CSG::Mesh::Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const
|
||||
rawVertex.Tangent = tangent;
|
||||
rawVertex.Bitangent = bitangent;
|
||||
rawVertex.LightmapUVs = Vector2::Zero;
|
||||
rawVertex.Color = Color::Black;
|
||||
|
||||
cacheVB.Add(rawVertex);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace CSG
|
||||
/// <param name="data">Result data</param>
|
||||
/// <param name="cacheVB">Cache data</param>
|
||||
/// <returns>True if cannot generate valid mesh data (due to missing triangles etc.)</returns>
|
||||
bool Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const;
|
||||
bool Triangulate(RawData& data, Array<MeshVertex>& cacheVB) const;
|
||||
|
||||
/// <summary>
|
||||
/// Perform CSG operation with another mesh
|
||||
|
||||
@@ -8,4 +8,14 @@
|
||||
namespace CSG
|
||||
{
|
||||
typedef ::BrushMode Mode;
|
||||
|
||||
struct MeshVertex
|
||||
{
|
||||
Float3 Position;
|
||||
Float2 TexCoord;
|
||||
Float3 Normal;
|
||||
Float3 Tangent;
|
||||
Float3 Bitangent;
|
||||
Float2 LightmapUVs;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Content/JsonAsset.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#endif
|
||||
|
||||
REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false);
|
||||
@@ -490,6 +492,64 @@ bool Animation::Save(const StringView& path)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Animation::SaveHeader(const ModelData& modelData, WriteStream& stream, int32 animIndex)
|
||||
{
|
||||
// Validate input
|
||||
if (animIndex < 0 || animIndex >= modelData.Animations.Count())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("animIndex"));
|
||||
return true;
|
||||
}
|
||||
auto& anim = modelData.Animations.Get()[animIndex];
|
||||
if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("Invalid animation duration."));
|
||||
return true;
|
||||
}
|
||||
if (anim.Channels.IsEmpty())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty."));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Info
|
||||
stream.Write(103); // Header version (for fast version upgrades without serialization format change)
|
||||
stream.Write(anim.Duration);
|
||||
stream.Write(anim.FramesPerSecond);
|
||||
stream.Write((byte)anim.RootMotionFlags);
|
||||
stream.Write(anim.RootNodeName, 13);
|
||||
|
||||
// Animation channels
|
||||
stream.WriteInt32(anim.Channels.Count());
|
||||
for (const auto& channel : anim.Channels)
|
||||
{
|
||||
stream.Write(channel.NodeName, 172);
|
||||
Serialization::Serialize(stream, channel.Position);
|
||||
Serialization::Serialize(stream, channel.Rotation);
|
||||
Serialization::Serialize(stream, channel.Scale);
|
||||
}
|
||||
|
||||
// Animation events
|
||||
stream.WriteInt32(anim.Events.Count());
|
||||
for (auto& e : anim.Events)
|
||||
{
|
||||
stream.Write(e.First, 172);
|
||||
stream.Write(e.Second.GetKeyframes().Count());
|
||||
for (const auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
stream.Write(k.Time);
|
||||
stream.Write(k.Value.Duration);
|
||||
stream.Write(k.Value.TypeName, 17);
|
||||
stream.WriteJson(k.Value.JsonData);
|
||||
}
|
||||
}
|
||||
|
||||
// Nested animations
|
||||
stream.WriteInt32(0); // Empty list
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Animation::GetReferences(Array<Guid>& assets, Array<String>& files) const
|
||||
{
|
||||
BinaryAsset::GetReferences(assets, files);
|
||||
|
||||
@@ -151,6 +151,12 @@ public:
|
||||
bool Save(const StringView& path = StringView::Empty);
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if USE_EDITOR
|
||||
friend class ImportModel;
|
||||
static bool SaveHeader(const class ModelData& modelData, WriteStream& stream, int32 animIndex);
|
||||
#endif
|
||||
|
||||
public:
|
||||
// [BinaryAsset]
|
||||
#if USE_EDITOR
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "Engine/Content/WeakAssetReference.h"
|
||||
#include "Engine/Content/Upgraders/ModelAssetUpgrader.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
|
||||
@@ -18,13 +18,13 @@
|
||||
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
|
||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||
#include "Engine/Graphics/Textures/GPUTexture.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Tools/ModelTool/ModelTool.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#endif
|
||||
|
||||
REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase");
|
||||
@@ -246,330 +246,6 @@ bool Model::SetupLODs(const Span<int32>& meshesCountPerLod)
|
||||
return Init(meshesCountPerLod);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Model::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
{
|
||||
// Validate state
|
||||
if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && path.IsEmpty())
|
||||
{
|
||||
LOG(Error, "To save virtual asset asset you need to specify the target asset path location.");
|
||||
return true;
|
||||
}
|
||||
if (withMeshDataFromGpu && IsInMainThread())
|
||||
{
|
||||
LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread).");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && !withMeshDataFromGpu)
|
||||
{
|
||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Create model data header
|
||||
MemoryWriteStream headerStream(1024);
|
||||
MemoryWriteStream* stream = &headerStream;
|
||||
{
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
stream->WriteInt32(MaterialSlots.Count());
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < MaterialSlots.Count(); materialSlotIndex++)
|
||||
{
|
||||
auto& slot = MaterialSlots[materialSlotIndex];
|
||||
|
||||
const auto id = slot.Material.GetID();
|
||||
stream->Write(id);
|
||||
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
|
||||
stream->WriteString(slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
const int32 lods = LODs.Count();
|
||||
stream->WriteByte(lods);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream->WriteFloat(lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
stream->WriteUint16(meshes);
|
||||
|
||||
// For each mesh
|
||||
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
|
||||
// Material Slot index
|
||||
stream->WriteInt32(mesh.GetMaterialSlotIndex());
|
||||
|
||||
// Box
|
||||
const auto box = mesh.GetBox();
|
||||
stream->WriteBoundingBox(box);
|
||||
|
||||
// Sphere
|
||||
const auto sphere = mesh.GetSphere();
|
||||
stream->WriteBoundingSphere(sphere);
|
||||
|
||||
// Has Lightmap UVs
|
||||
stream->WriteBool(mesh.HasLightmapUVs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS];
|
||||
Platform::MemoryClear(tmpChunks, sizeof(tmpChunks));
|
||||
Array<FlaxChunk> chunks;
|
||||
if (IsVirtual())
|
||||
chunks.Resize(ASSET_FILE_DATA_CHUNKS);
|
||||
#define GET_CHUNK(index) (IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index))
|
||||
|
||||
// Check if use data from drive or from GPU
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Download all meshes buffers
|
||||
Array<Task*> tasks;
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
const int32 meshesCount = lod.Meshes.Count();
|
||||
struct MeshData
|
||||
{
|
||||
BytesContainer VB0;
|
||||
BytesContainer VB1;
|
||||
BytesContainer VB2;
|
||||
BytesContainer IB;
|
||||
|
||||
uint32 DataSize() const
|
||||
{
|
||||
return VB0.Length() + VB1.Length() + VB2.Length() + IB.Length();
|
||||
}
|
||||
};
|
||||
Array<MeshData> meshesData;
|
||||
meshesData.Resize(meshesCount);
|
||||
tasks.EnsureCapacity(meshesCount * 4);
|
||||
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
auto& meshData = meshesData[meshIndex];
|
||||
|
||||
// Vertex Buffer 0 (required)
|
||||
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Vertex Buffer 1 (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB1);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Vertex Buffer 2 (optional)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB2);
|
||||
if (task)
|
||||
{
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Index Buffer (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Wait for all
|
||||
if (Task::WaitAll(tasks))
|
||||
return true;
|
||||
tasks.Clear();
|
||||
|
||||
// Create meshes data
|
||||
{
|
||||
int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool));
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
dataSize += meshesData[meshIndex].DataSize();
|
||||
}
|
||||
|
||||
MemoryWriteStream meshesStream(dataSize);
|
||||
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
const auto& meshData = meshesData[meshIndex];
|
||||
|
||||
uint32 vertices = mesh.GetVertexCount();
|
||||
uint32 triangles = mesh.GetTriangleCount();
|
||||
bool hasColors = meshData.VB2.IsValid();
|
||||
uint32 vb0Size = vertices * sizeof(VB0ElementType);
|
||||
uint32 vb1Size = vertices * sizeof(VB1ElementType);
|
||||
uint32 vb2Size = vertices * sizeof(VB2ElementType);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer();
|
||||
uint32 ibSize = indicesCount * (use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32));
|
||||
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.VB0.Length() < vb0Size)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer 0 size.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.VB1.Length() < vb1Size)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer 1 size.");
|
||||
return true;
|
||||
}
|
||||
if (hasColors && (uint32)meshData.VB2.Length() < vb2Size)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer 2 size.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.IB.Length() < ibSize)
|
||||
{
|
||||
LOG(Warning, "Invalid index buffer size.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
meshesStream.WriteUint32(vertices);
|
||||
meshesStream.WriteUint32(triangles);
|
||||
|
||||
meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size);
|
||||
meshesStream.WriteBytes(meshData.VB1.Get(), vb1Size);
|
||||
|
||||
meshesStream.WriteBool(hasColors);
|
||||
|
||||
if (hasColors)
|
||||
{
|
||||
meshesStream.WriteBytes(meshData.VB2.Get(), vb2Size);
|
||||
}
|
||||
|
||||
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
|
||||
{
|
||||
meshesStream.WriteBytes(meshData.IB.Get(), ibSize);
|
||||
}
|
||||
else if (shouldUse16BitIndexBuffer)
|
||||
{
|
||||
auto ib = (const int32*)meshData.IB.Get();
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
{
|
||||
meshesStream.WriteUint16(ib[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CRASH;
|
||||
}
|
||||
}
|
||||
|
||||
// Override LOD data chunk with the fetched GPU meshes memory
|
||||
auto lodChunk = GET_CHUNK(MODEL_LOD_TO_CHUNK_INDEX(lodIndex));
|
||||
if (lodChunk == nullptr)
|
||||
return true;
|
||||
lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
// Download SDF data
|
||||
if (SDF.Texture)
|
||||
{
|
||||
auto sdfChunk = GET_CHUNK(15);
|
||||
if (sdfChunk == nullptr)
|
||||
return true;
|
||||
MemoryWriteStream sdfStream;
|
||||
sdfStream.WriteInt32(1); // Version
|
||||
ModelSDFHeader data(SDF, SDF.Texture->GetDescription());
|
||||
sdfStream.WriteBytes(&data, sizeof(data));
|
||||
TextureData sdfTextureData;
|
||||
if (SDF.Texture->DownloadData(sdfTextureData))
|
||||
return true;
|
||||
for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++)
|
||||
{
|
||||
auto& mip = sdfTextureData.Items[0].Mips[mipLevel];
|
||||
ModelSDFMip mipData(mipLevel, mip);
|
||||
sdfStream.WriteBytes(&mipData, sizeof(mipData));
|
||||
sdfStream.WriteBytes(mip.Data.Get(), mip.Data.Length());
|
||||
}
|
||||
sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load all chunks with a mesh data
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SDF.Texture)
|
||||
{
|
||||
// SDF data from file (only if has no cached texture data)
|
||||
if (LoadChunk(15))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No SDF texture
|
||||
ReleaseChunk(15);
|
||||
}
|
||||
}
|
||||
|
||||
// Set mesh header data
|
||||
auto headerChunk = GET_CHUNK(0);
|
||||
ASSERT(headerChunk != nullptr);
|
||||
headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition());
|
||||
|
||||
#undef GET_CHUNK
|
||||
|
||||
// Save
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = SerializedVersion;
|
||||
if (IsVirtual())
|
||||
Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks));
|
||||
const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true);
|
||||
if (IsVirtual())
|
||||
Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks));
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Error, "Cannot save \'{0}\'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold, bool useGPU)
|
||||
{
|
||||
if (EnableModelSDF == 2)
|
||||
@@ -666,6 +342,172 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
{
|
||||
if (ModelBase::LoadHeader(stream, headerVersion))
|
||||
return true;
|
||||
|
||||
// LODs
|
||||
byte lods = stream.ReadByte();
|
||||
if (lods == 0 || lods > MODEL_MAX_LODS)
|
||||
return true;
|
||||
LODs.Resize(lods);
|
||||
_initialized = true;
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
stream.ReadFloat(&lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
uint16 meshesCount;
|
||||
stream.ReadUint16(&meshesCount);
|
||||
if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES)
|
||||
return true;
|
||||
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
|
||||
lod.Meshes.Resize(meshesCount, false);
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
Mesh& mesh = lod.Meshes[meshIndex];
|
||||
mesh.Link(this, lodIndex, meshIndex);
|
||||
|
||||
// Material Slot index
|
||||
int32 materialSlotIndex;
|
||||
stream.ReadInt32(&materialSlotIndex);
|
||||
if (materialSlotIndex < 0 || materialSlotIndex >= MaterialSlots.Count())
|
||||
{
|
||||
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, MaterialSlots.Count());
|
||||
return true;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
mesh.SetBounds(box, sphere);
|
||||
|
||||
// Lightmap UVs channel
|
||||
int8 lightmapUVs;
|
||||
stream.ReadInt8(&lightmapUVs);
|
||||
mesh.LightmapUVsIndex = (int32)lightmapUVs;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Model::SaveHeader(WriteStream& stream)
|
||||
{
|
||||
if (ModelBase::SaveHeader(stream))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
stream.Write((byte)LODs.Count());
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
stream.Write(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
stream.Write((uint16)lod.Meshes.Count());
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
stream.Write(mesh.GetMaterialSlotIndex());
|
||||
stream.Write(mesh.GetBox());
|
||||
stream.Write(mesh.GetSphere());
|
||||
stream.Write((int8)mesh.LightmapUVsIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
{
|
||||
if (ModelBase::SaveHeader(stream, modelData))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
stream.Write((byte)modelData.LODs.Count());
|
||||
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = modelData.LODs[lodIndex];
|
||||
stream.Write(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
stream.Write((uint16)lod.Meshes.Count());
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
BoundingBox box;
|
||||
BoundingSphere sphere;
|
||||
mesh->CalculateBounds(box, sphere);
|
||||
stream.Write(mesh->MaterialSlotIndex);
|
||||
stream.Write(box);
|
||||
stream.Write(sphere);
|
||||
stream.Write((int8)mesh->LightmapUVsIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk)
|
||||
{
|
||||
if (ModelBase::Save(withMeshDataFromGpu, getChunk))
|
||||
return true;
|
||||
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Download SDF data
|
||||
if (SDF.Texture)
|
||||
{
|
||||
auto sdfChunk = getChunk(15);
|
||||
if (sdfChunk == nullptr)
|
||||
return true;
|
||||
MemoryWriteStream sdfStream;
|
||||
sdfStream.WriteInt32(1); // Version
|
||||
ModelSDFHeader data(SDF, SDF.Texture->GetDescription());
|
||||
sdfStream.WriteBytes(&data, sizeof(data));
|
||||
TextureData sdfTextureData;
|
||||
if (SDF.Texture->DownloadData(sdfTextureData))
|
||||
return true;
|
||||
for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++)
|
||||
{
|
||||
auto& mip = sdfTextureData.Items[0].Mips[mipLevel];
|
||||
ModelSDFMip mipData(mipLevel, mip);
|
||||
sdfStream.WriteBytes(&mipData, sizeof(mipData));
|
||||
sdfStream.WriteBytes(mip.Data.Get(), mip.Data.Length());
|
||||
}
|
||||
sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDF.Texture)
|
||||
{
|
||||
// SDF data from file (only if has no cached texture data)
|
||||
if (LoadChunk(15))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No SDF texture
|
||||
ReleaseChunk(15);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Model::SetupMaterialSlots(int32 slotsCount)
|
||||
{
|
||||
ModelBase::SetupMaterialSlots(slotsCount);
|
||||
@@ -687,6 +529,26 @@ int32 Model::GetLODsCount() const
|
||||
return LODs.Count();
|
||||
}
|
||||
|
||||
const MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex) const
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
return &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
return &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
void Model::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
@@ -722,91 +584,11 @@ Asset::LoadResult Model::load()
|
||||
if (chunk0 == nullptr || chunk0->IsMissing())
|
||||
return LoadResult::MissingDataChunk;
|
||||
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
||||
ReadStream* stream = &headerStream;
|
||||
|
||||
// Min Screen Size
|
||||
stream->ReadFloat(&MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
int32 materialSlotsCount;
|
||||
stream->ReadInt32(&materialSlotsCount);
|
||||
if (materialSlotsCount <= 0 || materialSlotsCount > 4096)
|
||||
|
||||
// Load asset data (anything but mesh contents that use streaming)
|
||||
byte headerVersion;
|
||||
if (LoadHeader(headerStream, headerVersion))
|
||||
return LoadResult::InvalidData;
|
||||
MaterialSlots.Resize(materialSlotsCount, false);
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
auto& slot = MaterialSlots[materialSlotIndex];
|
||||
|
||||
// Material
|
||||
Guid materialId;
|
||||
stream->Read(materialId);
|
||||
slot.Material = materialId;
|
||||
|
||||
// Shadows Mode
|
||||
slot.ShadowsMode = static_cast<ShadowsCastingMode>(stream->ReadByte());
|
||||
|
||||
// Name
|
||||
stream->ReadString(&slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
byte lods;
|
||||
stream->ReadByte(&lods);
|
||||
if (lods == 0 || lods > MODEL_MAX_LODS)
|
||||
return LoadResult::InvalidData;
|
||||
LODs.Resize(lods);
|
||||
_initialized = true;
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
|
||||
// Screen Size
|
||||
stream->ReadFloat(&lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream->ReadUint16(&meshesCount);
|
||||
if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES)
|
||||
return LoadResult::InvalidData;
|
||||
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
|
||||
|
||||
// Allocate memory
|
||||
lod.Meshes.Resize(meshesCount, false);
|
||||
|
||||
// For each mesh
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
Mesh& mesh = lod.Meshes[meshIndex];
|
||||
mesh.Link(this, lodIndex, meshIndex);
|
||||
|
||||
// Material Slot index
|
||||
int32 materialSlotIndex;
|
||||
stream->ReadInt32(&materialSlotIndex);
|
||||
if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount)
|
||||
{
|
||||
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount);
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream->ReadBoundingBox(&box);
|
||||
BoundingSphere sphere;
|
||||
stream->ReadBoundingSphere(&sphere);
|
||||
mesh.SetBounds(box, sphere);
|
||||
|
||||
// Has Lightmap UVs
|
||||
bool hasLightmapUVs = stream->ReadBool();
|
||||
mesh.LightmapUVsIndex = hasLightmapUVs ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Load SDF
|
||||
auto chunk15 = GetChunk(15);
|
||||
@@ -815,7 +597,7 @@ Asset::LoadResult Model::load()
|
||||
PROFILE_CPU_NAMED("SDF");
|
||||
MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size());
|
||||
int32 version;
|
||||
sdfStream.ReadInt32(&version);
|
||||
sdfStream.Read(version);
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
|
||||
@@ -159,7 +159,7 @@ public:
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API Model : public ModelBase
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(Model, 25);
|
||||
DECLARE_BINARY_ASSET_HEADER(Model, 30);
|
||||
friend Mesh;
|
||||
|
||||
public:
|
||||
@@ -274,19 +274,6 @@ public:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupLODs(const Span<int32>& meshesCountPerLod);
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
/// <remarks>If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread.</remarks>
|
||||
/// <param name="withMeshDataFromGpu">True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location and it's loaded.</param>
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True if cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Generates the Sign Distant Field for this model.
|
||||
/// </summary>
|
||||
@@ -312,10 +299,22 @@ private:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool Init(const Span<int32>& meshesCountPerLod);
|
||||
|
||||
// [ModelBase]
|
||||
bool LoadHeader(ReadStream& stream, byte& headerVersion);
|
||||
#if USE_EDITOR
|
||||
friend class ImportModel;
|
||||
bool SaveHeader(WriteStream& stream) override;
|
||||
static bool SaveHeader(WriteStream& stream, const ModelData& modelData);
|
||||
bool Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk) override;
|
||||
#endif
|
||||
|
||||
public:
|
||||
// [ModelBase]
|
||||
void SetupMaterialSlots(int32 slotsCount) override;
|
||||
int32 GetLODsCount() const 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;
|
||||
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||
void InitAsVirtual() override;
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include "Engine/Content/WeakAssetReference.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Graphics/Config.h"
|
||||
#include "Engine/Graphics/GPUBuffer.h"
|
||||
#include "Engine/Graphics/Models/MeshBase.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
|
||||
#include "Engine/Threading/ThreadPoolTask.h"
|
||||
#define STREAM_TASK_BASE ThreadPoolTask
|
||||
@@ -12,8 +15,11 @@
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#define STREAM_TASK_BASE MainThreadTask
|
||||
#endif
|
||||
#include "SkinnedModel.h" // TODO: remove this
|
||||
#include "Model.h" // TODO: remove this
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Model LOD streaming task.
|
||||
@@ -54,93 +60,26 @@ public:
|
||||
}
|
||||
MemoryReadStream stream(data.Get(), data.Length());
|
||||
|
||||
// Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
|
||||
|
||||
// Load model LOD (initialize vertex and index buffers)
|
||||
// TODO: reformat model data storage to be the same for both assets (only custom per-mesh type data like BlendShapes)
|
||||
// Load meshes data and pass data to the GPU buffers
|
||||
Array<MeshBase*> meshes;
|
||||
model->GetMeshes(meshes, _lodIndex);
|
||||
if (model->Is<SkinnedModel>())
|
||||
byte meshVersion = stream.ReadByte();
|
||||
if (meshVersion < 2 || meshVersion > ModelBase::MeshVersion)
|
||||
{
|
||||
byte version = stream.ReadByte();
|
||||
for (int32 i = 0; i < meshes.Count(); i++)
|
||||
{
|
||||
auto& mesh = (SkinnedMesh&)*meshes[i];
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.ReadUint16(&blendShapesCount);
|
||||
if (blendShapesCount != mesh.BlendShapes.Count())
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Incorrect blend shapes amount: {} (expected: {})", i, _lodIndex, model->ToString(), blendShapesCount, mesh.BlendShapes.Count());
|
||||
return true;
|
||||
}
|
||||
for (auto& blendShape : mesh.BlendShapes)
|
||||
{
|
||||
blendShape.UseNormals = stream.ReadBool();
|
||||
stream.ReadUint32(&blendShape.MinVertexIndex);
|
||||
stream.ReadUint32(&blendShape.MaxVertexIndex);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
blendShape.Vertices.Resize(blendShapeVertices);
|
||||
stream.ReadBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
const uint32 indicesCount = triangles * 3;
|
||||
const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
const auto vb0 = stream.Move<VB0SkinnedElementType>(vertices);
|
||||
const auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
|
||||
// Setup GPU resources
|
||||
if (mesh.Load(vertices, triangles, vb0, ib, use16BitIndexBuffer))
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG(Warning, "Unsupported mesh version {}", meshVersion);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
for (int32 i = 0; i < meshes.Count(); i++)
|
||||
if (model->LoadMesh(stream, meshVersion, meshes[meshIndex]))
|
||||
{
|
||||
auto& mesh = (Mesh&)*meshes[i];
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
auto vb0 = stream.Move<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Move<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
|
||||
// Setup GPU resources
|
||||
if (mesh.Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer))
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles);
|
||||
return true;
|
||||
}
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'", meshIndex, _lodIndex, model->ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update residency level
|
||||
// Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
|
||||
model->_loadedLODs++;
|
||||
model->ResidencyChanged();
|
||||
|
||||
@@ -209,6 +148,638 @@ void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const
|
||||
GetChunkData(chunkIndex, data);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
{
|
||||
// Validate state
|
||||
if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && path.IsEmpty())
|
||||
{
|
||||
LOG(Error, "To save virtual asset asset you need to specify the target asset path location.");
|
||||
return true;
|
||||
}
|
||||
if (withMeshDataFromGpu && IsInMainThread())
|
||||
{
|
||||
LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread).");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && !withMeshDataFromGpu)
|
||||
{
|
||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS];
|
||||
Platform::MemoryClear(tmpChunks, sizeof(tmpChunks));
|
||||
Array<FlaxChunk> chunks;
|
||||
if (IsVirtual())
|
||||
chunks.Resize(ASSET_FILE_DATA_CHUNKS);
|
||||
Function<FlaxChunk*(int32)> getChunk = [&](int32 index) -> FlaxChunk* {
|
||||
return IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index);
|
||||
};
|
||||
|
||||
// Save LODs data
|
||||
const int32 lodsCount = GetLODsCount();
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Fetch runtime mesh data (from GPU)
|
||||
MemoryWriteStream meshesStream;
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
meshesStream.SetPosition(0);
|
||||
if (SaveLOD(meshesStream, lodIndex))
|
||||
return true;
|
||||
auto lodChunk = getChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex));
|
||||
if (lodChunk == nullptr)
|
||||
return true;
|
||||
lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition());
|
||||
}
|
||||
}
|
||||
else if (!IsVirtual())
|
||||
{
|
||||
// Load all chunks with a mesh data
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Save custom data
|
||||
if (Save(withMeshDataFromGpu, getChunk))
|
||||
return true;
|
||||
|
||||
// Save header data
|
||||
{
|
||||
MemoryWriteStream headerStream(1024);
|
||||
if (SaveHeader(headerStream))
|
||||
return true;
|
||||
auto headerChunk = getChunk(0);
|
||||
ASSERT(headerChunk != nullptr);
|
||||
headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition());
|
||||
}
|
||||
|
||||
// Save file
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = GetSerializedVersion();
|
||||
if (IsVirtual())
|
||||
Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks));
|
||||
const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true);
|
||||
if (IsVirtual())
|
||||
Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks));
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Error, "Cannot save \'{0}\'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
{
|
||||
// Basic info
|
||||
stream.Read(headerVersion);
|
||||
if (headerVersion < 2 || headerVersion > HeaderVersion)
|
||||
{
|
||||
LOG(Warning, "Unsupported model asset header version {}", headerVersion);
|
||||
return true;
|
||||
}
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Read(MinScreenSize);
|
||||
|
||||
// Materials
|
||||
int32 materialsCount;
|
||||
stream.Read(materialsCount);
|
||||
if (materialsCount < 0 || materialsCount > 4096)
|
||||
return true;
|
||||
MaterialSlots.Resize(materialsCount, false);
|
||||
Guid materialId;
|
||||
for (auto& slot : MaterialSlots)
|
||||
{
|
||||
stream.Read(materialId);
|
||||
slot.Material = materialId;
|
||||
slot.ShadowsMode = (ShadowsCastingMode)stream.ReadByte();
|
||||
stream.Read(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
|
||||
{
|
||||
// Load descriptor
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
uint32 vertices, triangles;
|
||||
stream.Read(vertices);
|
||||
stream.Read(triangles);
|
||||
const uint32 indicesCount = triangles * 3;
|
||||
const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
Array<const void*, FixedAllocation<MODEL_MAX_VB>> vbData;
|
||||
Array<GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>> vbLayout;
|
||||
byte vbCount;
|
||||
stream.Read(vbCount);
|
||||
if (vbCount > MODEL_MAX_VB)
|
||||
return true;
|
||||
vbData.Resize(vbCount);
|
||||
vbLayout.Resize(vbCount);
|
||||
for (int32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
GPUVertexLayout::Elements elements;
|
||||
stream.Read(elements);
|
||||
vbLayout[i] = GPUVertexLayout::Get(elements);
|
||||
}
|
||||
|
||||
// Move over actual mesh data
|
||||
for (int32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
auto layout = vbLayout[i];
|
||||
if (!layout)
|
||||
{
|
||||
LOG(Warning, "Failed to get vertex layout for buffer {}", i);
|
||||
return true;
|
||||
}
|
||||
vbData[i] = stream.Move(vertices * layout->GetStride());
|
||||
}
|
||||
const auto ibData = stream.Move(indicesCount * ibStride);
|
||||
|
||||
// Pass results if read-only
|
||||
if (dataIfReadOnly)
|
||||
{
|
||||
dataIfReadOnly->Vertices = vertices;
|
||||
dataIfReadOnly->Triangles = triangles;
|
||||
dataIfReadOnly->IBStride = ibStride;
|
||||
dataIfReadOnly->VBData = vbData;
|
||||
dataIfReadOnly->VBLayout = vbLayout;
|
||||
dataIfReadOnly->IBData = ibData;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup GPU resources
|
||||
return mesh->Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool ModelBase::SaveHeader(WriteStream& stream)
|
||||
{
|
||||
// Basic info
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Write(HeaderVersion);
|
||||
stream.Write(MinScreenSize);
|
||||
|
||||
// Materials
|
||||
stream.Write(MaterialSlots.Count());
|
||||
for (const auto& slot : MaterialSlots)
|
||||
{
|
||||
stream.Write(slot.Material.GetID());
|
||||
stream.Write((byte)slot.ShadowsMode);
|
||||
stream.Write(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
{
|
||||
// Validate data
|
||||
if (modelData.LODs.Count() > MODEL_MAX_LODS)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("LODs"), TEXT("Too many LODs."));
|
||||
return true;
|
||||
}
|
||||
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = modelData.LODs[lodIndex];
|
||||
if (lod.Meshes.Count() > MODEL_MAX_MESHES)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("LOD.Meshes"), TEXT("Too many meshes."));
|
||||
return true;
|
||||
}
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
auto& mesh = *lod.Meshes[meshIndex];
|
||||
if (mesh.MaterialSlotIndex < 0 || mesh.MaterialSlotIndex >= modelData.Materials.Count())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("MaterialSlotIndex"), TEXT("Incorrect material index used by the mesh."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Basic info
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Write(HeaderVersion);
|
||||
stream.Write(modelData.MinScreenSize);
|
||||
|
||||
// Materials
|
||||
stream.WriteInt32(modelData.Materials.Count());
|
||||
for (const auto& slot : modelData.Materials)
|
||||
{
|
||||
stream.Write(slot.AssetID);
|
||||
stream.Write((byte)slot.ShadowsMode);
|
||||
stream.Write(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveLOD(WriteStream& stream, int32 lodIndex) const
|
||||
{
|
||||
// Download all meshes buffers from the GPU
|
||||
Array<Task*> tasks;
|
||||
Array<const MeshBase*> meshes;
|
||||
GetMeshes(meshes, lodIndex);
|
||||
const int32 meshesCount = meshes.Count();
|
||||
struct MeshData
|
||||
{
|
||||
BytesContainer VB[3];
|
||||
BytesContainer IB;
|
||||
};
|
||||
Array<MeshData> meshesData;
|
||||
meshesData.Resize(meshesCount);
|
||||
tasks.EnsureCapacity(meshesCount * 4);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = *meshes[meshIndex];
|
||||
auto& meshData = meshesData[meshIndex];
|
||||
|
||||
// Vertex Buffer 0 (required)
|
||||
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB[0]);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Vertex Buffer 1 (optional)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB[1]);
|
||||
if (task)
|
||||
{
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Vertex Buffer 2 (optional)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB[2]);
|
||||
if (task)
|
||||
{
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Index Buffer (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Wait for async tasks
|
||||
if (Task::WaitAll(tasks))
|
||||
return true;
|
||||
|
||||
// Create meshes data
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
stream.Write(MeshVersion);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = *meshes[meshIndex];
|
||||
const auto& meshData = meshesData[meshIndex];
|
||||
uint32 vertices = mesh.GetVertexCount();
|
||||
uint32 triangles = mesh.GetTriangleCount();
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer();
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
uint32 ibSize = indicesCount * ibStride;
|
||||
|
||||
// Validate data
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
Array<const GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>> vbLayout;
|
||||
for (int32 vbIndex = 0; vbIndex < MODEL_MAX_VB; vbIndex++)
|
||||
{
|
||||
if (vbIndex != 0 && meshData.VB[vbIndex].IsInvalid()) // VB0 is always required
|
||||
continue;
|
||||
const GPUBuffer* vb = mesh.GetVertexBuffer(vbIndex);
|
||||
if (!vb)
|
||||
break;
|
||||
const GPUVertexLayout* layout = vb->GetVertexLayout();
|
||||
if (!layout)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer {}. Missing vertex layout.", vbIndex);
|
||||
return true;
|
||||
}
|
||||
const uint32 vbSize = vb->GetSize();
|
||||
vbLayout.Add(layout);
|
||||
if ((uint32)meshData.VB[vbIndex].Length() != vbSize)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer {} size. Got {} bytes but expected {} bytes. Stride: {}. Layout:\n{}", vbIndex, meshData.VB[vbIndex].Length(), vbSize, vb->GetStride(), layout->GetElementsString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((uint32)meshData.IB.Length() < ibSize)
|
||||
{
|
||||
LOG(Warning, "Invalid index buffer size. Got {} bytes bytes expected {} bytes. Stride: {}", meshData.IB.Length(), ibSize, ibStride);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write descriptor
|
||||
stream.Write(vertices);
|
||||
stream.Write(triangles);
|
||||
byte vbCount = (byte)vbLayout.Count();
|
||||
stream.Write(vbCount);
|
||||
for (const GPUVertexLayout* layout : vbLayout)
|
||||
stream.Write(layout->GetElements());
|
||||
|
||||
// Write actual mesh data
|
||||
for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++)
|
||||
{
|
||||
const auto& vb = meshData.VB[vbIndex];
|
||||
stream.WriteBytes(vb.Get(), vb.Length());
|
||||
}
|
||||
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
|
||||
{
|
||||
stream.WriteBytes(meshData.IB.Get(), ibSize);
|
||||
}
|
||||
else if (shouldUse16BitIndexBuffer)
|
||||
{
|
||||
// Convert 32-bit indices to 16-bit
|
||||
auto ib = (const int32*)meshData.IB.Get();
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
{
|
||||
stream.WriteUint16(ib[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CRASH;
|
||||
}
|
||||
|
||||
// Write custom data
|
||||
if (SaveMesh(stream, &mesh))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool saveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex))
|
||||
{
|
||||
// Create meshes data
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
stream.Write(MeshVersion);
|
||||
const auto& lod = modelData.LODs[lodIndex];
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
const auto& mesh = *lod.Meshes[meshIndex];
|
||||
uint32 vertices = (uint32)mesh.Positions.Count();
|
||||
uint32 indicesCount = (uint32)mesh.Indices.Count();
|
||||
uint32 triangles = indicesCount / 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const bool isSkinned = mesh.BlendIndices.HasItems() && mesh.BlendWeights.HasItems();
|
||||
|
||||
// Validate data
|
||||
if (vertices == 0 || triangles == 0 || indicesCount % 3 != 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
bool hasUVs = mesh.UVs.HasItems();
|
||||
if (hasUVs && (uint32)mesh.UVs.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("UVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasNormals = mesh.Normals.HasItems();
|
||||
if (hasNormals && (uint32)mesh.Normals.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
|
||||
return true;
|
||||
}
|
||||
bool hasTangents = mesh.Tangents.HasItems();
|
||||
if (hasTangents && (uint32)mesh.Tangents.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
|
||||
return true;
|
||||
}
|
||||
bool hasBitangentSigns = mesh.BitangentSigns.HasItems();
|
||||
if (hasBitangentSigns && (uint32)mesh.BitangentSigns.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
|
||||
return true;
|
||||
}
|
||||
bool hasLightmapUVs = mesh.LightmapUVs.HasItems();
|
||||
if (hasLightmapUVs && (uint32)mesh.LightmapUVs.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasColors = mesh.Colors.HasItems();
|
||||
if (hasColors && (uint32)mesh.Colors.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Colors"));
|
||||
return true;
|
||||
}
|
||||
if (isSkinned && (uint32)mesh.BlendIndices.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices"));
|
||||
return true;
|
||||
}
|
||||
if (isSkinned && (uint32)mesh.BlendWeights.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendWeights"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Define vertex buffers layout and packing
|
||||
Array<GPUVertexLayout::Elements, FixedAllocation<MODEL_MAX_VB>> vbElements;
|
||||
const bool useSeparatePositions = !isSkinned;
|
||||
const bool useSeparateColors = !isSkinned;
|
||||
{
|
||||
byte vbIndex = 0;
|
||||
// TODO: add option to quantize vertex positions (eg. 16-bit)
|
||||
// TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords)
|
||||
// TODO: add support for 16-bit blend indices (up to 65535 bones)
|
||||
|
||||
// Position
|
||||
if (useSeparatePositions)
|
||||
{
|
||||
auto& vb0 = vbElements.AddOne();
|
||||
vb0.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float });
|
||||
vbIndex++;
|
||||
}
|
||||
|
||||
// General purpose components
|
||||
{
|
||||
auto& vb = vbElements.AddOne();
|
||||
if (!useSeparatePositions)
|
||||
vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float });
|
||||
if (hasUVs)
|
||||
vb.Add({ VertexElement::Types::TexCoord, vbIndex, 0, 0, PixelFormat::R16G16_Float });
|
||||
if (hasLightmapUVs)
|
||||
vb.Add({ VertexElement::Types::TexCoord1, vbIndex, 0, 0, PixelFormat::R16G16_Float });
|
||||
vb.Add({ VertexElement::Types::Normal, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
vb.Add({ VertexElement::Types::Tangent, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
if (isSkinned)
|
||||
{
|
||||
vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UInt });
|
||||
vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, PixelFormat::R16G16B16A16_Float });
|
||||
}
|
||||
if (!useSeparateColors && hasColors)
|
||||
vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm });
|
||||
vbIndex++;
|
||||
}
|
||||
|
||||
// Colors
|
||||
if (useSeparateColors && hasColors)
|
||||
{
|
||||
auto& vb = vbElements.AddOne();
|
||||
vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm });
|
||||
vbIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Write descriptor
|
||||
stream.Write(vertices);
|
||||
stream.Write(triangles);
|
||||
byte vbCount = (byte)vbElements.Count();
|
||||
stream.Write(vbCount);
|
||||
for (const auto& elements : vbElements)
|
||||
stream.Write(elements);
|
||||
|
||||
// Write vertex buffers
|
||||
for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++)
|
||||
{
|
||||
if (useSeparatePositions && vbIndex == 0)
|
||||
{
|
||||
// Fast path for vertex positions of static models using the first buffer
|
||||
stream.WriteBytes(mesh.Positions.Get(), sizeof(Float3) * vertices);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Write vertex components interleaved
|
||||
const GPUVertexLayout::Elements& layout = vbElements[vbIndex];
|
||||
for (uint32 vertex = 0; vertex < vertices; vertex++)
|
||||
{
|
||||
for (const VertexElement& element : layout)
|
||||
{
|
||||
switch (element.Type)
|
||||
{
|
||||
case VertexElement::Types::Position:
|
||||
{
|
||||
const Float3 position = mesh.Positions.Get()[vertex];
|
||||
stream.Write(position);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Color:
|
||||
{
|
||||
const Color32 color(mesh.Colors.Get()[vertex]);
|
||||
stream.Write(color);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Normal:
|
||||
{
|
||||
const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ;
|
||||
const FloatR10G10B10A2 normalEnc(normal * 0.5f + 0.5f, 0);
|
||||
stream.Write(normalEnc.Value);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Tangent:
|
||||
{
|
||||
const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ;
|
||||
const Float3 tangent = hasTangents ? mesh.Tangents.Get()[vertex] : Float3::UnitX;
|
||||
const float bitangentSign = hasBitangentSigns ? mesh.BitangentSigns.Get()[vertex] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent);
|
||||
const Float1010102 tangentEnc(tangent * 0.5f + 0.5f, (byte)(bitangentSign < 0 ? 1 : 0));
|
||||
stream.Write(tangentEnc.Value);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::BlendIndices:
|
||||
{
|
||||
const Int4 blendIndices = mesh.BlendIndices.Get()[vertex];
|
||||
const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
|
||||
stream.Write(blendIndicesEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::BlendWeights:
|
||||
{
|
||||
const Float4 blendWeights = mesh.BlendWeights.Get()[vertex];
|
||||
const Half4 blendWeightsEnc(blendWeights);
|
||||
stream.Write(blendWeightsEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::TexCoord0:
|
||||
{
|
||||
const Float2 uv = hasUVs ? mesh.UVs.Get()[vertex] : Float2::Zero;
|
||||
const Half2 uvEnc(uv);
|
||||
stream.Write(uvEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::TexCoord1:
|
||||
{
|
||||
// TODO: refactor LightmapUVs into a generic TexCoord channel and support up to 4 UVs
|
||||
const Float2 lightmapUV = hasLightmapUVs ? mesh.LightmapUVs.Get()[vertex] : Float2::Zero;
|
||||
const Half2 lightmapUVEnc(lightmapUV);
|
||||
stream.Write(lightmapUVEnc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(Error, "Unsupported vertex element: {}", element.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write index buffer
|
||||
const uint32* meshIndices = mesh.Indices.Get();
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
stream.Write((uint16)meshIndices[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteBytes(meshIndices, sizeof(uint32) * indicesCount);
|
||||
}
|
||||
|
||||
// Write custom data
|
||||
if (saveMesh && saveMesh(stream, modelData, lodIndex, meshIndex))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveMesh(WriteStream& stream, const MeshBase* mesh) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void ModelBase::CancelStreaming()
|
||||
{
|
||||
BinaryAsset::CancelStreaming();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1)
|
||||
|
||||
class MeshBase;
|
||||
class StreamModelLODTask;
|
||||
struct RenderContextBatch;
|
||||
|
||||
/// <summary>
|
||||
@@ -28,9 +29,12 @@ API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset
|
||||
{
|
||||
DECLARE_ASSET_HEADER(ModelBase);
|
||||
friend MeshBase;
|
||||
friend class StreamModelLODTask;
|
||||
friend StreamModelLODTask;
|
||||
|
||||
protected:
|
||||
static constexpr byte HeaderVersion = 2;
|
||||
static constexpr byte MeshVersion = 2;
|
||||
|
||||
bool _initialized = false;
|
||||
int32 _loadedLODs = 0;
|
||||
StreamModelLODTask* _streamingTask = nullptr;
|
||||
@@ -174,12 +178,27 @@ public:
|
||||
/// <summary>
|
||||
/// Gets amount of the level of details in the model.
|
||||
/// </summary>
|
||||
virtual int32 GetLODsCount() const = 0;
|
||||
API_PROPERTY(Sealed) virtual int32 GetLODsCount() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mesh for a particular LOD index.
|
||||
/// </summary>
|
||||
virtual const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mesh for a particular LOD index.
|
||||
/// </summary>
|
||||
API_FUNCTION(Sealed) virtual MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the meshes for a particular LOD index.
|
||||
/// </summary>
|
||||
virtual void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) = 0;
|
||||
virtual void GetMeshes(Array<const MeshBase*>& meshes, int32 lodIndex = 0) const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the meshes for a particular LOD index.
|
||||
/// </summary>
|
||||
API_FUNCTION(Sealed) virtual void GetMeshes(API_PARAM(Out) Array<MeshBase*>& meshes, int32 lodIndex = 0) = 0;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -196,6 +215,38 @@ public:
|
||||
/// <param name="data">The data (it may be missing if failed to get it).</param>
|
||||
void GetLODData(int32 lodIndex, BytesContainer& data) const;
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
/// <remarks>If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread.</remarks>
|
||||
/// <param name="withMeshDataFromGpu">True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location, and it's loaded.</param>
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True when cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
friend class AssetExporters;
|
||||
friend class MeshAccessor;
|
||||
struct MeshData
|
||||
{
|
||||
uint32 Triangles, Vertices, IBStride;
|
||||
Array<const void*, FixedAllocation<MODEL_MAX_VB>> VBData;
|
||||
Array<class GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>> VBLayout;
|
||||
const void* IBData;
|
||||
};
|
||||
virtual bool LoadMesh(class MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly = nullptr);
|
||||
bool LoadHeader(ReadStream& stream, byte& headerVersion);
|
||||
#if USE_EDITOR
|
||||
virtual bool SaveHeader(WriteStream& stream);
|
||||
static bool SaveHeader(WriteStream& stream, const class ModelData& modelData);
|
||||
bool SaveLOD(WriteStream& stream, int32 lodIndex) const;
|
||||
static bool SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool(saveMesh)(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex) = nullptr);
|
||||
virtual bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const;
|
||||
virtual bool Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk);
|
||||
#endif
|
||||
|
||||
public:
|
||||
// [BinaryAsset]
|
||||
void CancelStreaming() override;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#endif
|
||||
|
||||
@@ -121,9 +122,9 @@ SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bo
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
#if !BUILD_RELEASE
|
||||
LOG(Error, "Missing asset {0} to use for skeleton mapping of {1}", retarget->SkeletonAsset, ToString());
|
||||
#endif
|
||||
#endif
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
@@ -231,7 +232,7 @@ FORCE_INLINE void SkinnedModelDraw(SkinnedModel* model, const RenderContext& ren
|
||||
if (!model->CanBeRendered())
|
||||
return;
|
||||
if (!info.Buffer->IsValidFor(model))
|
||||
info.Buffer->Setup(model);
|
||||
info.Buffer->Setup(model);
|
||||
const auto frame = Engine::FrameCount;
|
||||
const auto modelFrame = info.DrawState->PrevFrame + 1;
|
||||
|
||||
@@ -395,7 +396,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
||||
// Validate input
|
||||
if (nodes.Count() <= 0 || nodes.Count() > MAX_uint16)
|
||||
return true;
|
||||
if (bones.Count() <= 0 || bones.Count() > MAX_BONES_PER_MODEL)
|
||||
if (bones.Count() <= 0 || bones.Count() > MODEL_MAX_BONES_PER_MODEL)
|
||||
return true;
|
||||
auto model = this;
|
||||
if (!model->IsVirtual())
|
||||
@@ -431,310 +432,6 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
{
|
||||
// Validate state
|
||||
if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && path.IsEmpty())
|
||||
{
|
||||
LOG(Error, "To save virtual asset asset you need to specify the target asset path location.");
|
||||
return true;
|
||||
}
|
||||
if (withMeshDataFromGpu && IsInMainThread())
|
||||
{
|
||||
LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread).");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && !withMeshDataFromGpu)
|
||||
{
|
||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Create model data header
|
||||
MemoryWriteStream headerStream(1024);
|
||||
MemoryWriteStream* stream = &headerStream;
|
||||
{
|
||||
// Header Version
|
||||
stream->WriteByte(1);
|
||||
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
stream->WriteInt32(MaterialSlots.Count());
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < MaterialSlots.Count(); materialSlotIndex++)
|
||||
{
|
||||
auto& slot = MaterialSlots[materialSlotIndex];
|
||||
|
||||
const auto id = slot.Material.GetID();
|
||||
stream->Write(id);
|
||||
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
|
||||
stream->WriteString(slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
const int32 lods = LODs.Count();
|
||||
stream->WriteByte(lods);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream->WriteFloat(lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
stream->WriteUint16(meshes);
|
||||
|
||||
// For each mesh
|
||||
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
|
||||
// Material Slot index
|
||||
stream->WriteInt32(mesh.GetMaterialSlotIndex());
|
||||
|
||||
// Box
|
||||
const auto box = mesh.GetBox();
|
||||
stream->WriteBoundingBox(box);
|
||||
|
||||
// Sphere
|
||||
const auto sphere = mesh.GetSphere();
|
||||
stream->WriteBoundingSphere(sphere);
|
||||
|
||||
// Blend Shapes
|
||||
stream->WriteUint16(mesh.BlendShapes.Count());
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < mesh.BlendShapes.Count(); blendShapeIndex++)
|
||||
{
|
||||
auto& blendShape = mesh.BlendShapes[blendShapeIndex];
|
||||
stream->WriteString(blendShape.Name, 13);
|
||||
stream->WriteFloat(blendShape.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
stream->WriteInt32(Skeleton.Nodes.Count());
|
||||
|
||||
// For each node
|
||||
for (int32 nodeIndex = 0; nodeIndex < Skeleton.Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
auto& node = Skeleton.Nodes[nodeIndex];
|
||||
|
||||
stream->Write(node.ParentIndex);
|
||||
stream->WriteTransform(node.LocalTransform);
|
||||
stream->WriteString(node.Name, 71);
|
||||
}
|
||||
|
||||
stream->WriteInt32(Skeleton.Bones.Count());
|
||||
|
||||
// For each bone
|
||||
for (int32 boneIndex = 0; boneIndex < Skeleton.Bones.Count(); boneIndex++)
|
||||
{
|
||||
auto& bone = Skeleton.Bones[boneIndex];
|
||||
|
||||
stream->Write(bone.ParentIndex);
|
||||
stream->Write(bone.NodeIndex);
|
||||
stream->WriteTransform(bone.LocalTransform);
|
||||
stream->Write(bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
stream->WriteInt32(_skeletonRetargets.Count());
|
||||
for (const auto& retarget : _skeletonRetargets)
|
||||
{
|
||||
stream->Write(retarget.SourceAsset);
|
||||
stream->Write(retarget.SkeletonAsset);
|
||||
stream->Write(retarget.NodesMapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS];
|
||||
Platform::MemoryClear(tmpChunks, sizeof(tmpChunks));
|
||||
Array<FlaxChunk> chunks;
|
||||
if (IsVirtual())
|
||||
chunks.Resize(ASSET_FILE_DATA_CHUNKS);
|
||||
#define GET_CHUNK(index) (IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index))
|
||||
|
||||
// Check if use data from drive or from GPU
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Download all meshes buffers
|
||||
Array<Task*> tasks;
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
const int32 meshesCount = lod.Meshes.Count();
|
||||
struct MeshData
|
||||
{
|
||||
BytesContainer VB0;
|
||||
BytesContainer IB;
|
||||
|
||||
uint32 DataSize() const
|
||||
{
|
||||
return VB0.Length() + IB.Length();
|
||||
}
|
||||
};
|
||||
Array<MeshData> meshesData;
|
||||
meshesData.Resize(meshesCount);
|
||||
tasks.EnsureCapacity(meshesCount * 4);
|
||||
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
auto& meshData = meshesData[meshIndex];
|
||||
|
||||
// Vertex Buffer 0 (required)
|
||||
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Index Buffer (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
if (Task::WaitAll(tasks))
|
||||
return true;
|
||||
tasks.Clear();
|
||||
|
||||
// Create meshes data
|
||||
{
|
||||
int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool));
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
dataSize += meshesData[meshIndex].DataSize();
|
||||
MemoryWriteStream meshesStream(Math::RoundUpToPowerOf2(dataSize));
|
||||
|
||||
meshesStream.WriteByte(1);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
const auto& meshData = meshesData[meshIndex];
|
||||
|
||||
const uint32 vertices = mesh.GetVertexCount();
|
||||
const uint32 triangles = mesh.GetTriangleCount();
|
||||
const uint32 vb0Size = vertices * sizeof(VB0SkinnedElementType);
|
||||
const uint32 indicesCount = triangles * 3;
|
||||
const bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer();
|
||||
const uint32 ibSize = indicesCount * (use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32));
|
||||
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.VB0.Length() < vb0Size)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer 0 size.");
|
||||
return true;
|
||||
}
|
||||
if ((uint32)meshData.IB.Length() < ibSize)
|
||||
{
|
||||
LOG(Warning, "Invalid index buffer size.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
meshesStream.WriteUint32(vertices);
|
||||
meshesStream.WriteUint32(triangles);
|
||||
meshesStream.WriteUint16(mesh.BlendShapes.Count());
|
||||
for (const auto& blendShape : mesh.BlendShapes)
|
||||
{
|
||||
meshesStream.WriteBool(blendShape.UseNormals);
|
||||
meshesStream.WriteUint32(blendShape.MinVertexIndex);
|
||||
meshesStream.WriteUint32(blendShape.MaxVertexIndex);
|
||||
meshesStream.WriteUint32(blendShape.Vertices.Count());
|
||||
meshesStream.WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size);
|
||||
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
|
||||
{
|
||||
meshesStream.WriteBytes(meshData.IB.Get(), ibSize);
|
||||
}
|
||||
else if (shouldUse16BitIndexBuffer)
|
||||
{
|
||||
const auto ib = reinterpret_cast<const int32*>(meshData.IB.Get());
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
{
|
||||
meshesStream.WriteUint16(ib[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CRASH;
|
||||
}
|
||||
}
|
||||
|
||||
// Override meshes data chunk with the fetched GPU meshes memory
|
||||
auto lodChunk = GET_CHUNK(MODEL_LOD_TO_CHUNK_INDEX(lodIndex));
|
||||
if (lodChunk == nullptr)
|
||||
return true;
|
||||
lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(!IsVirtual());
|
||||
|
||||
// Load all chunks with a mesh data
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set mesh header data
|
||||
auto headerChunk = GET_CHUNK(0);
|
||||
ASSERT(headerChunk != nullptr);
|
||||
headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition());
|
||||
|
||||
#undef GET_CHUNK
|
||||
|
||||
// Save
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = SerializedVersion;
|
||||
if (IsVirtual())
|
||||
Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks));
|
||||
const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true);
|
||||
if (IsVirtual())
|
||||
Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks));
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Error, "Cannot save \'{0}\'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
||||
{
|
||||
if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS)
|
||||
@@ -777,6 +474,329 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
||||
return false;
|
||||
}
|
||||
|
||||
void BlendShape::LoadHeader(ReadStream& stream, byte headerVersion)
|
||||
{
|
||||
stream.Read(Name, 13);
|
||||
stream.Read(Weight);
|
||||
}
|
||||
|
||||
void BlendShape::Load(ReadStream& stream, byte meshVersion)
|
||||
{
|
||||
UseNormals = stream.ReadBool();
|
||||
stream.ReadUint32(&MinVertexIndex);
|
||||
stream.ReadUint32(&MaxVertexIndex);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
Vertices.Resize(blendShapeVertices);
|
||||
stream.ReadBytes(Vertices.Get(), Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void BlendShape::SaveHeader(WriteStream& stream) const
|
||||
{
|
||||
stream.Write(Name, 13);
|
||||
stream.Write(Weight);
|
||||
}
|
||||
|
||||
void BlendShape::Save(WriteStream& stream) const
|
||||
{
|
||||
stream.WriteBool(UseNormals);
|
||||
stream.WriteUint32(MinVertexIndex);
|
||||
stream.WriteUint32(MaxVertexIndex);
|
||||
stream.WriteUint32(Vertices.Count());
|
||||
stream.WriteBytes(Vertices.Get(), Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
|
||||
{
|
||||
if (ModelBase::LoadMesh(stream, meshVersion, mesh, dataIfReadOnly))
|
||||
return true;
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
auto skinnedMesh = (SkinnedMesh*)mesh;
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapesCount;
|
||||
stream.Read(blendShapesCount);
|
||||
if (dataIfReadOnly)
|
||||
{
|
||||
// Skip blend shapes
|
||||
BlendShape tmp;
|
||||
while (blendShapesCount-- != 0)
|
||||
tmp.Load(stream, meshVersion);
|
||||
return false;
|
||||
}
|
||||
if (blendShapesCount != skinnedMesh->BlendShapes.Count())
|
||||
{
|
||||
LOG(Warning, "Incorrect blend shapes amount: {} (expected: {})", blendShapesCount, skinnedMesh->BlendShapes.Count());
|
||||
return true;
|
||||
}
|
||||
for (auto& blendShape : skinnedMesh->BlendShapes)
|
||||
blendShape.Load(stream, meshVersion);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
{
|
||||
if (ModelBase::LoadHeader(stream, headerVersion))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
byte lods = stream.ReadByte();
|
||||
if (lods > MODEL_MAX_LODS)
|
||||
return true;
|
||||
LODs.Resize(lods);
|
||||
_initialized = true;
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
stream.Read(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
uint16 meshesCount;
|
||||
stream.Read(meshesCount);
|
||||
if (meshesCount > MODEL_MAX_MESHES)
|
||||
return true;
|
||||
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
|
||||
lod.Meshes.Resize(meshesCount, false);
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
SkinnedMesh& mesh = lod.Meshes[meshIndex];
|
||||
mesh.Link(this, lodIndex, meshIndex);
|
||||
|
||||
// Material Slot index
|
||||
int32 materialSlotIndex;
|
||||
stream.Read(materialSlotIndex);
|
||||
if (materialSlotIndex < 0 || materialSlotIndex >= MaterialSlots.Count())
|
||||
{
|
||||
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, MaterialSlots.Count());
|
||||
return true;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
mesh.SetBounds(box, sphere);
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapes;
|
||||
stream.Read(blendShapes);
|
||||
mesh.BlendShapes.Resize(blendShapes);
|
||||
for (auto& blendShape : mesh.BlendShapes)
|
||||
blendShape.LoadHeader(stream, headerVersion);
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
int32 nodesCount;
|
||||
stream.Read(nodesCount);
|
||||
if (nodesCount < 0)
|
||||
return true;
|
||||
Skeleton.Nodes.Resize(nodesCount, false);
|
||||
for (auto& node : Skeleton.Nodes)
|
||||
{
|
||||
stream.Read(node.ParentIndex);
|
||||
stream.Read(node.LocalTransform);
|
||||
stream.Read(node.Name, 71);
|
||||
}
|
||||
|
||||
int32 bonesCount;
|
||||
stream.Read(bonesCount);
|
||||
if (bonesCount < 0)
|
||||
return true;
|
||||
Skeleton.Bones.Resize(bonesCount, false);
|
||||
for (auto& bone : Skeleton.Bones)
|
||||
{
|
||||
stream.Read(bone.ParentIndex);
|
||||
stream.Read(bone.NodeIndex);
|
||||
stream.Read(bone.LocalTransform);
|
||||
stream.Read(bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
int32 entriesCount;
|
||||
stream.Read(entriesCount);
|
||||
_skeletonRetargets.Resize(entriesCount);
|
||||
for (auto& retarget : _skeletonRetargets)
|
||||
{
|
||||
stream.Read(retarget.SourceAsset);
|
||||
stream.Read(retarget.SkeletonAsset);
|
||||
stream.Read(retarget.NodesMapping);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool SkinnedModel::SaveHeader(WriteStream& stream)
|
||||
{
|
||||
if (ModelBase::SaveHeader(stream))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
stream.Write((byte)LODs.Count());
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
stream.Write(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
stream.Write((uint16)lod.Meshes.Count());
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
stream.Write(mesh.GetMaterialSlotIndex());
|
||||
stream.Write(mesh.GetBox());
|
||||
stream.Write(mesh.GetSphere());
|
||||
|
||||
// Blend Shapes
|
||||
const int32 blendShapes = mesh.BlendShapes.Count();
|
||||
stream.Write((uint16)blendShapes);
|
||||
for (const auto& blendShape : mesh.BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton nodes
|
||||
const auto& skeletonNodes = Skeleton.Nodes;
|
||||
stream.Write(skeletonNodes.Count());
|
||||
for (const auto& node : skeletonNodes)
|
||||
{
|
||||
stream.Write(node.ParentIndex);
|
||||
stream.Write(node.LocalTransform);
|
||||
stream.Write(node.Name, 71);
|
||||
}
|
||||
|
||||
// Skeleton bones
|
||||
const auto& skeletonBones = Skeleton.Bones;
|
||||
stream.WriteInt32(skeletonBones.Count());
|
||||
for (const auto& bone : skeletonBones)
|
||||
{
|
||||
stream.Write(bone.ParentIndex);
|
||||
stream.Write(bone.NodeIndex);
|
||||
stream.Write(bone.LocalTransform);
|
||||
stream.Write(bone.OffsetMatrix);
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
stream.WriteInt32(_skeletonRetargets.Count());
|
||||
for (const auto& retarget : _skeletonRetargets)
|
||||
{
|
||||
stream.Write(retarget.SourceAsset);
|
||||
stream.Write(retarget.SkeletonAsset);
|
||||
stream.Write(retarget.NodesMapping);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkinnedModel::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
{
|
||||
if (ModelBase::SaveHeader(stream, modelData))
|
||||
return true;
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
|
||||
// LODs
|
||||
stream.Write((byte)modelData.LODs.Count());
|
||||
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = modelData.LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream.Write(lod.ScreenSize);
|
||||
|
||||
// Meshes
|
||||
stream.Write((uint16)lod.Meshes.Count());
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
BoundingBox box;
|
||||
BoundingSphere sphere;
|
||||
mesh->CalculateBounds(box, sphere);
|
||||
stream.Write(mesh->MaterialSlotIndex);
|
||||
stream.Write(box);
|
||||
stream.Write(sphere);
|
||||
|
||||
// Blend Shapes
|
||||
const int32 blendShapes = mesh->BlendShapes.Count();
|
||||
stream.WriteUint16((uint16)blendShapes);
|
||||
for (const auto& blendShape : mesh->BlendShapes)
|
||||
blendShape.SaveHeader(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton nodes
|
||||
const auto& skeletonNodes = modelData.Skeleton.Nodes;
|
||||
stream.WriteInt32(skeletonNodes.Count());
|
||||
for (const auto& node : skeletonNodes)
|
||||
{
|
||||
stream.Write(node.ParentIndex);
|
||||
stream.Write(node.LocalTransform);
|
||||
stream.Write(node.Name, 71);
|
||||
}
|
||||
|
||||
// Skeleton bones
|
||||
const auto& skeletonBones = modelData.Skeleton.Bones;
|
||||
stream.WriteInt32(skeletonBones.Count());
|
||||
for (const auto& bone : skeletonBones)
|
||||
{
|
||||
stream.Write(bone.ParentIndex);
|
||||
stream.Write(bone.NodeIndex);
|
||||
stream.Write(bone.LocalTransform);
|
||||
stream.Write(bone.OffsetMatrix);
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
stream.Write(0); // Empty list
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkinnedModel::SaveMesh(WriteStream& stream, const MeshBase* mesh) const
|
||||
{
|
||||
if (ModelBase::SaveMesh(stream, mesh))
|
||||
return true;
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
auto skinnedMesh = (const SkinnedMesh*)mesh;
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapesCount = skinnedMesh->BlendShapes.Count();
|
||||
stream.Write(blendShapesCount);
|
||||
for (auto& blendShape : skinnedMesh->BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SkinnedModel::SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex)
|
||||
{
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
auto& mesh = modelData.LODs[lodIndex].Meshes[meshIndex];
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapesCount = (uint16)mesh->BlendShapes.Count();
|
||||
stream.Write(blendShapesCount);
|
||||
for (auto& blendShape : mesh->BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void SkinnedModel::ClearSkeletonMapping()
|
||||
{
|
||||
for (auto& e : _skeletonMappingCache)
|
||||
@@ -841,6 +861,26 @@ int32 SkinnedModel::GetLODsCount() const
|
||||
return LODs.Count();
|
||||
}
|
||||
|
||||
const MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex) const
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
return &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
return &lod.Meshes[meshIndex];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
void SkinnedModel::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
@@ -889,145 +929,11 @@ Asset::LoadResult SkinnedModel::load()
|
||||
if (chunk0 == nullptr || chunk0->IsMissing())
|
||||
return LoadResult::MissingDataChunk;
|
||||
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
||||
ReadStream* stream = &headerStream;
|
||||
|
||||
// Header Version
|
||||
byte version = stream->ReadByte();
|
||||
|
||||
// Min Screen Size
|
||||
stream->ReadFloat(&MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
int32 materialSlotsCount;
|
||||
stream->ReadInt32(&materialSlotsCount);
|
||||
if (materialSlotsCount < 0 || materialSlotsCount > 4096)
|
||||
// Load asset data (anything but mesh contents that use streaming)
|
||||
byte headerVersion;
|
||||
if (LoadHeader(headerStream, headerVersion))
|
||||
return LoadResult::InvalidData;
|
||||
MaterialSlots.Resize(materialSlotsCount, false);
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
auto& slot = MaterialSlots[materialSlotIndex];
|
||||
|
||||
// Material
|
||||
Guid materialId;
|
||||
stream->Read(materialId);
|
||||
slot.Material = materialId;
|
||||
|
||||
// Shadows Mode
|
||||
slot.ShadowsMode = static_cast<ShadowsCastingMode>(stream->ReadByte());
|
||||
|
||||
// Name
|
||||
stream->ReadString(&slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
byte lods;
|
||||
stream->ReadByte(&lods);
|
||||
if (lods > MODEL_MAX_LODS)
|
||||
return LoadResult::InvalidData;
|
||||
LODs.Resize(lods);
|
||||
_initialized = true;
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
lod._model = this;
|
||||
lod._lodIndex = lodIndex;
|
||||
|
||||
// Screen Size
|
||||
stream->ReadFloat(&lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream->ReadUint16(&meshesCount);
|
||||
if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES)
|
||||
return LoadResult::InvalidData;
|
||||
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
|
||||
|
||||
// Allocate memory
|
||||
lod.Meshes.Resize(meshesCount, false);
|
||||
|
||||
// For each mesh
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
SkinnedMesh& mesh = lod.Meshes[meshIndex];
|
||||
mesh.Link(this, lodIndex, meshIndex);
|
||||
|
||||
// Material Slot index
|
||||
int32 materialSlotIndex;
|
||||
stream->ReadInt32(&materialSlotIndex);
|
||||
if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount)
|
||||
{
|
||||
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount);
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
mesh.SetMaterialSlotIndex(materialSlotIndex);
|
||||
|
||||
// Bounds
|
||||
BoundingBox box;
|
||||
stream->ReadBoundingBox(&box);
|
||||
BoundingSphere sphere;
|
||||
stream->ReadBoundingSphere(&sphere);
|
||||
mesh.SetBounds(box, sphere);
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapes;
|
||||
stream->ReadUint16(&blendShapes);
|
||||
mesh.BlendShapes.Resize(blendShapes);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++)
|
||||
{
|
||||
auto& blendShape = mesh.BlendShapes[blendShapeIndex];
|
||||
stream->ReadString(&blendShape.Name, 13);
|
||||
stream->ReadFloat(&blendShape.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
int32 nodesCount;
|
||||
stream->ReadInt32(&nodesCount);
|
||||
if (nodesCount <= 0)
|
||||
return LoadResult::InvalidData;
|
||||
Skeleton.Nodes.Resize(nodesCount, false);
|
||||
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
|
||||
{
|
||||
auto& node = Skeleton.Nodes.Get()[nodeIndex];
|
||||
stream->Read(node.ParentIndex);
|
||||
stream->ReadTransform(&node.LocalTransform);
|
||||
stream->ReadString(&node.Name, 71);
|
||||
}
|
||||
|
||||
int32 bonesCount;
|
||||
stream->ReadInt32(&bonesCount);
|
||||
if (bonesCount <= 0)
|
||||
return LoadResult::InvalidData;
|
||||
Skeleton.Bones.Resize(bonesCount, false);
|
||||
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
|
||||
{
|
||||
auto& bone = Skeleton.Bones.Get()[boneIndex];
|
||||
stream->Read(bone.ParentIndex);
|
||||
stream->Read(bone.NodeIndex);
|
||||
stream->ReadTransform(&bone.LocalTransform);
|
||||
stream->Read(bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
int32 entriesCount;
|
||||
stream->ReadInt32(&entriesCount);
|
||||
_skeletonRetargets.Resize(entriesCount);
|
||||
for (int32 entryIndex = 0; entryIndex < entriesCount; entryIndex++)
|
||||
{
|
||||
auto& retarget = _skeletonRetargets[entryIndex];
|
||||
stream->Read(retarget.SourceAsset);
|
||||
stream->Read(retarget.SkeletonAsset);
|
||||
stream->Read(retarget.NodesMapping);
|
||||
}
|
||||
}
|
||||
|
||||
// Request resource streaming
|
||||
StartStreaming(true);
|
||||
|
||||
@@ -139,7 +139,7 @@ public:
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5);
|
||||
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 30);
|
||||
friend SkinnedMesh;
|
||||
public:
|
||||
// Skeleton mapping descriptor.
|
||||
@@ -331,17 +331,6 @@ public:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<SkeletonBone>& bones, bool autoCalculateOffsetMatrix);
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
/// <remarks>If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread.</remarks>
|
||||
/// <param name="withMeshDataFromGpu">True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location and it's loaded.</param>
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True if cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
|
||||
#endif
|
||||
|
||||
private:
|
||||
/// <summary>
|
||||
/// Initializes this skinned model to an empty collection of meshes. Ensure to init SkeletonData manually after the call.
|
||||
@@ -350,6 +339,17 @@ private:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool Init(const Span<int32>& meshesCountPerLod);
|
||||
|
||||
// [ModelBase]
|
||||
bool LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) override;
|
||||
bool LoadHeader(ReadStream& stream, byte& headerVersion);
|
||||
#if USE_EDITOR
|
||||
friend class ImportModel;
|
||||
bool SaveHeader(WriteStream& stream) override;
|
||||
static bool SaveHeader(WriteStream& stream, const ModelData& modelData);
|
||||
bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const override;
|
||||
static bool SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex);
|
||||
#endif
|
||||
|
||||
void ClearSkeletonMapping();
|
||||
void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
|
||||
|
||||
@@ -384,6 +384,9 @@ public:
|
||||
uint64 GetMemoryUsage() const override;
|
||||
void SetupMaterialSlots(int32 slotsCount) override;
|
||||
int32 GetLODsCount() const 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;
|
||||
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||
void InitAsVirtual() override;
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "BinaryAssetUpgrader.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
|
||||
/// <summary>
|
||||
/// Model Asset Upgrader
|
||||
@@ -13,17 +16,145 @@
|
||||
class ModelAssetUpgrader : public BinaryAssetUpgrader
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelAssetUpgrader"/> class.
|
||||
/// </summary>
|
||||
ModelAssetUpgrader()
|
||||
{
|
||||
static const Upgrader upgraders[] =
|
||||
Upgrader upgraders[] =
|
||||
{
|
||||
{},
|
||||
{ 25, 30, Upgrade_25_To_30 }, // [Deprecated in v1.10]
|
||||
};
|
||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
||||
}
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
static bool Upgrade_25_To_30(AssetMigrationContext& context)
|
||||
{
|
||||
// [Deprecated in v1.10]
|
||||
ASSERT(context.Input.SerializedVersion == 25 && context.Output.SerializedVersion == 30);
|
||||
MemoryWriteStream output;
|
||||
|
||||
// Upgrade header
|
||||
byte lodsCount;
|
||||
Array<int32, FixedAllocation<6>> meshesCountPerLod;
|
||||
{
|
||||
if (context.AllocateChunk(0))
|
||||
return true;
|
||||
auto& data = context.Input.Header.Chunks[0]->Data;
|
||||
MemoryReadStream stream(data.Get(), data.Length());
|
||||
|
||||
constexpr byte headerVersion = 2;
|
||||
output.Write(headerVersion);
|
||||
|
||||
float minScreenSize;
|
||||
stream.Read(minScreenSize);
|
||||
output.Write(minScreenSize);
|
||||
|
||||
// Materials
|
||||
int32 materialSlotsCount;
|
||||
stream.Read(materialSlotsCount);
|
||||
output.Write(materialSlotsCount);
|
||||
CHECK_RETURN(materialSlotsCount >= 0 && materialSlotsCount < 4096, true);
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
Guid materialId;
|
||||
stream.Read(materialId);
|
||||
output.Write(materialId);
|
||||
byte shadowsCastingMode;
|
||||
stream.Read(shadowsCastingMode);
|
||||
output.Write(shadowsCastingMode);
|
||||
String name;
|
||||
stream.Read(name, 11);
|
||||
output.Write(name, 11);
|
||||
}
|
||||
|
||||
// LODs
|
||||
stream.Read(lodsCount);
|
||||
output.Write(lodsCount);
|
||||
CHECK_RETURN(lodsCount <= 6, true);
|
||||
meshesCountPerLod.Resize(lodsCount);
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
float screenSize;
|
||||
stream.Read(screenSize);
|
||||
output.Write(screenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream.Read(meshesCount);
|
||||
output.Write(meshesCount);
|
||||
CHECK_RETURN(meshesCount > 0 && meshesCount < 4096, true);
|
||||
meshesCountPerLod[lodIndex] = meshesCount;
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
int32 materialSlotIndex;
|
||||
stream.Read(materialSlotIndex);
|
||||
output.Write(materialSlotIndex);
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
output.Write(box);
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
output.Write(sphere);
|
||||
bool hasLightmapUVs = stream.ReadBool();
|
||||
int8 lightmapUVsIndex = hasLightmapUVs ? 1 : -1;
|
||||
output.Write(lightmapUVsIndex);
|
||||
}
|
||||
}
|
||||
|
||||
context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
// Upgrade meshes data
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
output.SetPosition(0);
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
const FlaxChunk* lodData = context.Input.Header.Chunks[chunkIndex];
|
||||
MemoryReadStream stream(lodData->Get(), lodData->Size());
|
||||
|
||||
constexpr byte meshVersion = 2;
|
||||
output.Write(meshVersion);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCountPerLod[lodIndex]; meshIndex++)
|
||||
{
|
||||
// Descriptor
|
||||
uint32 vertices, triangles;
|
||||
stream.Read(vertices);
|
||||
stream.Read(triangles);
|
||||
output.Write(vertices);
|
||||
output.Write(triangles);
|
||||
auto vb0 = stream.Move<VB0ElementType18>(vertices);
|
||||
auto vb1 = stream.Move<VB1ElementType18>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
auto ibData = stream.Move(indicesCount * ibStride);
|
||||
byte vbCount = hasColors ? 3 : 2;
|
||||
output.Write(vbCount);
|
||||
output.Write(VB0ElementType18::GetLayout()->GetElements());
|
||||
output.Write(VB1ElementType18::GetLayout()->GetElements());
|
||||
if (hasColors)
|
||||
output.Write(VB2ElementType18::GetLayout()->GetElements());
|
||||
|
||||
// Buffers
|
||||
output.WriteBytes(vb0, vertices * sizeof(VB0ElementType18));
|
||||
output.WriteBytes(vb1, vertices * sizeof(VB1ElementType18));
|
||||
if (hasColors)
|
||||
output.WriteBytes(vb2, vertices * sizeof(VB2ElementType18));
|
||||
output.WriteBytes(ibData, indicesCount * ibStride);
|
||||
}
|
||||
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return true;
|
||||
context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
// Copy SDF data
|
||||
return CopyChunk(context, 15);
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "BinaryAssetUpgrader.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Models/BlendShape.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
|
||||
/// <summary>
|
||||
/// Skinned Model Asset Upgrader
|
||||
@@ -13,17 +17,218 @@
|
||||
class SkinnedModelAssetUpgrader : public BinaryAssetUpgrader
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkinnedModelAssetUpgrader"/> class.
|
||||
/// </summary>
|
||||
SkinnedModelAssetUpgrader()
|
||||
{
|
||||
static const Upgrader upgraders[] =
|
||||
const Upgrader upgraders[] =
|
||||
{
|
||||
{},
|
||||
{ 5, 30, Upgrade_5_To_30 }, // [Deprecated in v1.10]
|
||||
};
|
||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
||||
}
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
static bool Upgrade_5_To_30(AssetMigrationContext& context)
|
||||
{
|
||||
// [Deprecated in v1.10]
|
||||
ASSERT(context.Input.SerializedVersion == 5 && context.Output.SerializedVersion == 30);
|
||||
MemoryWriteStream output;
|
||||
|
||||
// Upgrade header
|
||||
byte lodsCount;
|
||||
Array<int32, FixedAllocation<6>> meshesCountPerLod;
|
||||
{
|
||||
if (context.AllocateChunk(0))
|
||||
return true;
|
||||
auto& data = context.Input.Header.Chunks[0]->Data;
|
||||
MemoryReadStream stream(data.Get(), data.Length());
|
||||
|
||||
constexpr byte headerVersion = 2;
|
||||
byte headerVersionOld;
|
||||
stream.Read(headerVersionOld);
|
||||
CHECK_RETURN(headerVersionOld == 1, true);
|
||||
output.Write(headerVersion);
|
||||
|
||||
float minScreenSize;
|
||||
stream.Read(minScreenSize);
|
||||
output.Write(minScreenSize);
|
||||
|
||||
// Materials
|
||||
int32 materialSlotsCount;
|
||||
stream.Read(materialSlotsCount);
|
||||
output.Write(materialSlotsCount);
|
||||
CHECK_RETURN(materialSlotsCount >= 0 && materialSlotsCount < 4096, true);
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
Guid materialId;
|
||||
stream.Read(materialId);
|
||||
output.Write(materialId);
|
||||
byte shadowsCastingMode;
|
||||
stream.Read(shadowsCastingMode);
|
||||
output.Write(shadowsCastingMode);
|
||||
String name;
|
||||
stream.Read(name, 11);
|
||||
output.Write(name, 11);
|
||||
}
|
||||
|
||||
// LODs
|
||||
stream.Read(lodsCount);
|
||||
output.Write(lodsCount);
|
||||
CHECK_RETURN(materialSlotsCount <= 6, true);
|
||||
meshesCountPerLod.Resize(lodsCount);
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
float screenSize;
|
||||
stream.Read(screenSize);
|
||||
output.Write(screenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream.Read(meshesCount);
|
||||
output.Write(meshesCount);
|
||||
CHECK_RETURN(meshesCount > 0 && meshesCount < 4096, true);
|
||||
meshesCountPerLod[lodIndex] = meshesCount;
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
int32 materialSlotIndex;
|
||||
stream.Read(materialSlotIndex);
|
||||
output.Write(materialSlotIndex);
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
output.Write(box);
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
output.Write(sphere);
|
||||
uint16 blendShapesCount;
|
||||
stream.Read(blendShapesCount);
|
||||
output.Write(blendShapesCount);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
|
||||
{
|
||||
String name;
|
||||
stream.Read(name, 13);
|
||||
output.Write(name, 13);
|
||||
float weight;
|
||||
stream.Read(weight);
|
||||
output.Write(weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
int32 nodesCount;
|
||||
stream.Read(nodesCount);
|
||||
output.Write(nodesCount);
|
||||
if (nodesCount < 0)
|
||||
return true;
|
||||
for (int32 i = 0 ; i < nodesCount; i++)
|
||||
{
|
||||
int32 parentIndex;
|
||||
Transform localTransform;
|
||||
String name;
|
||||
stream.Read(parentIndex);
|
||||
output.Write(parentIndex);
|
||||
stream.Read(localTransform);
|
||||
output.Write(localTransform);
|
||||
stream.Read(name, 71);
|
||||
output.Write(name, 71);
|
||||
}
|
||||
|
||||
int32 bonesCount;
|
||||
stream.Read(bonesCount);
|
||||
output.Write(bonesCount);
|
||||
if (bonesCount < 0)
|
||||
return true;
|
||||
for (int32 i = 0 ; i < bonesCount; i++)
|
||||
{
|
||||
int32 parentIndex, nodeIndex;
|
||||
Transform localTransform;
|
||||
String name;
|
||||
Matrix offsetMatrix;
|
||||
stream.Read(parentIndex);
|
||||
output.Write(parentIndex);
|
||||
stream.Read(nodeIndex);
|
||||
output.Write(nodeIndex);
|
||||
stream.Read(localTransform);
|
||||
output.Write(localTransform);
|
||||
stream.Read(offsetMatrix);
|
||||
output.Write(offsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
int32 entriesCount;
|
||||
stream.Read(entriesCount);
|
||||
output.Write(entriesCount);
|
||||
for (int32 i = 0 ; i < entriesCount; i++)
|
||||
{
|
||||
Guid sourceAsset, skeletonAsset;
|
||||
Dictionary<String, String> nodesMapping;
|
||||
stream.Read(sourceAsset);
|
||||
output.Write(sourceAsset);
|
||||
stream.Read(skeletonAsset);
|
||||
output.Write(skeletonAsset);
|
||||
stream.Read(nodesMapping);
|
||||
output.Write(nodesMapping);
|
||||
}
|
||||
}
|
||||
|
||||
context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
// Upgrade meshes data
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
output.SetPosition(0);
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
const FlaxChunk* lodData = context.Input.Header.Chunks[chunkIndex];
|
||||
MemoryReadStream stream(lodData->Get(), lodData->Size());
|
||||
|
||||
constexpr byte meshVersion = 2;
|
||||
byte headerVersionOld;
|
||||
stream.Read(headerVersionOld);
|
||||
CHECK_RETURN(headerVersionOld == 1, true);
|
||||
output.Write(meshVersion);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCountPerLod[lodIndex]; meshIndex++)
|
||||
{
|
||||
// Descriptor
|
||||
uint32 vertices, triangles;
|
||||
stream.Read(vertices);
|
||||
stream.Read(triangles);
|
||||
output.Write(vertices);
|
||||
output.Write(triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.Read(blendShapesCount);
|
||||
Array<BlendShape> blendShapes;
|
||||
blendShapes.Resize(blendShapesCount);
|
||||
for (auto& blendShape : blendShapes)
|
||||
blendShape.Load(stream, meshVersion);
|
||||
auto vb0 = stream.Move<VB0SkinnedElementType2>(vertices);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
auto ibData = stream.Move(indicesCount * ibStride);
|
||||
output.Write((byte)1);
|
||||
output.Write(VB0SkinnedElementType2::GetLayout()->GetElements());
|
||||
|
||||
// Buffers
|
||||
output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType2));
|
||||
output.WriteBytes(ibData, indicesCount * ibStride);
|
||||
|
||||
// Blend Shapes
|
||||
output.Write(blendShapesCount);
|
||||
for (auto& blendShape : blendShapes)
|
||||
blendShape.Save(output);
|
||||
}
|
||||
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return true;
|
||||
context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
#include "Engine/Serialization/FileWriteStream.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Graphics/Models/MeshAccessor.h"
|
||||
#include "Engine/Core/DeleteMe.h"
|
||||
|
||||
ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
{
|
||||
// Prepare
|
||||
auto asset = (Model*)context.Asset.Get();
|
||||
auto asset = (ModelBase*)context.Asset.Get();
|
||||
auto lock = asset->Storage->LockSafe();
|
||||
auto path = GET_OUTPUT_PATH(context, "obj");
|
||||
const int32 lodIndex = 0;
|
||||
@@ -26,7 +26,6 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
const auto chunk = asset->GetChunk(chunkIndex);
|
||||
if (!chunk)
|
||||
return ExportAssetResult::CannotLoadData;
|
||||
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
FileWriteStream* output = FileWriteStream::Open(path);
|
||||
if (output == nullptr)
|
||||
@@ -37,62 +36,61 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get()));
|
||||
|
||||
// Extract all meshes
|
||||
const auto& lod = asset->LODs[lodIndex];
|
||||
int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
if (asset->GetLODsCount() <= lodIndex)
|
||||
return ExportAssetResult::Error;
|
||||
Array<MeshBase*> meshes;
|
||||
asset->GetMeshes(meshes, lodIndex);
|
||||
uint32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
|
||||
ModelBase::MeshData meshData;
|
||||
byte meshVersion = stream.ReadByte();
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
auto& mesh = lod.Meshes[meshIndex];
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
auto mesh = meshes[meshIndex];
|
||||
if (asset->LoadMesh(stream, meshVersion, mesh, &meshData))
|
||||
return ExportAssetResult::CannotLoadData;
|
||||
if (meshData.Vertices == 0 || meshData.Triangles == 0)
|
||||
return ExportAssetResult::Error;
|
||||
auto vb0 = stream.Move<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Move<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
|
||||
MeshAccessor accessor;
|
||||
if (accessor.LoadFromMeshData(&meshData))
|
||||
return ExportAssetResult::CannotLoadAsset;
|
||||
output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex));
|
||||
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
auto positionStream = accessor.Position();
|
||||
if (!positionStream.IsValid())
|
||||
return ExportAssetResult::Error;
|
||||
for (uint32 i = 0; i < meshData.Vertices; i++)
|
||||
{
|
||||
auto v = vb0[i].Position;
|
||||
auto v = positionStream.GetFloat3(i);
|
||||
output->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
auto texCoordStream = accessor.TexCoord();
|
||||
if (texCoordStream.IsValid())
|
||||
{
|
||||
auto v = vb1[i].TexCoord.ToFloat2();
|
||||
output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y));
|
||||
for (uint32 i = 0; i < meshData.Vertices; i++)
|
||||
{
|
||||
auto v = texCoordStream.GetFloat2(i);
|
||||
output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y));
|
||||
}
|
||||
output->WriteChar('\n');
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
auto normalStream = accessor.Normal();
|
||||
if (normalStream.IsValid())
|
||||
{
|
||||
auto v = vb1[i].Normal.ToFloat3() * 2.0f - 1.0f;
|
||||
output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
for (uint32 i = 0; i < meshData.Vertices; i++)
|
||||
{
|
||||
auto v = normalStream.GetFloat3(i) * 2.0f - 1.0f;
|
||||
output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
}
|
||||
output->WriteChar('\n');
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
|
||||
if (use16BitIndexBuffer)
|
||||
if (meshData.IBStride == sizeof(uint16))
|
||||
{
|
||||
auto t = (uint16*)ib;
|
||||
for (uint32 i = 0; i < triangles; i++)
|
||||
auto t = (const uint16*)meshData.IBData;
|
||||
for (uint32 i = 0; i < meshData.Triangles; i++)
|
||||
{
|
||||
auto i0 = vertexStart + *t++;
|
||||
auto i1 = vertexStart + *t++;
|
||||
@@ -102,8 +100,8 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
}
|
||||
else
|
||||
{
|
||||
auto t = (uint32*)ib;
|
||||
for (uint32 i = 0; i < triangles; i++)
|
||||
auto t = (const uint32*)meshData.IBData;
|
||||
for (uint32 i = 0; i < meshData.Triangles; i++)
|
||||
{
|
||||
auto i0 = vertexStart + *t++;
|
||||
auto i1 = vertexStart + *t++;
|
||||
@@ -111,10 +109,9 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2));
|
||||
}
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
|
||||
vertexStart += vertices;
|
||||
vertexStart += meshData.Vertices;
|
||||
}
|
||||
|
||||
if (output->HasError())
|
||||
@@ -125,120 +122,8 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
|
||||
ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context)
|
||||
{
|
||||
// Prepare
|
||||
auto asset = (SkinnedModel*)context.Asset.Get();
|
||||
auto lock = asset->Storage->LockSafe();
|
||||
auto path = GET_OUTPUT_PATH(context, "obj");
|
||||
const int32 lodIndex = 0;
|
||||
|
||||
// Fetch chunk with data
|
||||
const auto chunkIndex = 1;
|
||||
if (asset->LoadChunk(chunkIndex))
|
||||
return ExportAssetResult::CannotLoadData;
|
||||
const auto chunk = asset->GetChunk(chunkIndex);
|
||||
if (!chunk)
|
||||
return ExportAssetResult::CannotLoadData;
|
||||
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
FileWriteStream* output = FileWriteStream::Open(path);
|
||||
if (output == nullptr)
|
||||
return ExportAssetResult::Error;
|
||||
DeleteMe<FileWriteStream> outputDeleteMe(output);
|
||||
|
||||
const auto name = StringUtils::GetFileNameWithoutExtension(asset->GetPath()).ToStringAnsi();
|
||||
output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get()));
|
||||
|
||||
// Extract all meshes
|
||||
const auto& lod = asset->LODs[lodIndex];
|
||||
int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
|
||||
byte version = stream.ReadByte();
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
auto& mesh = lod.Meshes[meshIndex];
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.ReadUint16(&blendShapesCount);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
|
||||
{
|
||||
stream.ReadBool();
|
||||
uint32 tmp;
|
||||
stream.ReadUint32(&tmp);
|
||||
stream.ReadUint32(&tmp);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
stream.Move(blendShapeVertices * sizeof(BlendShapeVertex));
|
||||
}
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return ExportAssetResult::Error;
|
||||
auto vb0 = stream.Move<VB0SkinnedElementType>(vertices);
|
||||
auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
|
||||
output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex));
|
||||
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb0[i].Position;
|
||||
output->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb0[i].TexCoord.ToFloat2();
|
||||
output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb0[i].Normal.ToFloat3() * 2.0f - 1.0f;
|
||||
output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
auto t = (uint16*)ib;
|
||||
for (uint32 i = 0; i < triangles; i++)
|
||||
{
|
||||
auto i0 = vertexStart + *t++;
|
||||
auto i1 = vertexStart + *t++;
|
||||
auto i2 = vertexStart + *t++;
|
||||
output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto t = (uint32*)ib;
|
||||
for (uint32 i = 0; i < triangles; i++)
|
||||
{
|
||||
auto i0 = vertexStart + *t++;
|
||||
auto i1 = vertexStart + *t++;
|
||||
auto i2 = vertexStart + *t++;
|
||||
output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2));
|
||||
}
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
|
||||
vertexStart += vertices;
|
||||
}
|
||||
|
||||
if (output->HasError())
|
||||
return ExportAssetResult::Error;
|
||||
|
||||
return ExportAssetResult::Ok;
|
||||
// The same code, except SkinnedModel::LoadMesh will be used to read Blend Shapes data
|
||||
return ExportModel(context);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -132,7 +132,6 @@ void RepackMeshLightmapUVs(ModelData& data)
|
||||
{
|
||||
Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv);
|
||||
Float2 uvScale(entry.Slot->Width * atlasSizeInv, entry.Slot->Height * atlasSizeInv);
|
||||
// TODO: SIMD
|
||||
for (auto& uv : entry.Mesh->LightmapUVs)
|
||||
{
|
||||
uv = uv * uvScale + uvOffset;
|
||||
@@ -540,37 +539,28 @@ CreateAssetResult ImportModel::Create(CreateAssetContext& context)
|
||||
return CreateModel(context, modelData);
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const ModelData& modelData, const Options* options)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
IMPORT_SETUP(Model, Model::SerializedVersion);
|
||||
static_assert(Model::SerializedVersion == 30, "Update code.");
|
||||
|
||||
// Save model header
|
||||
MemoryWriteStream stream(4096);
|
||||
if (modelData.Pack2ModelHeader(&stream))
|
||||
if (Model::SaveHeader(stream, modelData))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
|
||||
// Pack model LODs data
|
||||
const auto lodCount = modelData.GetLODsCount();
|
||||
const auto lodCount = modelData.LODs.Count();
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
|
||||
// Pack meshes
|
||||
auto& meshes = modelData.LODs[lodIndex].Meshes;
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
if (meshes[meshIndex]->Pack2Model(&stream))
|
||||
{
|
||||
LOG(Warning, "Cannot pack mesh.");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
if (Model::SaveLOD(stream, modelData, lodIndex))
|
||||
return CreateAssetResult::Error;
|
||||
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
@@ -591,40 +581,28 @@ CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelDat
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, const ModelData& modelData, const Options* options)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
|
||||
static_assert(SkinnedModel::SerializedVersion == 30, "Update code.");
|
||||
|
||||
// Save skinned model header
|
||||
MemoryWriteStream stream(4096);
|
||||
if (modelData.Pack2SkinnedModelHeader(&stream))
|
||||
if (SkinnedModel::SaveHeader(stream, modelData))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
|
||||
// Pack model LODs data
|
||||
const auto lodCount = modelData.GetLODsCount();
|
||||
const auto lodCount = modelData.LODs.Count();
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
|
||||
// Mesh Data Version
|
||||
stream.WriteByte(1);
|
||||
|
||||
// Pack meshes
|
||||
auto& meshes = modelData.LODs[lodIndex].Meshes;
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
if (meshes[meshIndex]->Pack2SkinnedModel(&stream))
|
||||
{
|
||||
LOG(Warning, "Cannot pack mesh.");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
if (SkinnedModel::SaveLOD(stream, modelData, lodIndex, SkinnedModel::SaveMesh))
|
||||
return CreateAssetResult::Error;
|
||||
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
@@ -633,10 +611,11 @@ CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, M
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, const ModelData& modelData, const Options* options)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
IMPORT_SETUP(Animation, Animation::SerializedVersion);
|
||||
static_assert(Animation::SerializedVersion == 1, "Update code.");
|
||||
|
||||
// Save animation data
|
||||
MemoryWriteStream stream(8182);
|
||||
@@ -651,7 +630,7 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode
|
||||
animIndex = i;
|
||||
}
|
||||
}
|
||||
if (modelData.Pack2AnimationHeader(&stream, animIndex))
|
||||
if (Animation::SaveHeader(modelData, stream, animIndex))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
@@ -660,7 +639,7 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<PrefabObject>& prefabObjects)
|
||||
CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const ModelData& data, const Options& options, const Array<PrefabObject>& prefabObjects)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
if (data.Nodes.Count() == 0)
|
||||
|
||||
@@ -40,10 +40,10 @@ public:
|
||||
static CreateAssetResult Create(CreateAssetContext& context);
|
||||
|
||||
private:
|
||||
static CreateAssetResult CreateModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreateAnimation(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<struct PrefabObject>& prefabObjects);
|
||||
static CreateAssetResult CreateModel(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreateAnimation(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreatePrefab(CreateAssetContext& context, const ModelData& data, const Options& options, const Array<struct PrefabObject>& prefabObjects);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -356,17 +356,29 @@ namespace FlaxEngine
|
||||
/// <returns>The newly constructed bounding box.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="points" /> is <c>null</c>.</exception>
|
||||
public static BoundingBox FromPoints(Vector3[] points)
|
||||
{
|
||||
FromPoints(points, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="BoundingBox" /> that fully contains the given points.
|
||||
/// </summary>
|
||||
/// <param name="points">The points that will be contained by the box.</param>
|
||||
/// <param name="result">When the method completes, contains the newly constructed bounding box.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="points" /> is <c>null</c>.</exception>
|
||||
public static void FromPoints(Span<Float3> points, out BoundingBox result)
|
||||
{
|
||||
if (points == null)
|
||||
throw new ArgumentNullException(nameof(points));
|
||||
var min = Vector3.Maximum;
|
||||
var max = Vector3.Minimum;
|
||||
var min = Float3.Maximum;
|
||||
var max = Float3.Minimum;
|
||||
for (var i = 0; i < points.Length; ++i)
|
||||
{
|
||||
Vector3.Min(ref min, ref points[i], out min);
|
||||
Vector3.Max(ref max, ref points[i], out max);
|
||||
Float3.Min(ref min, ref points[i], out min);
|
||||
Float3.Max(ref max, ref points[i], out max);
|
||||
}
|
||||
return new BoundingBox(min, max);
|
||||
result = new BoundingBox(min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1343,6 +1343,16 @@ namespace FlaxEngine
|
||||
return new Float3(value.X, value.Y, value.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an explicit conversion from <see cref="Float4" /> to <see cref="Int4" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The result of the conversion.</returns>
|
||||
public static implicit operator Int4(Float4 value)
|
||||
{
|
||||
return new Int4((int)value.X, (int)value.Y, (int)value.Z, (int)value.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -73,7 +73,7 @@ int32 GameBase::LoadProduct()
|
||||
goto LOAD_GAME_HEAD_FAILED;
|
||||
|
||||
// Load game primary data
|
||||
stream->ReadArray(&data);
|
||||
stream->Read(data);
|
||||
if (data.Count() != 808 + sizeof(Guid))
|
||||
goto LOAD_GAME_HEAD_FAILED;
|
||||
Encryption::DecryptBytes(data.Get(), data.Count());
|
||||
|
||||
@@ -400,6 +400,12 @@ namespace FlaxEngine
|
||||
Format = format;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}, {1}, offset {2}, {3}, slot {3}", Type, Format, Offset, PerInstance != 0 ? "per-instance" : "per-vertex", Slot);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(VertexElement other)
|
||||
{
|
||||
|
||||
@@ -68,4 +68,11 @@ public:
|
||||
/// The list of shape vertices.
|
||||
/// </summary>
|
||||
Array<BlendShapeVertex> Vertices;
|
||||
|
||||
void LoadHeader(class ReadStream& stream, byte headerVersion);
|
||||
void Load(ReadStream& stream, byte meshVersion);
|
||||
#if USE_EDITOR
|
||||
void SaveHeader(class WriteStream& stream) const;
|
||||
void Save(WriteStream& stream) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -31,21 +31,21 @@ public:
|
||||
}
|
||||
|
||||
template<typename IndexType>
|
||||
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices)
|
||||
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3))
|
||||
{
|
||||
Triangles.Clear();
|
||||
Triangles.EnsureCapacity(triangles, false);
|
||||
|
||||
const IndexType* it = indices;
|
||||
for (uint32 i = 0; i < triangles; i++)
|
||||
{
|
||||
auto i0 = *(it++);
|
||||
auto i1 = *(it++);
|
||||
auto i2 = *(it++);
|
||||
|
||||
const IndexType i0 = *(it++);
|
||||
const IndexType i1 = *(it++);
|
||||
const IndexType i2 = *(it++);
|
||||
if (i0 < vertices && i1 < vertices && i2 < vertices)
|
||||
{
|
||||
Triangles.Add({ positions[i0], positions[i1], positions[i2] });
|
||||
#define GET_POS(idx) *(const Float3*)((const byte*)positions + positionsStride * idx)
|
||||
Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
|
||||
#undef GET_POS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
#define MODEL_MAX_MESHES 4096
|
||||
|
||||
// Maximum amount of texture channels (UVs) per vertex
|
||||
#define MODEL_MAX_UVS 4
|
||||
#define MODEL_MAX_UV 4
|
||||
|
||||
// Maximum amount of vertex buffers (VBs) per mesh
|
||||
#define MODEL_MAX_VB 3
|
||||
|
||||
// Enable/disable precise mesh collision testing (with in-build vertex buffer caching, this will increase memory usage)
|
||||
#define MODEL_USE_PRECISE_MESH_INTERSECTS (USE_EDITOR)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Mesh.h"
|
||||
#include "MeshAccessor.h"
|
||||
#include "MeshDeformation.h"
|
||||
#include "ModelInstanceEntry.h"
|
||||
#include "Engine/Content/Assets/Material.h"
|
||||
@@ -11,16 +12,14 @@
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Threading/Task.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Renderer/GBufferPass.h"
|
||||
#endif
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
GPUVertexLayout* VB0ElementType18::GetLayout()
|
||||
{
|
||||
return GPUVertexLayout::Get({
|
||||
@@ -44,77 +43,100 @@ GPUVertexLayout* VB2ElementType18::GetLayout()
|
||||
{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm },
|
||||
});
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename IndexType>
|
||||
bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const IndexType* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
|
||||
bool UpdateMesh(MeshBase* mesh, uint32 vertexCount, uint32 triangleCount, PixelFormat indexFormat, const Float3* vertices, const void* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
|
||||
{
|
||||
auto model = mesh->GetModel();
|
||||
auto model = mesh->GetModelBase();
|
||||
CHECK_RETURN(model && model->IsVirtual(), true);
|
||||
CHECK_RETURN(triangles && vertices, true);
|
||||
MeshAccessor accessor;
|
||||
|
||||
// Index Buffer
|
||||
{
|
||||
if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount, indexFormat))
|
||||
return true;
|
||||
auto indexStream = accessor.Index();
|
||||
ASSERT(indexStream.IsLinear(indexFormat));
|
||||
indexStream.SetLinear(triangles);
|
||||
}
|
||||
|
||||
// Pack mesh data into vertex buffers
|
||||
Array<VB1ElementType> vb1;
|
||||
Array<VB2ElementType> vb2;
|
||||
vb1.Resize(vertexCount);
|
||||
// Vertex Buffer 0 (position-only)
|
||||
{
|
||||
GPUVertexLayout* vb0layout = GPUVertexLayout::Get({ { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float } });
|
||||
if (accessor.AllocateBuffer(MeshBufferType::Vertex0, vertexCount, vb0layout))
|
||||
return true;
|
||||
auto positionStream = accessor.Position();
|
||||
ASSERT(positionStream.IsLinear(PixelFormat::R32G32B32_Float));
|
||||
positionStream.SetLinear(vertices);
|
||||
}
|
||||
|
||||
// Vertex Buffer 1 (general purpose components)
|
||||
GPUVertexLayout::Elements vb1elements;
|
||||
if (normals)
|
||||
{
|
||||
vb1elements.Add({ VertexElement::Types::Normal, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
if (tangents)
|
||||
{
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
const Float3 normal = normals[i];
|
||||
const Float3 tangent = tangents[i];
|
||||
auto& v = vb1.Get()[i];
|
||||
RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
const Float3 normal = normals[i];
|
||||
auto& v = vb1.Get()[i];
|
||||
RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set default tangent frame
|
||||
const auto n = Float1010102(Float3::UnitZ);
|
||||
const auto t = Float1010102(Float3::UnitX);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vb1.Get()[i].Normal = n;
|
||||
vb1.Get()[i].Tangent = t;
|
||||
}
|
||||
vb1elements.Add({ VertexElement::Types::Tangent, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
}
|
||||
if (uvs)
|
||||
vb1elements.Add({ VertexElement::Types::TexCoord, 1, 0, 0, PixelFormat::R16G16_Float });
|
||||
if (vb1elements.HasItems())
|
||||
{
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb1.Get()[i].TexCoord = Half2(uvs[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto v = Half2::Zero;
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb1.Get()[i].TexCoord = v;
|
||||
}
|
||||
{
|
||||
auto v = Half2::Zero;
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb1.Get()[i].LightmapUVs = v;
|
||||
}
|
||||
if (colors)
|
||||
{
|
||||
vb2.Resize(vertexCount);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb2.Get()[i].Color = colors[i];
|
||||
GPUVertexLayout* vb1layout = GPUVertexLayout::Get(vb1elements);
|
||||
if (accessor.AllocateBuffer(MeshBufferType::Vertex1, vertexCount, vb1layout))
|
||||
return true;
|
||||
if (normals)
|
||||
{
|
||||
auto normalStream = accessor.Normal();
|
||||
if (tangents)
|
||||
{
|
||||
auto tangentStream = accessor.Tangent();
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
const Float3 normal = normals[i];
|
||||
const Float3 tangent = tangents[i];
|
||||
Float3 n;
|
||||
Float4 t;
|
||||
RenderTools::CalculateTangentFrame(n, t, normal, tangent);
|
||||
normalStream.SetFloat3(i, n);
|
||||
tangentStream.SetFloat4(i, t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
const Float3 normal = normals[i];
|
||||
Float3 n;
|
||||
Float4 t;
|
||||
RenderTools::CalculateTangentFrame(n, t, normal);
|
||||
normalStream.SetFloat3(i, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uvs)
|
||||
{
|
||||
auto uvsStream = accessor.TexCoord();
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
uvsStream.SetFloat2(i, uvs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vertices, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, triangles);
|
||||
// Vertex Buffer 2 (color-only)
|
||||
if (colors)
|
||||
{
|
||||
GPUVertexLayout* vb2layout = GPUVertexLayout::Get({{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }});
|
||||
if (accessor.AllocateBuffer(MeshBufferType::Vertex2, vertexCount, vb2layout))
|
||||
return true;
|
||||
auto colorStream = accessor.Color();
|
||||
ASSERT(colorStream.IsLinear(PixelFormat::R8G8B8A8_UNorm));
|
||||
colorStream.SetLinear(colors);
|
||||
}
|
||||
|
||||
return accessor.UpdateMesh(mesh);
|
||||
}
|
||||
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
@@ -125,21 +147,38 @@ namespace
|
||||
ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount);
|
||||
auto vertices = MCore::Array::GetAddress<Float3>(verticesObj);
|
||||
auto triangles = MCore::Array::GetAddress<IndexType>(trianglesObj);
|
||||
const PixelFormat indexFormat = sizeof(IndexType) == 4 ? PixelFormat::R32_UInt : PixelFormat::R16_UInt;
|
||||
const auto normals = normalsObj ? MCore::Array::GetAddress<Float3>(normalsObj) : nullptr;
|
||||
const auto tangents = tangentsObj ? MCore::Array::GetAddress<Float3>(tangentsObj) : nullptr;
|
||||
const auto uvs = uvObj ? MCore::Array::GetAddress<Float2>(uvObj) : nullptr;
|
||||
const auto colors = colorsObj ? MCore::Array::GetAddress<Color32>(colorsObj) : nullptr;
|
||||
return UpdateMesh<IndexType>(mesh, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
|
||||
return UpdateMesh(mesh, vertexCount, triangleCount, indexFormat, vertices, triangles, normals, tangents, uvs, colors);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib)
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false);
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
}
|
||||
|
||||
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib)
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true);
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
}
|
||||
|
||||
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices)
|
||||
{
|
||||
Release();
|
||||
|
||||
// Setup GPU resources
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices);
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
if (!failed)
|
||||
{
|
||||
// Calculate mesh bounds
|
||||
@@ -152,12 +191,12 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0Element
|
||||
|
||||
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
|
||||
{
|
||||
return ::UpdateMesh<uint16>(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
|
||||
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, normals, tangents, uvs, colors);
|
||||
}
|
||||
|
||||
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
|
||||
{
|
||||
return ::UpdateMesh<uint32>(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
|
||||
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, normals, tangents, uvs, colors);
|
||||
}
|
||||
|
||||
bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer)
|
||||
@@ -169,9 +208,11 @@ bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void*
|
||||
if (vb2)
|
||||
vbData.Add(vb2);
|
||||
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
vbLayout.Add(VB0ElementType::GetLayout());
|
||||
vbLayout.Add(VB1ElementType::GetLayout());
|
||||
vbLayout.Add(VB2ElementType::GetLayout());
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
|
||||
}
|
||||
|
||||
@@ -251,7 +292,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
|
||||
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
|
||||
vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
|
||||
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType);
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(Color32);
|
||||
}
|
||||
drawCall.Draw.IndicesCount = _triangles * 3;
|
||||
drawCall.InstanceCount = 1;
|
||||
@@ -314,7 +355,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
|
||||
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
|
||||
vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
|
||||
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType);
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(Color32);
|
||||
}
|
||||
drawCall.Draw.IndicesCount = _triangles * 3;
|
||||
drawCall.InstanceCount = 1;
|
||||
@@ -364,96 +405,6 @@ void Mesh::Release()
|
||||
MeshBase::Release();
|
||||
}
|
||||
|
||||
bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const
|
||||
{
|
||||
if (_cachedVertexBuffers[0].IsEmpty())
|
||||
{
|
||||
PROFILE_CPU();
|
||||
auto model = GetModel();
|
||||
ScopeLock lock(model->Locker);
|
||||
if (model->IsVirtual())
|
||||
{
|
||||
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fetch chunk with data from drive/memory
|
||||
const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(GetLODIndex());
|
||||
if (model->LoadChunk(chunkIndex))
|
||||
return true;
|
||||
const auto chunk = model->GetChunk(chunkIndex);
|
||||
if (!chunk)
|
||||
{
|
||||
LOG(Error, "Missing chunk.");
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
|
||||
// Seek to find mesh location
|
||||
for (int32 i = 0; i <= _index; i++)
|
||||
{
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Error, "Invalid mesh data.");
|
||||
return true;
|
||||
}
|
||||
auto vb0 = stream.Move<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Move<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
|
||||
if (i != _index)
|
||||
continue;
|
||||
|
||||
// Cache mesh data
|
||||
_cachedIndexBufferCount = indicesCount;
|
||||
_cachedIndexBuffer.Set(ib, indicesCount * ibStride);
|
||||
_cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0ElementType));
|
||||
_cachedVertexBuffers[1].Set((const byte*)vb1, vertices * sizeof(VB1ElementType));
|
||||
if (hasColors)
|
||||
_cachedVertexBuffers[2].Set((const byte*)vb2, vertices * sizeof(VB2ElementType));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
result.Link(_cachedIndexBuffer);
|
||||
count = _cachedIndexBufferCount;
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
result.Link(_cachedVertexBuffers[0]);
|
||||
count = _cachedVertexBuffers[0].Count() / sizeof(VB0ElementType);
|
||||
break;
|
||||
case MeshBufferType::Vertex1:
|
||||
result.Link(_cachedVertexBuffers[1]);
|
||||
count = _cachedVertexBuffers[1].Count() / sizeof(VB1ElementType);
|
||||
break;
|
||||
case MeshBufferType::Vertex2:
|
||||
result.Link(_cachedVertexBuffers[2]);
|
||||
count = _cachedVertexBuffers[2].Count() / sizeof(VB2ElementType);
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
|
||||
bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
|
||||
@@ -466,135 +417,80 @@ bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray
|
||||
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||
}
|
||||
|
||||
// [Deprecated in v1.10]
|
||||
enum class InternalBufferType
|
||||
{
|
||||
VB0 = 0,
|
||||
VB1 = 1,
|
||||
VB2 = 2,
|
||||
IB16 = 3,
|
||||
IB32 = 4,
|
||||
};
|
||||
|
||||
MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI)
|
||||
{
|
||||
auto mesh = this;
|
||||
auto type = (InternalBufferType)typeI;
|
||||
auto model = mesh->GetModel();
|
||||
ScopeLock lock(model->Locker);
|
||||
// [Deprecated in v1.10]
|
||||
ScopeLock lock(GetModelBase()->Locker);
|
||||
|
||||
// Virtual assets always fetch from GPU memory
|
||||
forceGpu |= model->IsVirtual();
|
||||
|
||||
if (!mesh->IsInitialized() && forceGpu)
|
||||
// Get vertex buffers data from the mesh (CPU or GPU)
|
||||
MeshAccessor accessor;
|
||||
MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 };
|
||||
switch ((InternalBufferType)typeI)
|
||||
{
|
||||
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MeshBufferType bufferType;
|
||||
switch (type)
|
||||
{
|
||||
case InternalBufferType::VB0:
|
||||
bufferType = MeshBufferType::Vertex0;
|
||||
break;
|
||||
case InternalBufferType::VB1:
|
||||
bufferType = MeshBufferType::Vertex1;
|
||||
bufferTypes[0] = MeshBufferType::Vertex1;
|
||||
break;
|
||||
case InternalBufferType::VB2:
|
||||
bufferType = MeshBufferType::Vertex2;
|
||||
bufferTypes[0] = MeshBufferType::Vertex2;
|
||||
break;
|
||||
case InternalBufferType::IB16:
|
||||
case InternalBufferType::IB32:
|
||||
bufferType = MeshBufferType::Index;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1)))
|
||||
return nullptr;
|
||||
}
|
||||
BytesContainer data;
|
||||
int32 dataCount;
|
||||
if (forceGpu)
|
||||
{
|
||||
// Get data from GPU
|
||||
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
|
||||
auto task = mesh->DownloadDataGPUAsync(bufferType, data);
|
||||
if (task == nullptr)
|
||||
return nullptr;
|
||||
task->Start();
|
||||
model->Locker.Unlock();
|
||||
if (task->Wait())
|
||||
{
|
||||
LOG(Error, "Task failed.");
|
||||
return nullptr;
|
||||
}
|
||||
model->Locker.Lock();
|
||||
|
||||
// Extract elements count from result data
|
||||
switch (bufferType)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
dataCount = data.Length() / (Use16BitIndexBuffer() ? sizeof(uint16) : sizeof(uint32));
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
dataCount = data.Length() / sizeof(VB0ElementType);
|
||||
break;
|
||||
case MeshBufferType::Vertex1:
|
||||
dataCount = data.Length() / sizeof(VB1ElementType);
|
||||
break;
|
||||
case MeshBufferType::Vertex2:
|
||||
dataCount = data.Length() / sizeof(VB2ElementType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get data from CPU
|
||||
if (DownloadDataCPU(bufferType, data, dataCount))
|
||||
return nullptr;
|
||||
}
|
||||
auto positionStream = accessor.Position();
|
||||
auto texCoordStream = accessor.TexCoord();
|
||||
auto lightmapUVsStream = accessor.TexCoord(1);
|
||||
auto normalStream = accessor.Normal();
|
||||
auto tangentStream = accessor.Tangent();
|
||||
auto colorStream = accessor.Color();
|
||||
auto count = GetVertexCount();
|
||||
|
||||
// Convert into managed array
|
||||
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), dataCount);
|
||||
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), count);
|
||||
void* managedArrayPtr = MCore::Array::GetAddress(result);
|
||||
const int32 elementSize = data.Length() / dataCount;
|
||||
switch (type)
|
||||
switch ((InternalBufferType)typeI)
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
case InternalBufferType::VB0:
|
||||
CHECK_RETURN(positionStream.IsValid(), nullptr);
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
auto& dst = ((VB0ElementType*)managedArrayPtr)[i];
|
||||
dst.Position = positionStream.GetFloat3(i);
|
||||
}
|
||||
break;
|
||||
case InternalBufferType::VB1:
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
auto& dst = ((VB1ElementType*)managedArrayPtr)[i];
|
||||
if (texCoordStream.IsValid())
|
||||
dst.TexCoord = texCoordStream.GetFloat2(i);
|
||||
if (normalStream.IsValid())
|
||||
dst.Normal = normalStream.GetFloat3(i);
|
||||
if (tangentStream.IsValid())
|
||||
dst.Tangent = tangentStream.GetFloat4(i);
|
||||
if (lightmapUVsStream.IsValid())
|
||||
dst.LightmapUVs = lightmapUVsStream.GetFloat2(i);
|
||||
}
|
||||
break;
|
||||
case InternalBufferType::VB2:
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::IB16:
|
||||
{
|
||||
if (elementSize == sizeof(uint16))
|
||||
if (colorStream.IsValid())
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dst = (uint16*)managedArrayPtr;
|
||||
auto src = (uint32*)data.Get();
|
||||
for (int32 i = 0; i < dataCount; i++)
|
||||
dst[i] = src[i];
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
auto& dst = ((VB2ElementType*)managedArrayPtr)[i];
|
||||
dst.Color = Color32(colorStream.GetFloat4(i));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::IB32:
|
||||
{
|
||||
if (elementSize == sizeof(uint16))
|
||||
{
|
||||
auto dst = (uint32*)managedArrayPtr;
|
||||
auto src = (uint16*)data.Get();
|
||||
for (int32 i = 0; i < dataCount; i++)
|
||||
dst[i] = src[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
||||
}
|
||||
break;
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -9,7 +9,9 @@ namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// The Vertex Buffer 0 structure format.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public struct Vertex0
|
||||
{
|
||||
/// <summary>
|
||||
@@ -20,7 +22,9 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// The Vertex Buffer 1 structure format.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public struct Vertex1
|
||||
{
|
||||
/// <summary>
|
||||
@@ -46,7 +50,9 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// The Vertex Buffer 2 structure format.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public struct Vertex2
|
||||
{
|
||||
/// <summary>
|
||||
@@ -57,7 +63,9 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// The raw Vertex Buffer structure format.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public struct Vertex
|
||||
{
|
||||
/// <summary>
|
||||
@@ -425,6 +433,10 @@ namespace FlaxEngine
|
||||
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
internal enum InternalBufferType
|
||||
{
|
||||
VB0 = 0,
|
||||
@@ -436,9 +448,11 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the first vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false)
|
||||
{
|
||||
var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0);
|
||||
@@ -449,9 +463,11 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the second vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public Vertex1[] DownloadVertexBuffer1(bool forceGpu = false)
|
||||
{
|
||||
var result = (Vertex1[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex1), (int)InternalBufferType.VB1);
|
||||
@@ -462,12 +478,14 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the third vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If mesh has no vertex colors (stored in vertex buffer 2) the returned value is null.
|
||||
/// </remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data or null if mesh has no vertex colors.</returns>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public Vertex2[] DownloadVertexBuffer2(bool forceGpu = false)
|
||||
{
|
||||
if (!HasVertexColors)
|
||||
@@ -480,14 +498,13 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the raw vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public Vertex[] DownloadVertexBuffer(bool forceGpu = false)
|
||||
{
|
||||
// TODO: perform data conversion on C++ side to make it faster
|
||||
// TODO: implement batched data download (3 buffers at once) to reduce stall
|
||||
|
||||
var vb0 = DownloadVertexBuffer0(forceGpu);
|
||||
var vb1 = DownloadVertexBuffer1(forceGpu);
|
||||
var vb2 = DownloadVertexBuffer2(forceGpu);
|
||||
@@ -519,33 +536,5 @@ namespace FlaxEngine
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// </summary>
|
||||
/// <remarks>If mesh index buffer format (see <see cref="MeshBase.IndexBufferFormat"/>) is <see cref="PixelFormat.R16_UInt"/> then it's faster to call .</remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
public uint[] DownloadIndexBuffer(bool forceGpu = false)
|
||||
{
|
||||
var result = (uint[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(uint), (int)InternalBufferType.IB32);
|
||||
if (result == null)
|
||||
throw new Exception("Failed to download mesh data.");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// </summary>
|
||||
/// <remarks>If mesh index buffer format (see <see cref="MeshBase.IndexBufferFormat"/>) is <see cref="PixelFormat.R32_UInt"/> then data won't be downloaded.</remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
public ushort[] DownloadIndexBufferUShort(bool forceGpu = false)
|
||||
{
|
||||
var result = (ushort[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(ushort), (int)InternalBufferType.IB16);
|
||||
if (result == null)
|
||||
throw new Exception("Failed to download mesh data.");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,14 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap texture coordinates channel index.
|
||||
/// Lightmap texture coordinates channel index. Value -1 indicates that channel is not available.
|
||||
/// </summary>
|
||||
API_FIELD() int32 LightmapUVsIndex = -1;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
@@ -58,13 +59,12 @@ public:
|
||||
/// <param name="vb2">The third vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false);
|
||||
}
|
||||
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
@@ -73,15 +73,14 @@ public:
|
||||
/// <param name="vb2">The third vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true);
|
||||
}
|
||||
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
|
||||
/// Mesh data will be cached and uploaded to the GPU with a delay.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
@@ -91,6 +90,7 @@ public:
|
||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices);
|
||||
|
||||
/// <summary>
|
||||
@@ -174,7 +174,6 @@ public:
|
||||
// [MeshBase]
|
||||
bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout) override;
|
||||
void Release() override;
|
||||
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
|
||||
|
||||
private:
|
||||
// Internal bindings
|
||||
|
||||
728
Source/Engine/Graphics/Models/MeshAccessor.cs
Normal file
728
Source/Engine/Graphics/Models/MeshAccessor.cs
Normal file
@@ -0,0 +1,728 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// General purpose utility for accessing mesh data (both read and write).
|
||||
/// </summary>
|
||||
public class MeshAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Mesh data stream.
|
||||
/// </summary>
|
||||
public unsafe ref struct Stream
|
||||
{
|
||||
private Span<byte> _data;
|
||||
private PixelFormat _format;
|
||||
private int _stride;
|
||||
private readonly PixelFormatSampler _sampler;
|
||||
|
||||
internal Stream(Span<byte> data, PixelFormat format, int stride)
|
||||
{
|
||||
_data = data;
|
||||
_stride = stride;
|
||||
if (PixelFormatSampler.Get(format, out _sampler))
|
||||
{
|
||||
_format = format;
|
||||
}
|
||||
else if (format != PixelFormat.Unknown)
|
||||
{
|
||||
Debug.LogError($"Unsupported pixel format '{format}' to sample vertex attribute.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw data block.
|
||||
/// </summary>
|
||||
public Span<byte> Data => _data;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the format of the data.
|
||||
/// </summary>
|
||||
public PixelFormat Format => _format;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stride (in bytes) of the data.
|
||||
/// </summary>
|
||||
public int Stride => _stride;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of the items in the stride.
|
||||
/// </summary>
|
||||
public int Count => _data.Length / _stride;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if stream is valid.
|
||||
/// </summary>
|
||||
public bool IsValid => _format != PixelFormat.Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the stream can use <seealso cref="SetLinear"/> via a single memory copy.
|
||||
/// </summary>
|
||||
/// <param name="expectedFormat">Source data format.</param>
|
||||
/// <returns>True if stream is linear and format matches expected input data.</returns>
|
||||
public bool IsLinear(PixelFormat expectedFormat)
|
||||
{
|
||||
return _format == expectedFormat && _stride == PixelFormatExtensions.SizeInBytes(_format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a float value from a given item.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of the item.</param>
|
||||
/// <returns>Loaded value.</returns>
|
||||
public float GetFloat(int index)
|
||||
{
|
||||
fixed (byte* data = _data)
|
||||
return _sampler.Read(data + index * _stride).X;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a Float2 value from a given item.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of the item.</param>
|
||||
/// <returns>Loaded value.</returns>
|
||||
public Float2 GetFloat2(int index)
|
||||
{
|
||||
fixed (byte* data = _data)
|
||||
return new Float2(_sampler.Read(data + index * _stride));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a Float3 value from a given item.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of the item.</param>
|
||||
/// <returns>Loaded value.</returns>
|
||||
public Float3 GetFloat3(int index)
|
||||
{
|
||||
fixed (byte* data = _data)
|
||||
return new Float3(_sampler.Read(data + index * _stride));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a Float4 value from a given item.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of the item.</param>
|
||||
/// <returns>Loaded value.</returns>
|
||||
public Float4 GetFloat4(int index)
|
||||
{
|
||||
fixed (byte* data = _data)
|
||||
return _sampler.Read(data + index * _stride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a float value to a given item.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of the item.</param>
|
||||
/// <param name="value">Value to assign.</param>
|
||||
public void SetFloat(int index, float value)
|
||||
{
|
||||
var v = new Float4(value);
|
||||
fixed (byte* data = _data)
|
||||
_sampler.Write(data + index * _stride, ref v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Float2 value to a given item.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of the item.</param>
|
||||
/// <param name="value">Value to assign.</param>
|
||||
public void SetFloat2(int index, Float2 value)
|
||||
{
|
||||
var v = new Float4(value, 0.0f, 0.0f);
|
||||
fixed (byte* data = _data)
|
||||
_sampler.Write(data + index * _stride, ref v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Float3 value to a given item.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of the item.</param>
|
||||
/// <param name="value">Value to assign.</param>
|
||||
public void SetFloat3(int index, Float3 value)
|
||||
{
|
||||
var v = new Float4(value, 0.0f);
|
||||
fixed (byte* data = _data)
|
||||
_sampler.Write(data + index * _stride, ref v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Float4 value to a given item.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of the item.</param>
|
||||
/// <param name="value">Value to assign.</param>
|
||||
public void SetFloat4(int index, Float4 value)
|
||||
{
|
||||
fixed (byte* data = _data)
|
||||
_sampler.Write(data + index * _stride, ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a linear block of data into the stream. Assumes <seealso cref="IsLinear"/> returned true for the format of the input data.
|
||||
/// </summary>
|
||||
/// <remarks>Check input data and stream type with IsLinear before calling.</remarks>
|
||||
/// <param name="data">Pointer to the source data.</param>
|
||||
public void SetLinear(IntPtr data)
|
||||
{
|
||||
new Span<byte>(data.ToPointer(), _data.Length).CopyTo(_data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
|
||||
/// </summary>
|
||||
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
|
||||
public void Set(Span<Float2> src)
|
||||
{
|
||||
if (IsLinear(PixelFormat.R32G32_Float))
|
||||
{
|
||||
src.CopyTo(MemoryMarshal.Cast<byte, Float2>(_data));
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var v = new Float4(src[i], 0.0f, 0.0f);
|
||||
_sampler.Write(data + i * _stride, ref v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
|
||||
/// </summary>
|
||||
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
|
||||
public void Set(Span<Float3> src)
|
||||
{
|
||||
if (IsLinear(PixelFormat.R32G32B32_Float))
|
||||
{
|
||||
src.CopyTo(MemoryMarshal.Cast<byte, Float3>(_data));
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var v = new Float4(src[i], 0.0f);
|
||||
_sampler.Write(data + i * _stride, ref v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
|
||||
/// </summary>
|
||||
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
|
||||
public void Set(Span<Color> src)
|
||||
{
|
||||
if (IsLinear(PixelFormat.R32G32B32A32_Float))
|
||||
{
|
||||
src.CopyTo(MemoryMarshal.Cast<byte, Color>(_data));
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var v = (Float4)src[i];
|
||||
_sampler.Write(data + i * _stride, ref v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
|
||||
/// </summary>
|
||||
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
|
||||
public void CopyTo(Span<Float2> dst)
|
||||
{
|
||||
if (IsLinear(PixelFormat.R32G32_Float))
|
||||
{
|
||||
_data.CopyTo(MemoryMarshal.Cast<Float2, byte>(dst));
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dst[i] = new Float2(_sampler.Read(data + i * _stride));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
|
||||
/// </summary>
|
||||
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
|
||||
public void CopyTo(Span<Float3> dst)
|
||||
{
|
||||
if (IsLinear(PixelFormat.R32G32B32_Float))
|
||||
{
|
||||
_data.CopyTo(MemoryMarshal.Cast<Float3, byte>(dst));
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dst[i] = new Float3(_sampler.Read(data + i * _stride));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
|
||||
/// </summary>
|
||||
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
|
||||
public void CopyTo(Span<Color> dst)
|
||||
{
|
||||
if (IsLinear(PixelFormat.R32G32B32A32_Float))
|
||||
{
|
||||
_data.CopyTo(MemoryMarshal.Cast<Color, byte>(dst));
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dst[i] = (Color)_sampler.Read(data + i * _stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[][] _data = new byte[(int)MeshBufferType.MAX][];
|
||||
private PixelFormat[] _formats = new PixelFormat[(int)MeshBufferType.MAX];
|
||||
private GPUVertexLayout[] _layouts = new GPUVertexLayout[(int)MeshBufferType.MAX];
|
||||
|
||||
/// <summary>
|
||||
/// Loads the data from the mesh.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The source mesh object to access.</param>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <param name="buffers">Custom list of mesh buffers to load. Use empty value to access all of them.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
public bool LoadMesh(MeshBase mesh, bool forceGpu = false, Span<MeshBufferType> buffers = new())
|
||||
{
|
||||
if (mesh == null)
|
||||
return true;
|
||||
Span<MeshBufferType> allBuffers = stackalloc MeshBufferType[(int)MeshBufferType.MAX] { MeshBufferType.Index, MeshBufferType.Vertex0, MeshBufferType.Vertex1, MeshBufferType.Vertex2 };
|
||||
var buffersLocal = buffers.IsEmpty ? allBuffers : buffers;
|
||||
if (mesh.DownloadData(buffersLocal.ToArray(), out var meshBuffers, out var meshLayouts, forceGpu))
|
||||
return true;
|
||||
for (int i = 0; i < buffersLocal.Length; i++)
|
||||
{
|
||||
_data[(int)buffersLocal[i]] = meshBuffers[i];
|
||||
_layouts[(int)buffersLocal[i]] = meshLayouts[i];
|
||||
}
|
||||
_formats[(int)MeshBufferType.Index] = mesh.Use16BitIndexBuffer ? PixelFormat.R16_UInt : PixelFormat.R32_UInt;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the data from the provided mesh buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferType">Type of the mesh buffer to load.</param>
|
||||
/// <param name="bufferData">Data used by that buffer.</param>
|
||||
/// <param name="layout">Layout of the elements in the buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
public bool LoadBuffer(MeshBufferType bufferType, Span<byte> bufferData, GPUVertexLayout layout)
|
||||
{
|
||||
if (bufferData.IsEmpty)
|
||||
return true;
|
||||
if (layout == null)
|
||||
return true;
|
||||
int idx = (int)bufferType;
|
||||
_data[idx] = bufferData.ToArray();
|
||||
_layouts[idx] = layout;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the data for the mesh vertex buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferType">Type of the mesh buffer to initialize.</param>
|
||||
/// <param name="count">Amount of items in the buffer.</param>
|
||||
/// <param name="layout">Layout of the elements in the buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
public bool AllocateBuffer(MeshBufferType bufferType, int count, GPUVertexLayout layout)
|
||||
{
|
||||
if (count <= 0)
|
||||
return true;
|
||||
if (layout == null)
|
||||
return true;
|
||||
int idx = (int)bufferType;
|
||||
_data[idx] = new byte[count * layout.Stride];
|
||||
_layouts[idx] = layout;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the data for the mesh buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferType">Type of the mesh buffer to initialize.</param>
|
||||
/// <param name="count">Amount of items in the buffer.</param>
|
||||
/// <param name="format">Format of the elements in the buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
public bool AllocateBuffer(MeshBufferType bufferType, int count, PixelFormat format)
|
||||
{
|
||||
if (count <= 0)
|
||||
return true;
|
||||
int stride = PixelFormatExtensions.SizeInBytes(format);
|
||||
if (stride <= 0)
|
||||
return true;
|
||||
int idx = (int)bufferType;
|
||||
_data[idx] = new byte[count * stride];
|
||||
_formats[idx] = format;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the mesh vertex and index buffers with data assigned to the accessor (eg. via AllocateBuffer).
|
||||
/// </summary>
|
||||
/// <param name="mesh">The target mesh to update.</param>
|
||||
/// <param name="calculateBounds">True if auto-calculate mesh local bounding box based on input positions stream. Otherwise, mesh bound swill stay unchanged.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
public unsafe bool UpdateMesh(MeshBase mesh, bool calculateBounds = true)
|
||||
{
|
||||
if (mesh == null)
|
||||
return true;
|
||||
int IB = (int)MeshBufferType.Index;
|
||||
int VB0 = (int)MeshBufferType.Vertex0;
|
||||
int VB1 = (int)MeshBufferType.Vertex1;
|
||||
int VB2 = (int)MeshBufferType.Vertex2;
|
||||
|
||||
uint vertices = 0, triangles = 0;
|
||||
fixed (byte* data0Ptr = _data[0])
|
||||
fixed (byte* data1Ptr = _data[1])
|
||||
fixed (byte* data2Ptr = _data[2])
|
||||
fixed (byte* data3Ptr = _data[3])
|
||||
{
|
||||
Span<IntPtr> dataPtr = stackalloc IntPtr[(int)MeshBufferType.MAX] { new IntPtr(data0Ptr), new IntPtr(data1Ptr), new IntPtr(data2Ptr), new IntPtr(data3Ptr) };
|
||||
IntPtr ibData = IntPtr.Zero;
|
||||
bool use16BitIndexBuffer = false;
|
||||
IntPtr[] vbData = new IntPtr[3];
|
||||
GPUVertexLayout[] vbLayout = new GPUVertexLayout[3];
|
||||
if (_data[VB0] != null)
|
||||
{
|
||||
vbData[0] = dataPtr[VB0];
|
||||
vbLayout[0] = _layouts[VB0];
|
||||
vertices = (uint)_data[VB0].Length / _layouts[VB0].Stride;
|
||||
}
|
||||
if (_data[VB1] != null)
|
||||
{
|
||||
vbData[1] = dataPtr[VB1];
|
||||
vbLayout[1] = _layouts[VB1];
|
||||
vertices = (uint)_data[VB1].Length / _layouts[VB1].Stride;
|
||||
}
|
||||
if (_data[VB2] != null)
|
||||
{
|
||||
vbData[2] = dataPtr[VB2];
|
||||
vbLayout[2] = _layouts[VB2];
|
||||
vertices = (uint)_data[VB2].Length / _layouts[VB2].Stride;
|
||||
}
|
||||
if (_data[IB] != null && _formats[IB] != PixelFormat.Unknown)
|
||||
{
|
||||
ibData = dataPtr[IB];
|
||||
use16BitIndexBuffer = _formats[IB] == PixelFormat.R16_UInt;
|
||||
triangles = (uint)(_data[IB].Length / PixelFormatExtensions.SizeInBytes(_formats[IB]));
|
||||
}
|
||||
|
||||
if (mesh.Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (calculateBounds)
|
||||
{
|
||||
// Calculate mesh bounds
|
||||
BoundingBox bounds;
|
||||
var positionStream = Position();
|
||||
if (!positionStream.IsValid)
|
||||
return true;
|
||||
if (positionStream.IsLinear(PixelFormat.R32G32B32_Float))
|
||||
{
|
||||
Span<byte> positionData = positionStream.Data;
|
||||
BoundingBox.FromPoints(MemoryMarshal.Cast<byte, Float3>(positionData), out bounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Float3 min = Float3.Maximum, max = Float3.Minimum;
|
||||
for (int i = 0; i < vertices; i++)
|
||||
{
|
||||
Float3 pos = positionStream.GetFloat3(i);
|
||||
Float3.Min(ref min, ref pos, out min);
|
||||
Float3.Max(ref max, ref pos, out max);
|
||||
}
|
||||
bounds = new BoundingBox(min, max);
|
||||
}
|
||||
mesh.SetBounds(ref bounds);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with index buffer.
|
||||
/// </summary>
|
||||
/// <returns>Mesh data stream (might be invalid if data is not provided).</returns>
|
||||
public Stream Index()
|
||||
{
|
||||
Span<byte> data = new Span<byte>();
|
||||
PixelFormat format = PixelFormat.Unknown;
|
||||
int stride = 0;
|
||||
var ib = _data[(int)MeshBufferType.Index];
|
||||
if (ib != null)
|
||||
{
|
||||
data = ib;
|
||||
format = _formats[(int)MeshBufferType.Index];
|
||||
stride = PixelFormatExtensions.SizeInBytes(format);
|
||||
}
|
||||
return new Stream(data, format, stride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with a specific vertex attribute.
|
||||
/// </summary>
|
||||
/// <param name="attribute">Type of the attribute.</param>
|
||||
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
|
||||
public Stream Attribute(VertexElement.Types attribute)
|
||||
{
|
||||
Span<byte> data = new Span<byte>();
|
||||
PixelFormat format = PixelFormat.Unknown;
|
||||
int stride = 0;
|
||||
for (int vbIndex = 0; vbIndex < 3 && format == PixelFormat.Unknown; vbIndex++)
|
||||
{
|
||||
int idx = vbIndex + 1;
|
||||
var layout = _layouts[idx];
|
||||
if (!layout)
|
||||
continue;
|
||||
foreach (VertexElement e in layout.Elements)
|
||||
{
|
||||
var vb = _data[idx];
|
||||
if (e.Type == attribute && vb != null)
|
||||
{
|
||||
data = new Span<byte>(vb).Slice(e.Offset);
|
||||
format = e.Format;
|
||||
stride = (int)layout.Stride;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Stream(data, format, stride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with vertex position attribute.
|
||||
/// </summary>
|
||||
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
|
||||
public Stream Position()
|
||||
{
|
||||
return Attribute(VertexElement.Types.Position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with vertex color attribute.
|
||||
/// </summary>
|
||||
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
|
||||
public Stream Color()
|
||||
{
|
||||
return Attribute(VertexElement.Types.Color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with vertex normal vector attribute.
|
||||
/// </summary>
|
||||
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
|
||||
public Stream Normal()
|
||||
{
|
||||
return Attribute(VertexElement.Types.Normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with vertex tangent vector attribute.
|
||||
/// </summary>
|
||||
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
|
||||
public Stream Tangent()
|
||||
{
|
||||
return Attribute(VertexElement.Types.Tangent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with vertex skeleton bones blend indices attribute.
|
||||
/// </summary>
|
||||
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
|
||||
public Stream BlendIndices()
|
||||
{
|
||||
return Attribute(VertexElement.Types.BlendIndices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with vertex skeleton bones blend weights attribute.
|
||||
/// </summary>
|
||||
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
|
||||
public Stream BlendWeights()
|
||||
{
|
||||
return Attribute(VertexElement.Types.BlendWeights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access stream with vertex texture coordinates attribute (specific UV channel).
|
||||
/// </summary>
|
||||
/// <param name="channel">UV channel index (zero-based).</param>
|
||||
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
|
||||
public Stream TexCoord(int channel = 0)
|
||||
{
|
||||
return Attribute((VertexElement.Types)((byte)VertexElement.Types.TexCoord0 + channel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex positions. Null if <see cref="VertexElement.Types.Position"/> does not exist in vertex buffers of the mesh.
|
||||
/// </summary>
|
||||
/// <remarks>Uses <see cref="Position"/> stream to read or write data to the vertex buffer.</remarks>
|
||||
public Float3[] Positions
|
||||
{
|
||||
get => GetStreamFloat3(VertexElement.Types.Position);
|
||||
set => SetStreamFloat3(VertexElement.Types.Position, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex colors. Null if <see cref="VertexElement.Types.Color"/> does not exist in vertex buffers of the mesh.
|
||||
/// </summary>
|
||||
/// <remarks>Uses <see cref="Color"/> stream to read or write data to the vertex buffer.</remarks>
|
||||
public Color[] Colors
|
||||
{
|
||||
get => GetStreamColor(VertexElement.Types.Color);
|
||||
set => SetStreamColor(VertexElement.Types.Color, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex normal vectors (unpacked, normalized). Null if <see cref="VertexElement.Types.Normal"/> does not exist in vertex buffers of the mesh.
|
||||
/// </summary>
|
||||
/// <remarks>Uses <see cref="Normal"/> stream to read or write data to the vertex buffer.</remarks>
|
||||
public Float3[] Normals
|
||||
{
|
||||
get => GetStreamFloat3(VertexElement.Types.Normal, UnpackNormal);
|
||||
set => SetStreamFloat3(VertexElement.Types.Normal, value, PackNormal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex UVs (texcoord channel 0). Null if <see cref="VertexElement.Types.TexCoord"/> does not exist in vertex buffers of the mesh.
|
||||
/// </summary>
|
||||
/// <remarks>Uses <see cref="TexCoord"/> stream to read or write data to the vertex buffer.</remarks>
|
||||
public Float2[] TexCoords
|
||||
{
|
||||
get => GetStreamFloat2(VertexElement.Types.TexCoord);
|
||||
set => SetStreamFloat2(VertexElement.Types.TexCoord, value);
|
||||
}
|
||||
|
||||
private delegate void TransformDelegate3(ref Float3 value);
|
||||
|
||||
private Float3[] GetStreamFloat3(VertexElement.Types attribute, TransformDelegate3 transform = null)
|
||||
{
|
||||
Float3[] result = null;
|
||||
var stream = Attribute(attribute);
|
||||
if (stream.IsValid)
|
||||
{
|
||||
result = new Float3[stream.Count];
|
||||
stream.CopyTo(result);
|
||||
if (transform != null)
|
||||
{
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
transform(ref result[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetStreamFloat3(VertexElement.Types attribute, Float3[] value, TransformDelegate3 transform = null)
|
||||
{
|
||||
var stream = Attribute(attribute);
|
||||
if (stream.IsValid)
|
||||
{
|
||||
if (transform != null)
|
||||
{
|
||||
// TODO: transform in-place?
|
||||
value = (Float3[])value.Clone();
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
transform(ref value[i]);
|
||||
}
|
||||
stream.Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
private Float2[] GetStreamFloat2(VertexElement.Types attribute)
|
||||
{
|
||||
Float2[] result = null;
|
||||
var stream = Attribute(attribute);
|
||||
if (stream.IsValid)
|
||||
{
|
||||
result = new Float2[stream.Count];
|
||||
stream.CopyTo(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetStreamFloat2(VertexElement.Types attribute, Float2[] value)
|
||||
{
|
||||
var stream = Attribute(attribute);
|
||||
if (stream.IsValid)
|
||||
{
|
||||
stream.Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
private Color[] GetStreamColor(VertexElement.Types attribute)
|
||||
{
|
||||
Color[] result = null;
|
||||
var stream = Attribute(attribute);
|
||||
if (stream.IsValid)
|
||||
{
|
||||
result = new Color[stream.Count];
|
||||
stream.CopyTo(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetStreamColor(VertexElement.Types attribute, Color[] value)
|
||||
{
|
||||
var stream = Attribute(attribute);
|
||||
if (stream.IsValid)
|
||||
{
|
||||
stream.Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UnpackNormal(ref Float3 value)
|
||||
{
|
||||
// [0; 1] -> [-1; 1]
|
||||
value = value * 2.0f - 1.0f;
|
||||
}
|
||||
|
||||
private static void PackNormal(ref Float3 value)
|
||||
{
|
||||
// [-1; 1] -> [0; 1]
|
||||
value = value * 0.5f + 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
185
Source/Engine/Graphics/Models/MeshAccessor.h
Normal file
185
Source/Engine/Graphics/Models/MeshAccessor.h
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "Engine/Core/Types/DataContainer.h"
|
||||
#include "Engine/Graphics/PixelFormat.h"
|
||||
#include "Engine/Graphics/PixelFormatSampler.h"
|
||||
#include "Engine/Graphics/Shaders/VertexElement.h"
|
||||
|
||||
/// <summary>
|
||||
/// General purpose utility for accessing mesh data (both read and write).
|
||||
/// </summary>
|
||||
class FLAXENGINE_API MeshAccessor
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Mesh data stream.
|
||||
/// </summary>
|
||||
struct Stream
|
||||
{
|
||||
friend MeshAccessor;
|
||||
|
||||
private:
|
||||
Span<byte> _data;
|
||||
PixelFormat _format;
|
||||
int32 _stride;
|
||||
PixelFormatSampler _sampler;
|
||||
|
||||
Stream(Span<byte> data, PixelFormat format, int32 stride);
|
||||
|
||||
public:
|
||||
Span<byte> GetData() const;
|
||||
PixelFormat GetFormat() const;
|
||||
int32 GetStride() const;
|
||||
int32 GetCount() const;
|
||||
bool IsValid() const;
|
||||
bool IsLinear(PixelFormat expectedFormat) const;
|
||||
|
||||
FORCE_INLINE float GetFloat(int32 index) const
|
||||
{
|
||||
return _sampler.Read(_data.Get() + index * _stride).X;
|
||||
}
|
||||
|
||||
FORCE_INLINE Float2 GetFloat2(int32 index) const
|
||||
{
|
||||
return Float2(_sampler.Read(_data.Get() + index * _stride));
|
||||
}
|
||||
|
||||
FORCE_INLINE Float3 GetFloat3(int32 index) const
|
||||
{
|
||||
return Float3(_sampler.Read(_data.Get() + index * _stride));
|
||||
}
|
||||
|
||||
FORCE_INLINE Float4 GetFloat4(int32 index) const
|
||||
{
|
||||
return _sampler.Read(_data.Get() + index * _stride);
|
||||
}
|
||||
|
||||
FORCE_INLINE void SetFloat(int32 index, const float& value)
|
||||
{
|
||||
_sampler.Write(_data.Get() + index * _stride, Float4(value));
|
||||
}
|
||||
|
||||
FORCE_INLINE void SetFloat2(int32 index, const Float2& value)
|
||||
{
|
||||
_sampler.Write(_data.Get() + index * _stride, Float4(value));
|
||||
}
|
||||
|
||||
FORCE_INLINE void SetFloat3(int32 index, const Float3& value)
|
||||
{
|
||||
_sampler.Write(_data.Get() + index * _stride, Float4(value));
|
||||
}
|
||||
|
||||
FORCE_INLINE void SetFloat4(int32 index, const Float4& value)
|
||||
{
|
||||
_sampler.Write(_data.Get() + index * _stride, value);
|
||||
}
|
||||
|
||||
// Check input data and stream type with IsLinear before calling.
|
||||
void SetLinear(const void* data);
|
||||
};
|
||||
|
||||
private:
|
||||
BytesContainer _data[(int32)MeshBufferType::MAX];
|
||||
PixelFormat _formats[(int32)MeshBufferType::MAX] = {};
|
||||
GPUVertexLayout* _layouts[(int32)MeshBufferType::MAX] = {};
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Loads the data from the mesh.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The source mesh object to access.</param>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <param name="buffers">Custom list of mesh buffers to load. Use empty value to access all of them.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool LoadMesh(const MeshBase* mesh, bool forceGpu = false, Span<MeshBufferType> buffers = Span<MeshBufferType>());
|
||||
|
||||
/// <summary>
|
||||
/// Loads the data from the provided mesh buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferType">Type of the mesh buffer to load.</param>
|
||||
/// <param name="bufferData">Data used by that buffer.</param>
|
||||
/// <param name="layout">Layout of the elements in the buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool LoadBuffer(MeshBufferType bufferType, Span<byte> bufferData, GPUVertexLayout* layout);
|
||||
|
||||
// Used internally via ModelBase::LoadMesh.
|
||||
bool LoadFromMeshData(const void* meshDataPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the data for the mesh vertex buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferType">Type of the mesh buffer to initialize.</param>
|
||||
/// <param name="count">Amount of items in the buffer.</param>
|
||||
/// <param name="layout">Layout of the elements in the buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool AllocateBuffer(MeshBufferType bufferType, int32 count, GPUVertexLayout* layout);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the data for the mesh buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferType">Type of the mesh buffer to initialize.</param>
|
||||
/// <param name="count">Amount of items in the buffer.</param>
|
||||
/// <param name="format">Format of the elements in the buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool AllocateBuffer(MeshBufferType bufferType, int32 count, PixelFormat format);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the mesh vertex and index buffers with data assigned to the accessor (eg. via AllocateBuffer).
|
||||
/// </summary>
|
||||
/// <param name="mesh">The target mesh to update.</param>
|
||||
/// <param name="calculateBounds">True if auto-calculate mesh local bounding box based on input positions stream. Otherwise, mesh bound swill stay unchanged.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool UpdateMesh(MeshBase* mesh, bool calculateBounds = true);
|
||||
|
||||
public:
|
||||
// Access stream with index buffer.
|
||||
Stream Index();
|
||||
|
||||
// Access stream with a specific vertex attribute.
|
||||
Stream Attribute(VertexElement::Types attribute);
|
||||
|
||||
// Access stream with vertex position attribute.
|
||||
FORCE_INLINE Stream Position()
|
||||
{
|
||||
return Attribute(VertexElement::Types::Position);
|
||||
}
|
||||
|
||||
// Access stream with vertex color attribute.
|
||||
FORCE_INLINE Stream Color()
|
||||
{
|
||||
return Attribute(VertexElement::Types::Color);
|
||||
}
|
||||
|
||||
// Access stream with vertex normal vector attribute.
|
||||
FORCE_INLINE Stream Normal()
|
||||
{
|
||||
return Attribute(VertexElement::Types::Normal);
|
||||
}
|
||||
|
||||
// Access stream with vertex tangent vector attribute.
|
||||
FORCE_INLINE Stream Tangent()
|
||||
{
|
||||
return Attribute(VertexElement::Types::Tangent);
|
||||
}
|
||||
|
||||
// Access stream with vertex skeleton bones blend indices attribute.
|
||||
FORCE_INLINE Stream BlendIndices()
|
||||
{
|
||||
return Attribute(VertexElement::Types::BlendIndices);
|
||||
}
|
||||
|
||||
// Access stream with vertex skeleton bones blend weights attribute.
|
||||
FORCE_INLINE Stream BlendWeights()
|
||||
{
|
||||
return Attribute(VertexElement::Types::BlendWeights);
|
||||
}
|
||||
|
||||
// Access stream with vertex texture coordinates attribute (specific UV channel).
|
||||
FORCE_INLINE Stream TexCoord(int32 channel = 0)
|
||||
{
|
||||
return Attribute((VertexElement::Types)((byte)VertexElement::Types::TexCoord0 + channel));
|
||||
}
|
||||
};
|
||||
@@ -1,14 +1,23 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "MeshBase.h"
|
||||
#include "MeshAccessor.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Content/Assets/ModelBase.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Graphics/GPUBuffer.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
#include "Engine/Scripting/Enums.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Threading/Task.h"
|
||||
|
||||
static_assert(MODEL_MAX_VB == 3, "Update code in mesh to match amount of vertex buffers.");
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -28,6 +37,252 @@ namespace
|
||||
#endif
|
||||
}
|
||||
|
||||
MeshAccessor::Stream::Stream(Span<byte> data, PixelFormat format, int32 stride)
|
||||
: _data(data)
|
||||
, _format(PixelFormat::Unknown)
|
||||
, _stride(stride)
|
||||
{
|
||||
auto sampler = PixelFormatSampler::Get(format);
|
||||
if (sampler)
|
||||
{
|
||||
_format = format;
|
||||
_sampler = *sampler;
|
||||
}
|
||||
else if (format != PixelFormat::Unknown)
|
||||
{
|
||||
LOG(Error, "Unsupported pixel format '{}' to sample vertex attribute.", ScriptingEnum::ToString(format));
|
||||
}
|
||||
}
|
||||
|
||||
Span<byte> MeshAccessor::Stream::GetData() const
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
PixelFormat MeshAccessor::Stream::GetFormat() const
|
||||
{
|
||||
return _format;
|
||||
}
|
||||
|
||||
int32 MeshAccessor::Stream::GetStride() const
|
||||
{
|
||||
return _stride;
|
||||
}
|
||||
|
||||
int32 MeshAccessor::Stream::GetCount() const
|
||||
{
|
||||
return _data.Length() / _stride;
|
||||
}
|
||||
|
||||
bool MeshAccessor::Stream::IsValid() const
|
||||
{
|
||||
return _format != PixelFormat::Unknown;
|
||||
}
|
||||
|
||||
bool MeshAccessor::Stream::IsLinear(PixelFormat expectedFormat) const
|
||||
{
|
||||
return _format == expectedFormat && _stride == PixelFormatExtensions::SizeInBytes(_format);
|
||||
}
|
||||
|
||||
void MeshAccessor::Stream::SetLinear(const void* data)
|
||||
{
|
||||
Platform::MemoryCopy(_data.Get(), data, _data.Length());
|
||||
}
|
||||
|
||||
bool MeshAccessor::LoadMesh(const MeshBase* mesh, bool forceGpu, Span<MeshBufferType> buffers)
|
||||
{
|
||||
CHECK_RETURN(mesh, true);
|
||||
constexpr MeshBufferType allBuffers[(int32)MeshBufferType::MAX] = { MeshBufferType::Index, MeshBufferType::Vertex0, MeshBufferType::Vertex1, MeshBufferType::Vertex2 };
|
||||
if (buffers.IsInvalid())
|
||||
buffers = Span<MeshBufferType>(allBuffers, ARRAY_COUNT(allBuffers));
|
||||
Array<BytesContainer, FixedAllocation<4>> meshBuffers;
|
||||
Array<GPUVertexLayout*, FixedAllocation<4>> meshLayouts;
|
||||
if (mesh->DownloadData(buffers, meshBuffers, meshLayouts, forceGpu))
|
||||
return true;
|
||||
for (int32 i = 0; i < buffers.Length(); i++)
|
||||
{
|
||||
_data[(int32)buffers[i]] = MoveTemp(meshBuffers[i]);
|
||||
_layouts[(int32)buffers[i]] = meshLayouts[i];
|
||||
}
|
||||
_formats[(int32)MeshBufferType::Index] = mesh->Use16BitIndexBuffer() ? PixelFormat::R16_UInt : PixelFormat::R32_UInt;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshAccessor::LoadBuffer(MeshBufferType bufferType, Span<byte> bufferData, GPUVertexLayout* layout)
|
||||
{
|
||||
CHECK_RETURN(layout, true);
|
||||
CHECK_RETURN(bufferData.IsValid(), true);
|
||||
const int32 idx = (int32)bufferType;
|
||||
_data[idx].Link(bufferData);
|
||||
_layouts[idx] = layout;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshAccessor::LoadFromMeshData(const void* meshDataPtr)
|
||||
{
|
||||
if (!meshDataPtr)
|
||||
return true;
|
||||
const auto& meshData = *(const ModelBase::MeshData*)meshDataPtr;
|
||||
if (meshData.VBData.Count() != meshData.VBLayout.Count())
|
||||
return true;
|
||||
_data[(int32)MeshBufferType::Index].Link((const byte*)meshData.IBData, meshData.IBStride * meshData.Triangles * 3);
|
||||
if (meshData.VBData.Count() > 0 && meshData.VBLayout[0])
|
||||
{
|
||||
constexpr int32 idx = (int32)MeshBufferType::Vertex0;
|
||||
_data[idx].Link((const byte*)meshData.VBData[0], meshData.VBLayout[0]->GetStride() * meshData.Vertices);
|
||||
_layouts[idx] = meshData.VBLayout[0];
|
||||
}
|
||||
if (meshData.VBData.Count() > 1 && meshData.VBLayout[1])
|
||||
{
|
||||
constexpr int32 idx = (int32)MeshBufferType::Vertex1;
|
||||
_data[idx].Link((const byte*)meshData.VBData[1], meshData.VBLayout[1]->GetStride() * meshData.Vertices);
|
||||
_layouts[idx] = meshData.VBLayout[1];
|
||||
}
|
||||
if (meshData.VBData.Count() > 2 && meshData.VBLayout[2])
|
||||
{
|
||||
constexpr int32 idx = (int32)MeshBufferType::Vertex2;
|
||||
_data[idx].Link((const byte*)meshData.VBData[2], meshData.VBLayout[2]->GetStride() * meshData.Vertices);
|
||||
_layouts[idx] = meshData.VBLayout[2];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshAccessor::AllocateBuffer(MeshBufferType bufferType, int32 count, GPUVertexLayout* layout)
|
||||
{
|
||||
CHECK_RETURN(count, true);
|
||||
CHECK_RETURN(layout, true);
|
||||
const int32 idx = (int32)bufferType;
|
||||
_data[idx].Allocate(count * layout->GetStride());
|
||||
_layouts[idx] = layout;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshAccessor::AllocateBuffer(MeshBufferType bufferType, int32 count, PixelFormat format)
|
||||
{
|
||||
CHECK_RETURN(count, true);
|
||||
const int32 stride = PixelFormatExtensions::SizeInBytes(format);
|
||||
CHECK_RETURN(stride, true);
|
||||
const int32 idx = (int32)bufferType;
|
||||
_data[idx].Allocate(count * stride);
|
||||
_formats[idx] = format;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshAccessor::UpdateMesh(MeshBase* mesh, bool calculateBounds)
|
||||
{
|
||||
CHECK_RETURN(mesh, true);
|
||||
constexpr int32 IB = (int32)MeshBufferType::Index;
|
||||
constexpr int32 VB0 = (int32)MeshBufferType::Vertex0;
|
||||
constexpr int32 VB1 = (int32)MeshBufferType::Vertex1;
|
||||
constexpr int32 VB2 = (int32)MeshBufferType::Vertex2;
|
||||
|
||||
uint32 vertices = 0, triangles = 0;
|
||||
const void* ibData = nullptr;
|
||||
bool use16BitIndexBuffer = false;
|
||||
Array<const void*, FixedAllocation<3>> vbData;
|
||||
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
|
||||
vbData.Resize(3);
|
||||
vbLayout.Resize(3);
|
||||
vbData.SetAll(nullptr);
|
||||
vbLayout.SetAll(nullptr);
|
||||
if (_data[VB0].IsValid())
|
||||
{
|
||||
vbData[0] = _data[VB0].Get();
|
||||
vbLayout[0] = _layouts[VB0];
|
||||
vertices = _data[VB0].Length() / _layouts[VB0]->GetStride();
|
||||
}
|
||||
if (_data[VB1].IsValid())
|
||||
{
|
||||
vbData[1] = _data[VB1].Get();
|
||||
vbLayout[1] = _layouts[VB1];
|
||||
vertices = _data[VB1].Length() / _layouts[VB1]->GetStride();
|
||||
}
|
||||
if (_data[VB2].IsValid())
|
||||
{
|
||||
vbData[2] = _data[VB2].Get();
|
||||
vbLayout[2] = _layouts[VB2];
|
||||
vertices = _data[VB2].Length() / _layouts[VB2]->GetStride();
|
||||
}
|
||||
if (_data[IB].IsValid() && _formats[IB] != PixelFormat::Unknown)
|
||||
{
|
||||
ibData = _data[IB].Get();
|
||||
use16BitIndexBuffer = _formats[IB] == PixelFormat::R16_UInt;
|
||||
triangles = _data[IB].Length() / PixelFormatExtensions::SizeInBytes(_formats[IB]);
|
||||
}
|
||||
|
||||
if (mesh->Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout))
|
||||
return true;
|
||||
|
||||
if (calculateBounds)
|
||||
{
|
||||
// Calculate mesh bounds
|
||||
BoundingBox bounds;
|
||||
auto positionStream = Position();
|
||||
CHECK_RETURN(positionStream.IsValid(), true);
|
||||
if (positionStream.IsLinear(PixelFormat::R32G32B32_Float))
|
||||
{
|
||||
Span<byte> positionData = positionStream.GetData();
|
||||
BoundingBox::FromPoints((const Float3*)positionData.Get(), positionData.Length() / sizeof(Float3), bounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Float3 min = Float3::Maximum, max = Float3::Minimum;
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
Float3 pos = positionStream.GetFloat3(i);
|
||||
Float3::Min(min, pos, min);
|
||||
Float3::Max(max, pos, max);
|
||||
}
|
||||
bounds = BoundingBox(min, max);
|
||||
}
|
||||
mesh->SetBounds(bounds);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MeshAccessor::Stream MeshAccessor::Index()
|
||||
{
|
||||
Span<byte> data;
|
||||
PixelFormat format = PixelFormat::Unknown;
|
||||
int32 stride = 0;
|
||||
auto& ib = _data[(int32)MeshBufferType::Index];
|
||||
if (ib.IsValid())
|
||||
{
|
||||
data = ib;
|
||||
format = _formats[(int32)MeshBufferType::Index];
|
||||
stride = PixelFormatExtensions::SizeInBytes(format);
|
||||
}
|
||||
return Stream(data, format, stride);
|
||||
}
|
||||
|
||||
MeshAccessor::Stream MeshAccessor::Attribute(VertexElement::Types attribute)
|
||||
{
|
||||
Span<byte> data;
|
||||
PixelFormat format = PixelFormat::Unknown;
|
||||
int32 stride = 0;
|
||||
for (int32 vbIndex = 0; vbIndex < 3 && format == PixelFormat::Unknown; vbIndex++)
|
||||
{
|
||||
static_assert((int32)MeshBufferType::Vertex0 == 1, "Update code.");
|
||||
const int32 idx = vbIndex + 1;
|
||||
auto layout = _layouts[idx];
|
||||
if (!layout)
|
||||
continue;
|
||||
for (const VertexElement& e : layout->GetElements())
|
||||
{
|
||||
auto& vb = _data[idx];
|
||||
if (e.Type == attribute && vb.IsValid())
|
||||
{
|
||||
data = vb.Slice(e.Offset);
|
||||
format = e.Format;
|
||||
stride = layout->GetStride();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Stream(data, format, stride);
|
||||
}
|
||||
|
||||
MeshBase::~MeshBase()
|
||||
{
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]);
|
||||
@@ -70,7 +325,12 @@ void MeshBase::SetBounds(const BoundingBox& box, const BoundingSphere& sphere)
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout)
|
||||
GPUVertexLayout* MeshBase::GetVertexLayout() const
|
||||
{
|
||||
return GPUVertexLayout::Get(Span<GPUBuffer*>(_vertexBuffers, MODEL_MAX_VB));
|
||||
}
|
||||
|
||||
bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<MODEL_MAX_VB>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>>& vbLayout)
|
||||
{
|
||||
CHECK_RETURN(vbData.HasItems() && vertices, true);
|
||||
CHECK_RETURN(ibData, true);
|
||||
@@ -126,9 +386,10 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
|
||||
_triangles = triangles;
|
||||
_vertices = vertices;
|
||||
_use16BitIndexBuffer = use16BitIndexBuffer;
|
||||
_cachedVertexBuffers[0].Clear();
|
||||
_cachedVertexBuffers[1].Clear();
|
||||
_cachedVertexBuffers[2].Clear();
|
||||
_cachedVertexBuffers[0].Release();
|
||||
_cachedVertexBuffers[1].Release();
|
||||
_cachedVertexBuffers[2].Release();
|
||||
Platform::MemoryClear(_cachedVertexLayouts, sizeof(_cachedVertexLayouts));
|
||||
|
||||
return false;
|
||||
|
||||
@@ -154,10 +415,11 @@ void MeshBase::Release()
|
||||
_triangles = 0;
|
||||
_vertices = 0;
|
||||
_use16BitIndexBuffer = false;
|
||||
_cachedIndexBuffer.Resize(0);
|
||||
_cachedVertexBuffers[0].Clear();
|
||||
_cachedVertexBuffers[1].Clear();
|
||||
_cachedVertexBuffers[2].Clear();
|
||||
_cachedIndexBuffer.Release();
|
||||
_cachedVertexBuffers[0].Release();
|
||||
_cachedVertexBuffers[1].Release();
|
||||
_cachedVertexBuffers[2].Release();
|
||||
Platform::MemoryClear(_cachedVertexLayouts, sizeof(_cachedVertexLayouts));
|
||||
}
|
||||
|
||||
bool MeshBase::UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices)
|
||||
@@ -243,7 +505,7 @@ bool MeshBase::Intersects(const Ray& ray, const Transform& transform, Real& dist
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const
|
||||
bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout) const
|
||||
{
|
||||
GPUBuffer* buffer = nullptr;
|
||||
switch (type)
|
||||
@@ -253,18 +515,24 @@ bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result) cons
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
buffer = _vertexBuffers[0];
|
||||
if (layout && buffer)
|
||||
*layout = buffer->GetVertexLayout();
|
||||
break;
|
||||
case MeshBufferType::Vertex1:
|
||||
buffer = _vertexBuffers[1];
|
||||
if (layout && buffer)
|
||||
*layout = buffer->GetVertexLayout();
|
||||
break;
|
||||
case MeshBufferType::Vertex2:
|
||||
buffer = _vertexBuffers[2];
|
||||
if (layout && buffer)
|
||||
*layout = buffer->GetVertexLayout();
|
||||
break;
|
||||
}
|
||||
return buffer && buffer->DownloadData(result);
|
||||
}
|
||||
|
||||
Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const
|
||||
Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout) const
|
||||
{
|
||||
GPUBuffer* buffer = nullptr;
|
||||
switch (type)
|
||||
@@ -274,17 +542,163 @@ Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
buffer = _vertexBuffers[0];
|
||||
if (layout && buffer)
|
||||
*layout = buffer->GetVertexLayout();
|
||||
break;
|
||||
case MeshBufferType::Vertex1:
|
||||
buffer = _vertexBuffers[1];
|
||||
if (layout && buffer)
|
||||
*layout = buffer->GetVertexLayout();
|
||||
break;
|
||||
case MeshBufferType::Vertex2:
|
||||
buffer = _vertexBuffers[2];
|
||||
if (layout && buffer)
|
||||
*layout = buffer->GetVertexLayout();
|
||||
break;
|
||||
}
|
||||
return buffer ? buffer->DownloadDataAsync(result) : nullptr;
|
||||
}
|
||||
|
||||
bool MeshBase::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const
|
||||
{
|
||||
if (_cachedVertexBuffers[0].IsInvalid())
|
||||
{
|
||||
PROFILE_CPU();
|
||||
auto model = GetModelBase();
|
||||
ScopeLock lock(model->Locker);
|
||||
if (model->IsVirtual())
|
||||
{
|
||||
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fetch chunk with data from drive/memory
|
||||
const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(GetLODIndex());
|
||||
if (model->LoadChunk(chunkIndex))
|
||||
return true;
|
||||
const auto chunk = model->GetChunk(chunkIndex);
|
||||
if (!chunk)
|
||||
{
|
||||
LOG(Error, "Missing chunk.");
|
||||
return true;
|
||||
}
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
ModelBase::MeshData meshData;
|
||||
|
||||
// Seek to find mesh location
|
||||
byte meshVersion = stream.ReadByte();
|
||||
for (int32 meshIndex = 0; meshIndex <= _index; meshIndex++)
|
||||
{
|
||||
if (model->LoadMesh(stream, meshVersion, model->GetMesh(meshIndex, _lodIndex), &meshData))
|
||||
return true;
|
||||
if (meshIndex != _index)
|
||||
continue;
|
||||
|
||||
// Cache mesh data
|
||||
_cachedVertexBufferCount = meshData.Vertices;
|
||||
_cachedIndexBufferCount = (int32)meshData.Triangles * 3;
|
||||
_cachedIndexBuffer.Copy((const byte*)meshData.IBData, _cachedIndexBufferCount * (int32)meshData.IBStride);
|
||||
for (int32 vb = 0; vb < meshData.VBData.Count(); vb++)
|
||||
{
|
||||
_cachedVertexBuffers[vb].Copy((const byte*)meshData.VBData[vb], (int32)(meshData.VBLayout[vb]->GetStride() * meshData.Vertices));
|
||||
_cachedVertexLayouts[vb] = meshData.VBLayout[vb];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
result.Link(_cachedIndexBuffer);
|
||||
count = _cachedIndexBufferCount;
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
result.Link(_cachedVertexBuffers[0]);
|
||||
count = _cachedVertexBufferCount;
|
||||
if (layout)
|
||||
*layout = _cachedVertexLayouts[0];
|
||||
break;
|
||||
case MeshBufferType::Vertex1:
|
||||
result.Link(_cachedVertexBuffers[1]);
|
||||
count = _cachedVertexBufferCount;
|
||||
if (layout)
|
||||
*layout = _cachedVertexLayouts[1];
|
||||
break;
|
||||
case MeshBufferType::Vertex2:
|
||||
result.Link(_cachedVertexBuffers[2]);
|
||||
count = _cachedVertexBufferCount;
|
||||
if (layout)
|
||||
*layout = _cachedVertexLayouts[2];
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshBase::DownloadData(Span<MeshBufferType> types, Array<BytesContainer, FixedAllocation<4>>& buffers, Array<GPUVertexLayout*, FixedAllocation<4>>& layouts, bool forceGpu) const
|
||||
{
|
||||
PROFILE_CPU();
|
||||
buffers.Resize(types.Length());
|
||||
layouts.Resize(types.Length());
|
||||
layouts.SetAll(nullptr);
|
||||
auto model = _model;
|
||||
model->Locker.Lock();
|
||||
|
||||
// Virtual assets always fetch from GPU memory
|
||||
forceGpu |= model->IsVirtual();
|
||||
|
||||
if (forceGpu)
|
||||
{
|
||||
if (!IsInitialized())
|
||||
{
|
||||
model->Locker.Unlock();
|
||||
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get data from GPU (start of series of async tasks that copy GPU-read data into staging buffers)
|
||||
Array<Task*, FixedAllocation<(int32)MeshBufferType::MAX>> tasks;
|
||||
for (int32 i = 0; i < types.Length(); i++)
|
||||
{
|
||||
auto task = DownloadDataGPUAsync(types[i], buffers[i], &layouts[i]);
|
||||
if (!task)
|
||||
{
|
||||
model->Locker.Unlock();
|
||||
return true;
|
||||
}
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Wait for async tasks
|
||||
model->Locker.Unlock();
|
||||
if (Task::WaitAll(tasks))
|
||||
{
|
||||
LOG(Error, "Failed to download mesh data from GPU.");
|
||||
return true;
|
||||
}
|
||||
model->Locker.Lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get data from CPU
|
||||
for (int32 i = 0; i < types.Length(); i++)
|
||||
{
|
||||
int32 count = 0;
|
||||
if (DownloadDataCPU(types[i], buffers[i], count, &layouts[i]))
|
||||
{
|
||||
model->Locker.Unlock();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model->Locker.Unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
void MeshBase::GetDrawCallGeometry(DrawCall& drawCall) const
|
||||
{
|
||||
drawCall.Geometry.IndexBuffer = _indexBuffer;
|
||||
@@ -324,4 +738,85 @@ bool MeshBase::UpdateTrianglesUShort(int32 triangleCount, const MArray* triangle
|
||||
return ::UpdateTriangles<uint16>(this, triangleCount, trianglesObj);
|
||||
}
|
||||
|
||||
MArray* MeshBase::DownloadIndexBuffer(bool forceGpu, MTypeObject* resultType, bool use16Bit)
|
||||
{
|
||||
ScopeLock lock(GetModelBase()->Locker);
|
||||
|
||||
// Get index buffer data from the mesh (CPU or GPU)
|
||||
MeshAccessor accessor;
|
||||
MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
|
||||
if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1)))
|
||||
return nullptr;
|
||||
auto indexStream = accessor.Index();
|
||||
if (!indexStream.IsValid())
|
||||
return nullptr;
|
||||
auto indexData = indexStream.GetData();
|
||||
auto indexCount = indexStream.GetCount();
|
||||
auto indexStride = indexStream.GetStride();
|
||||
|
||||
// Convert into managed array
|
||||
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), indexCount);
|
||||
void* managedArrayPtr = MCore::Array::GetAddress(result);
|
||||
if (use16Bit)
|
||||
{
|
||||
if (indexStride == sizeof(uint16))
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, indexData.Get(), indexData.Length());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dst = (uint16*)managedArrayPtr;
|
||||
auto src = (const uint32*)indexData.Get();
|
||||
for (int32 i = 0; i < indexCount; i++)
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (indexStride == sizeof(uint16))
|
||||
{
|
||||
auto dst = (uint32*)managedArrayPtr;
|
||||
auto src = (const uint16*)indexData.Get();
|
||||
for (int32 i = 0; i < indexCount; i++)
|
||||
dst[i] = src[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, indexData.Get(), indexData.Length());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MeshBase::DownloadData(int32 count, MeshBufferType* types, BytesContainer& buffer0, BytesContainer& buffer1, BytesContainer& buffer2, BytesContainer& buffer3, GPUVertexLayout*& layout0, GPUVertexLayout*& layout1, GPUVertexLayout*& layout2, GPUVertexLayout*& layout3, bool forceGpu) const
|
||||
{
|
||||
layout0 = layout1 = layout2 = layout3 = nullptr;
|
||||
Array<BytesContainer, FixedAllocation<4>> meshBuffers;
|
||||
Array<GPUVertexLayout*, FixedAllocation<4>> meshLayouts;
|
||||
if (DownloadData(Span<MeshBufferType>(types, count), meshBuffers, meshLayouts, forceGpu))
|
||||
return true;
|
||||
if (count > 0)
|
||||
{
|
||||
buffer0 = meshBuffers[0];
|
||||
layout0 = meshLayouts[0];
|
||||
if (count > 1)
|
||||
{
|
||||
buffer1 = meshBuffers[1];
|
||||
layout1 = meshLayouts[1];
|
||||
if (count > 2)
|
||||
{
|
||||
buffer2 = meshBuffers[2];
|
||||
layout2 = meshLayouts[2];
|
||||
if (count > 3)
|
||||
{
|
||||
buffer3 = meshBuffers[3];
|
||||
layout3 = meshLayouts[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -92,5 +92,84 @@ namespace FlaxEngine
|
||||
if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles)))
|
||||
throw new Exception("Failed to update mesh data.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// </summary>
|
||||
/// <remarks>If mesh index buffer format (see <see cref="IndexBufferFormat"/>) is <see cref="PixelFormat.R16_UInt"/> then it's faster to call .</remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
public uint[] DownloadIndexBuffer(bool forceGpu = false)
|
||||
{
|
||||
var result = (uint[])Internal_DownloadIndexBuffer(__unmanagedPtr, forceGpu, typeof(uint), false);
|
||||
if (result == null)
|
||||
throw new Exception("Failed to download mesh data.");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// </summary>
|
||||
/// <remarks>If mesh index buffer format (see <see cref="IndexBufferFormat"/>) is <see cref="PixelFormat.R32_UInt"/> then data won't be downloaded.</remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
public ushort[] DownloadIndexBufferUShort(bool forceGpu = false)
|
||||
{
|
||||
var result = (ushort[])Internal_DownloadIndexBuffer(__unmanagedPtr, forceGpu, typeof(ushort), true);
|
||||
if (result == null)
|
||||
throw new Exception("Failed to download mesh data.");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts mesh buffers data.
|
||||
/// </summary>
|
||||
/// <param name="types">List of buffers to load.</param>
|
||||
/// <param name="buffers">The result mesh buffers.</param>
|
||||
/// <param name="layouts">The result layouts of the vertex buffers.</param>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>True if failed, otherwise false</returns>
|
||||
public unsafe bool DownloadData(Span<MeshBufferType> types, out byte[][] buffers, out GPUVertexLayout[] layouts, bool forceGpu = false)
|
||||
{
|
||||
if (types == null)
|
||||
throw new ArgumentNullException(nameof(types));
|
||||
buffers = null;
|
||||
layouts = null;
|
||||
var count = types.Length;
|
||||
fixed (MeshBufferType* typesPtr = types)
|
||||
{
|
||||
if (Internal_DownloadData(__unmanagedPtr,
|
||||
count, typesPtr,
|
||||
out byte[] buffer0, out byte[] buffer1, out byte[] buffer2, out byte[] buffer3,
|
||||
out GPUVertexLayout layout0, out GPUVertexLayout layout1, out GPUVertexLayout layout2, out GPUVertexLayout layout3,
|
||||
forceGpu,
|
||||
out int __buffer0Count, out int __buffer1Count, out int __buffer2Count, out int __buffer3Count
|
||||
))
|
||||
return true;
|
||||
buffers = new byte[count][];
|
||||
layouts = new GPUVertexLayout[count];
|
||||
if (count > 0)
|
||||
{
|
||||
buffers[0] = buffer0;
|
||||
layouts[0] = layout0;
|
||||
if (count > 1)
|
||||
{
|
||||
buffers[1] = buffer1;
|
||||
layouts[1] = layout1;
|
||||
if (count > 2)
|
||||
{
|
||||
buffers[2] = buffer2;
|
||||
layouts[2] = layout2;
|
||||
if (count > 3)
|
||||
{
|
||||
buffers[3] = buffer3;
|
||||
layouts[3] = layout3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "Engine/Core/Math/BoundingBox.h"
|
||||
#include "Engine/Core/Math/BoundingSphere.h"
|
||||
#include "Engine/Core/Types/DataContainer.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Graphics/Enums.h"
|
||||
#include "Engine/Graphics/Models/Types.h"
|
||||
#include "Engine/Level/Types.h"
|
||||
@@ -47,12 +46,13 @@ protected:
|
||||
bool _use16BitIndexBuffer = false;
|
||||
bool _hasBounds = false;
|
||||
|
||||
GPUBuffer* _vertexBuffers[3] = {};
|
||||
GPUBuffer* _vertexBuffers[MODEL_MAX_VB] = {};
|
||||
GPUBuffer* _indexBuffer = nullptr;
|
||||
|
||||
mutable Array<byte> _cachedVertexBuffers[3];
|
||||
mutable Array<byte> _cachedIndexBuffer;
|
||||
mutable int32 _cachedIndexBufferCount = 0;
|
||||
mutable BytesContainer _cachedVertexBuffers[MODEL_MAX_VB];
|
||||
mutable GPUVertexLayout* _cachedVertexLayouts[MODEL_MAX_VB] = {};
|
||||
mutable BytesContainer _cachedIndexBuffer;
|
||||
mutable int32 _cachedIndexBufferCount = 0, _cachedVertexBufferCount = 0;
|
||||
|
||||
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
||||
CollisionProxy _collisionProxy;
|
||||
@@ -177,19 +177,19 @@ public:
|
||||
/// Sets the mesh bounds.
|
||||
/// </summary>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
void SetBounds(const BoundingBox& box);
|
||||
API_FUNCTION() void SetBounds(API_PARAM(ref) const BoundingBox& box);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mesh bounds.
|
||||
/// </summary>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
/// <param name="sphere">The bounding sphere.</param>
|
||||
void SetBounds(const BoundingBox& box, const BoundingSphere& sphere);
|
||||
API_FUNCTION() void SetBounds(API_PARAM(ref) const BoundingBox& box, API_PARAM(ref) const BoundingSphere& sphere);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index buffer.
|
||||
/// </summary>
|
||||
FORCE_INLINE GPUBuffer* GetIndexBuffer() const
|
||||
API_FUNCTION() FORCE_INLINE GPUBuffer* GetIndexBuffer() const
|
||||
{
|
||||
return _indexBuffer;
|
||||
}
|
||||
@@ -199,23 +199,29 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="index">The bind slot index.</param>
|
||||
/// <returns>The buffer or null if not used.</returns>
|
||||
FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const
|
||||
API_FUNCTION() FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const
|
||||
{
|
||||
return _vertexBuffers[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertex buffers layout. Made out of all buffers used by this mesh.
|
||||
/// </summary>
|
||||
/// <returns>The vertex layout.</returns>
|
||||
API_PROPERTY() GPUVertexLayout* GetVertexLayout() const;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes the mesh buffers.
|
||||
/// </summary>
|
||||
/// <param name="vertices">Amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangles">Amount of triangles in the index buffer.</param>
|
||||
/// <param name="vbData">Array with pointers to vertex buffers initial data (layout defined by <paramref name="vertexLayout"/>).</param>
|
||||
/// <param name="vbData">Array with pointers to vertex buffers initial data (layout defined by <paramref name="vbLayout"/>).</param>
|
||||
/// <param name="ibData">Pointer to index buffer data. Data is uint16 or uint32 depending on <paramref name="use16BitIndexBuffer"/> value.</param>
|
||||
/// <param name="use16BitIndexBuffer">True to use 16-bit indices for the index buffer (true: uint16, false: uint32).</param>
|
||||
/// <param name="vbLayout">Layout descriptors for the vertex buffers attributes (one for each vertex buffer).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
virtual bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout);
|
||||
API_FUNCTION(Sealed) virtual bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<MODEL_MAX_VB>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>>& vbLayout);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the mesh data (GPU buffers and local cache).
|
||||
@@ -290,16 +296,18 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <param name="layout">The result layout of the vertex buffer (optional).</param>
|
||||
/// <returns>True if failed, otherwise false</returns>
|
||||
bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const;
|
||||
bool DownloadDataGPU(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Extracts mesh buffer data from GPU in the async task.
|
||||
/// </summary>
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <param name="layout">The result layout of the vertex buffer (optional).</param>
|
||||
/// <returns>Created async task used to gather the buffer data.</returns>
|
||||
Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const;
|
||||
Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Extract mesh buffer data from CPU. Cached internally.
|
||||
@@ -307,8 +315,19 @@ public:
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <param name="count">The amount of items inside the result buffer.</param>
|
||||
/// <param name="layout">The result layout of the vertex buffer (optional).</param>
|
||||
/// <returns>True if failed, otherwise false</returns>
|
||||
virtual bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const = 0;
|
||||
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Extracts mesh buffers data.
|
||||
/// </summary>
|
||||
/// <param name="types">List of buffers to load.</param>
|
||||
/// <param name="buffers">The result mesh buffers.</param>
|
||||
/// <param name="layouts">The result layouts of the vertex buffers.</param>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>True if failed, otherwise false</returns>
|
||||
bool DownloadData(Span<MeshBufferType> types, Array<BytesContainer, FixedAllocation<4>>& buffers, Array<GPUVertexLayout*, FixedAllocation<4>>& layouts, bool forceGpu = false) const;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -423,5 +442,7 @@ private:
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj);
|
||||
API_FUNCTION(NoProxy) MArray* DownloadIndexBuffer(bool forceGpu, MTypeObject* resultType, bool use16Bit);
|
||||
API_FUNCTION(NoProxy) bool DownloadData(int32 count, MeshBufferType* types, API_PARAM(Out) BytesContainer& buffer0, API_PARAM(Out) BytesContainer& buffer1, API_PARAM(Out) BytesContainer& buffer2, API_PARAM(Out) BytesContainer& buffer3, API_PARAM(Out) GPUVertexLayout*& layout0, API_PARAM(Out) GPUVertexLayout*& layout1, API_PARAM(Out) GPUVertexLayout*& layout2, API_PARAM(Out) GPUVertexLayout*& layout3, bool forceGpu) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "MeshDeformation.h"
|
||||
#include "MeshAccessor.h"
|
||||
#include "Engine/Graphics/Models/MeshBase.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
|
||||
@@ -29,6 +30,11 @@ FORCE_INLINE static uint32 GetKey(int32 lodIndex, int32 meshIndex, MeshBufferTyp
|
||||
return key.Value;
|
||||
}
|
||||
|
||||
bool MeshDeformationData::LoadMeshAccessor(MeshAccessor& accessor) const
|
||||
{
|
||||
return accessor.LoadBuffer(Type, ToSpan(VertexBuffer.Data), VertexBuffer.GetLayout());
|
||||
}
|
||||
|
||||
void MeshDeformation::GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const
|
||||
{
|
||||
const auto key = GetKey(lodIndex, meshIndex, MeshBufferType::Vertex0);
|
||||
|
||||
@@ -32,6 +32,8 @@ struct MeshDeformationData
|
||||
~MeshDeformationData()
|
||||
{
|
||||
}
|
||||
|
||||
bool LoadMeshAccessor(class MeshAccessor& accessor) const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
#include "Engine/Core/Math/BoundingSphere.h"
|
||||
#include "Engine/Animations/CurveSerialization.h"
|
||||
#include "Engine/Serialization/WriteStream.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
|
||||
|
||||
void MeshData::Clear()
|
||||
{
|
||||
@@ -71,6 +68,7 @@ void MeshData::Release()
|
||||
BlendShapes.Resize(0);
|
||||
}
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount)
|
||||
{
|
||||
Positions.Resize(verticesCount, false);
|
||||
@@ -160,6 +158,7 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb
|
||||
vb1++;
|
||||
}
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
void MeshData::SetIndexBuffer(void* data, uint32 indicesCount)
|
||||
{
|
||||
@@ -181,237 +180,52 @@ void MeshData::SetIndexBuffer(void* data, uint32 indicesCount)
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshData::Pack2Model(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
LOG(Error, "Invalid input.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cache size
|
||||
uint32 verticiecCount = Positions.Count();
|
||||
uint32 indicesCount = Indices.Count();
|
||||
uint32 trianglesCount = indicesCount / 3;
|
||||
bool use16Bit = indicesCount <= MAX_uint16;
|
||||
if (verticiecCount == 0 || trianglesCount == 0 || indicesCount % 3 != 0)
|
||||
{
|
||||
LOG(Error, "Empty mesh! Triangles: {0}, Verticies: {1}.", trianglesCount, verticiecCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate data structure
|
||||
bool hasUVs = UVs.HasItems();
|
||||
if (hasUVs && UVs.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("UVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasNormals = Normals.HasItems();
|
||||
if (hasNormals && Normals.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
|
||||
return true;
|
||||
}
|
||||
bool hasTangents = Tangents.HasItems();
|
||||
if (hasTangents && Tangents.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
|
||||
return true;
|
||||
}
|
||||
bool hasBitangentSigns = BitangentSigns.HasItems();
|
||||
if (hasBitangentSigns && BitangentSigns.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
|
||||
return true;
|
||||
}
|
||||
bool hasLightmapUVs = LightmapUVs.HasItems();
|
||||
if (hasLightmapUVs && LightmapUVs.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasVertexColors = Colors.HasItems();
|
||||
if (hasVertexColors && Colors.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Colors"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vertices
|
||||
stream->WriteUint32(verticiecCount);
|
||||
|
||||
// Triangles
|
||||
stream->WriteUint32(trianglesCount);
|
||||
|
||||
// Vertex Buffer 0
|
||||
stream->WriteBytes(Positions.Get(), sizeof(Float3) * verticiecCount);
|
||||
|
||||
// Vertex Buffer 1
|
||||
VB1ElementType vb1;
|
||||
for (uint32 i = 0; i < verticiecCount; i++)
|
||||
{
|
||||
// Get vertex components
|
||||
Float2 uv = hasUVs ? UVs[i] : Float2::Zero;
|
||||
Float3 normal = hasNormals ? Normals[i] : Float3::UnitZ;
|
||||
Float3 tangent = hasTangents ? Tangents[i] : Float3::UnitX;
|
||||
Float2 lightmapUV = hasLightmapUVs ? LightmapUVs[i] : Float2::Zero;
|
||||
Float3 bitangentSign = hasBitangentSigns ? BitangentSigns[i] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent);
|
||||
|
||||
// Write vertex
|
||||
vb1.TexCoord = Half2(uv);
|
||||
vb1.Normal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast<byte>(bitangentSign < 0 ? 1 : 0));
|
||||
vb1.LightmapUVs = Half2(lightmapUV);
|
||||
stream->WriteBytes(&vb1, sizeof(vb1));
|
||||
}
|
||||
|
||||
// Vertex Buffer 2
|
||||
stream->WriteBool(hasVertexColors);
|
||||
if (hasVertexColors)
|
||||
{
|
||||
VB2ElementType vb2;
|
||||
for (uint32 i = 0; i < verticiecCount; i++)
|
||||
{
|
||||
vb2.Color = Color32(Colors[i]);
|
||||
stream->WriteBytes(&vb2, sizeof(vb2));
|
||||
}
|
||||
}
|
||||
|
||||
// Index Buffer
|
||||
if (use16Bit)
|
||||
{
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
stream->WriteUint16(Indices[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream->WriteBytes(Indices.Get(), sizeof(uint32) * indicesCount);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshData::Pack2SkinnedModel(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
LOG(Error, "Invalid input.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cache size
|
||||
uint32 verticiecCount = Positions.Count();
|
||||
uint32 indicesCount = Indices.Count();
|
||||
uint32 trianglesCount = indicesCount / 3;
|
||||
bool use16Bit = indicesCount <= MAX_uint16;
|
||||
if (verticiecCount == 0 || trianglesCount == 0 || indicesCount % 3 != 0)
|
||||
{
|
||||
LOG(Error, "Empty mesh! Triangles: {0}, Verticies: {1}.", trianglesCount, verticiecCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate data structure
|
||||
bool hasUVs = UVs.HasItems();
|
||||
if (hasUVs && UVs.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT( "UVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasNormals = Normals.HasItems();
|
||||
if (hasNormals && Normals.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
|
||||
return true;
|
||||
}
|
||||
bool hasTangents = Tangents.HasItems();
|
||||
if (hasTangents && Tangents.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
|
||||
return true;
|
||||
}
|
||||
bool hasBitangentSigns = BitangentSigns.HasItems();
|
||||
if (hasBitangentSigns && BitangentSigns.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
|
||||
return true;
|
||||
}
|
||||
if (BlendIndices.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices"));
|
||||
return true;
|
||||
}
|
||||
if (BlendWeights.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendWeights"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vertices
|
||||
stream->WriteUint32(verticiecCount);
|
||||
|
||||
// Triangles
|
||||
stream->WriteUint32(trianglesCount);
|
||||
|
||||
// Blend Shapes
|
||||
stream->WriteUint16(BlendShapes.Count());
|
||||
for (const auto& blendShape : BlendShapes)
|
||||
{
|
||||
stream->WriteBool(blendShape.UseNormals);
|
||||
stream->WriteUint32(blendShape.MinVertexIndex);
|
||||
stream->WriteUint32(blendShape.MaxVertexIndex);
|
||||
stream->WriteUint32(blendShape.Vertices.Count());
|
||||
stream->WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
|
||||
// Vertex Buffer
|
||||
VB0SkinnedElementType vb;
|
||||
for (uint32 i = 0; i < verticiecCount; i++)
|
||||
{
|
||||
// Get vertex components
|
||||
Float2 uv = hasUVs ? UVs[i] : Float2::Zero;
|
||||
Float3 normal = hasNormals ? Normals[i] : Float3::UnitZ;
|
||||
Float3 tangent = hasTangents ? Tangents[i] : Float3::UnitX;
|
||||
Float3 bitangentSign = hasBitangentSigns ? BitangentSigns[i] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent);
|
||||
Int4 blendIndices = BlendIndices[i];
|
||||
Float4 blendWeights = BlendWeights[i];
|
||||
|
||||
// Write vertex
|
||||
vb.Position = Positions[i];
|
||||
vb.TexCoord = Half2(uv);
|
||||
vb.Normal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
vb.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast<byte>(bitangentSign < 0 ? 1 : 0));
|
||||
vb.BlendIndices = Color32(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
|
||||
vb.BlendWeights = Half4(blendWeights);
|
||||
stream->WriteBytes(&vb, sizeof(vb));
|
||||
}
|
||||
|
||||
// Index Buffer
|
||||
if (use16Bit)
|
||||
{
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
stream->WriteUint16(Indices[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream->WriteBytes(Indices.Get(), sizeof(uint32) * indicesCount);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MeshData::CalculateBox(BoundingBox& result) const
|
||||
{
|
||||
if (Positions.HasItems())
|
||||
BoundingBox::FromPoints(Positions.Get(), Positions.Count(), result);
|
||||
else
|
||||
result = BoundingBox::Zero;
|
||||
}
|
||||
|
||||
void MeshData::CalculateSphere(BoundingSphere& result) const
|
||||
{
|
||||
if (Positions.HasItems())
|
||||
BoundingSphere::FromPoints(Positions.Get(), Positions.Count(), result);
|
||||
else
|
||||
result = BoundingSphere::Empty;
|
||||
}
|
||||
|
||||
void MeshData::CalculateBounds(BoundingBox& box, BoundingSphere& sphere) const
|
||||
{
|
||||
if (Positions.HasItems())
|
||||
{
|
||||
// Merged code of BoundingBox::FromPoints and BoundingSphere::FromPoints within a single loop
|
||||
const Float3* points = Positions.Get();
|
||||
const int32 pointsCount = Positions.Count();
|
||||
Float3 min = points[0];
|
||||
Float3 max = min;
|
||||
Float3 center = min;
|
||||
for (int32 i = 1; i < pointsCount; i++)
|
||||
Float3::Add(points[i], center, center);
|
||||
center /= (float)pointsCount;
|
||||
float radiusSq = Float3::DistanceSquared(center, min);
|
||||
for (int32 i = 1; i < pointsCount; i++)
|
||||
{
|
||||
Float3::Min(min, points[i], min);
|
||||
Float3::Max(max, points[i], max);
|
||||
const float distance = Float3::DistanceSquared(center, points[i]);
|
||||
if (distance > radiusSq)
|
||||
radiusSq = distance;
|
||||
}
|
||||
box = BoundingBox(min, max);
|
||||
sphere = BoundingSphere(center, Math::Sqrt(radiusSq));
|
||||
}
|
||||
else
|
||||
{
|
||||
box = BoundingBox::Zero;
|
||||
sphere = BoundingSphere::Empty;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::TransformBuffer(const Matrix& matrix)
|
||||
@@ -616,276 +430,3 @@ void ModelData::TransformBuffer(const Matrix& matrix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool ModelData::Pack2ModelHeader(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
Log::ArgumentNullException();
|
||||
return true;
|
||||
}
|
||||
const int32 lodCount = GetLODsCount();
|
||||
if (lodCount == 0 || lodCount > MODEL_MAX_LODS)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException();
|
||||
return true;
|
||||
}
|
||||
if (Materials.IsEmpty())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("MaterialSlots"), TEXT("Material slots collection cannot be empty."));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
stream->WriteInt32(Materials.Count());
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < Materials.Count(); materialSlotIndex++)
|
||||
{
|
||||
auto& slot = Materials[materialSlotIndex];
|
||||
|
||||
stream->Write(slot.AssetID);
|
||||
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
|
||||
stream->WriteString(slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
stream->WriteByte(lodCount);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream->WriteFloat(lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
if (meshes == 0)
|
||||
{
|
||||
LOG(Warning, "Empty LOD.");
|
||||
return true;
|
||||
}
|
||||
if (meshes > MODEL_MAX_MESHES)
|
||||
{
|
||||
LOG(Warning, "Too many meshes per LOD.");
|
||||
return true;
|
||||
}
|
||||
stream->WriteUint16(meshes);
|
||||
|
||||
// For each mesh
|
||||
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
|
||||
{
|
||||
auto& mesh = *lod.Meshes[meshIndex];
|
||||
|
||||
// Material Slot
|
||||
stream->WriteInt32(mesh.MaterialSlotIndex);
|
||||
|
||||
// Box
|
||||
BoundingBox box;
|
||||
mesh.CalculateBox(box);
|
||||
stream->WriteBoundingBox(box);
|
||||
|
||||
// Sphere
|
||||
BoundingSphere sphere;
|
||||
mesh.CalculateSphere(sphere);
|
||||
stream->WriteBoundingSphere(sphere);
|
||||
|
||||
// TODO: calculate Sphere and Box at once - make it faster using SSE
|
||||
|
||||
// Has Lightmap UVs
|
||||
stream->WriteBool(mesh.LightmapUVs.HasItems());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
Log::ArgumentNullException();
|
||||
return true;
|
||||
}
|
||||
const int32 lodCount = GetLODsCount();
|
||||
if (lodCount > MODEL_MAX_LODS)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Version
|
||||
stream->WriteByte(1);
|
||||
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
stream->WriteInt32(Materials.Count());
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < Materials.Count(); materialSlotIndex++)
|
||||
{
|
||||
auto& slot = Materials[materialSlotIndex];
|
||||
stream->Write(slot.AssetID);
|
||||
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
|
||||
stream->WriteString(slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
stream->WriteByte(lodCount);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream->WriteFloat(lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
if (meshes > MODEL_MAX_MESHES)
|
||||
{
|
||||
LOG(Warning, "Too many meshes per LOD.");
|
||||
return true;
|
||||
}
|
||||
stream->WriteUint16(meshes);
|
||||
|
||||
// For each mesh
|
||||
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
|
||||
{
|
||||
auto& mesh = *lod.Meshes[meshIndex];
|
||||
|
||||
// Material Slot
|
||||
stream->WriteInt32(mesh.MaterialSlotIndex);
|
||||
|
||||
// Box
|
||||
BoundingBox box;
|
||||
mesh.CalculateBox(box);
|
||||
stream->WriteBoundingBox(box);
|
||||
|
||||
// Sphere
|
||||
BoundingSphere sphere;
|
||||
mesh.CalculateSphere(sphere);
|
||||
stream->WriteBoundingSphere(sphere);
|
||||
|
||||
// TODO: calculate Sphere and Box at once - make it faster using SSE
|
||||
|
||||
// Blend Shapes
|
||||
const int32 blendShapes = mesh.BlendShapes.Count();
|
||||
stream->WriteUint16(blendShapes);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++)
|
||||
{
|
||||
auto& blendShape = mesh.BlendShapes[blendShapeIndex];
|
||||
stream->WriteString(blendShape.Name, 13);
|
||||
stream->WriteFloat(blendShape.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
stream->WriteInt32(Skeleton.Nodes.Count());
|
||||
|
||||
// For each node
|
||||
for (int32 nodeIndex = 0; nodeIndex < Skeleton.Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
auto& node = Skeleton.Nodes[nodeIndex];
|
||||
|
||||
stream->Write(node.ParentIndex);
|
||||
stream->WriteTransform(node.LocalTransform);
|
||||
stream->WriteString(node.Name, 71);
|
||||
}
|
||||
|
||||
stream->WriteInt32(Skeleton.Bones.Count());
|
||||
|
||||
// For each bone
|
||||
for (int32 boneIndex = 0; boneIndex < Skeleton.Bones.Count(); boneIndex++)
|
||||
{
|
||||
auto& bone = Skeleton.Bones[boneIndex];
|
||||
|
||||
stream->Write(bone.ParentIndex);
|
||||
stream->Write(bone.NodeIndex);
|
||||
stream->WriteTransform(bone.LocalTransform);
|
||||
stream->Write(bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
stream->WriteInt32(0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr || animIndex < 0 || animIndex >= Animations.Count())
|
||||
{
|
||||
Log::ArgumentNullException();
|
||||
return true;
|
||||
}
|
||||
auto& anim = Animations.Get()[animIndex];
|
||||
if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance)
|
||||
{
|
||||
Log::InvalidOperationException(TEXT("Invalid animation duration."));
|
||||
return true;
|
||||
}
|
||||
if (anim.Channels.IsEmpty())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty."));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Info
|
||||
stream->WriteInt32(103); // Header version (for fast version upgrades without serialization format change)
|
||||
stream->WriteDouble(anim.Duration);
|
||||
stream->WriteDouble(anim.FramesPerSecond);
|
||||
stream->WriteByte((byte)anim.RootMotionFlags);
|
||||
stream->WriteString(anim.RootNodeName, 13);
|
||||
|
||||
// Animation channels
|
||||
stream->WriteInt32(anim.Channels.Count());
|
||||
for (int32 i = 0; i < anim.Channels.Count(); i++)
|
||||
{
|
||||
auto& channel = anim.Channels[i];
|
||||
stream->WriteString(channel.NodeName, 172);
|
||||
Serialization::Serialize(*stream, channel.Position);
|
||||
Serialization::Serialize(*stream, channel.Rotation);
|
||||
Serialization::Serialize(*stream, channel.Scale);
|
||||
}
|
||||
|
||||
// Animation events
|
||||
stream->WriteInt32(anim.Events.Count());
|
||||
for (auto& e : anim.Events)
|
||||
{
|
||||
stream->WriteString(e.First, 172);
|
||||
stream->WriteInt32(e.Second.GetKeyframes().Count());
|
||||
for (const auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
stream->WriteFloat(k.Time);
|
||||
stream->WriteFloat(k.Value.Duration);
|
||||
stream->WriteStringAnsi(k.Value.TypeName, 17);
|
||||
stream->WriteJson(k.Value.JsonData);
|
||||
}
|
||||
}
|
||||
|
||||
// Nested animations
|
||||
stream->WriteInt32(0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -39,9 +39,11 @@ public:
|
||||
Array<Float3> Positions;
|
||||
|
||||
/// <summary>
|
||||
/// Texture coordinates
|
||||
/// Texture coordinates (list of channels)
|
||||
/// </summary>
|
||||
// TODO: multiple UVs
|
||||
Array<Float2> UVs;
|
||||
Array<Float2> LightmapUVs; // TODO: remove this and move to UVs
|
||||
|
||||
/// <summary>
|
||||
/// Normals vector
|
||||
@@ -65,11 +67,6 @@ public:
|
||||
/// </summary>
|
||||
Array<uint32> Indices;
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap UVs
|
||||
/// </summary>
|
||||
Array<Float2> LightmapUVs;
|
||||
|
||||
/// <summary>
|
||||
/// Vertex colors
|
||||
/// </summary>
|
||||
@@ -91,12 +88,17 @@ public:
|
||||
Array<BlendShape> BlendShapes;
|
||||
|
||||
/// <summary>
|
||||
/// Global translation for this mesh to be at it's local origin.
|
||||
/// Lightmap texture coordinates channel index. Value -1 indicates that channel is not available.
|
||||
/// </summary>
|
||||
int32 LightmapUVsIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Global translation for this mesh to be at its local origin.
|
||||
/// </summary>
|
||||
Vector3 OriginTranslation = Vector3::Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Orientation for this mesh at it's local origin.
|
||||
/// Orientation for this mesh at its local origin.
|
||||
/// </summary>
|
||||
Quaternion OriginOrientation = Quaternion::Identity;
|
||||
|
||||
@@ -105,15 +107,6 @@ public:
|
||||
/// </summary>
|
||||
Vector3 Scaling = Vector3::One;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Determines whether this instance has any mesh data.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool HasData() const
|
||||
{
|
||||
return Indices.HasItems();
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Clear arrays
|
||||
@@ -142,6 +135,7 @@ public:
|
||||
void Release();
|
||||
|
||||
public:
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
/// <summary>
|
||||
/// Init from model vertices array
|
||||
/// </summary>
|
||||
@@ -165,6 +159,7 @@ public:
|
||||
/// <param name="vb2">Array of data for vertex buffer 2</param>
|
||||
/// <param name="verticesCount">Amount of vertices</param>
|
||||
void InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount);
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
/// <summary>
|
||||
/// Sets the index buffer data.
|
||||
@@ -174,20 +169,6 @@ public:
|
||||
void SetIndexBuffer(void* data, uint32 indicesCount);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Pack mesh data to the stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2Model(WriteStream* stream) const;
|
||||
|
||||
/// <summary>
|
||||
/// Pack skinned mesh data to the stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2SkinnedModel(WriteStream* stream) const;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate bounding box for the mesh
|
||||
/// </summary>
|
||||
@@ -200,9 +181,14 @@ public:
|
||||
/// <param name="result">Output sphere</param>
|
||||
void CalculateSphere(BoundingSphere& result) const;
|
||||
|
||||
public:
|
||||
#if COMPILE_WITH_MODEL_TOOL
|
||||
/// <summary>
|
||||
/// Calculates bounding box and sphere for the mesh.
|
||||
/// </summary>
|
||||
/// <param name="box">Output box.</param>
|
||||
/// <param name="sphere">Output sphere.</param>
|
||||
void CalculateBounds(BoundingBox& box, BoundingSphere& sphere) const;
|
||||
|
||||
#if COMPILE_WITH_MODEL_TOOL
|
||||
/// <summary>
|
||||
/// Generate lightmap uvs for the mesh entry
|
||||
/// </summary>
|
||||
@@ -246,7 +232,6 @@ public:
|
||||
/// </summary>
|
||||
/// <returns>The area sum of all mesh triangles.</returns>
|
||||
float CalculateTrianglesArea() const;
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
@@ -439,23 +424,6 @@ public:
|
||||
/// </summary>
|
||||
Array<AnimationData> Animations;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the valid level of details count.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 GetLODsCount() const
|
||||
{
|
||||
return LODs.Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance has valid skeleton structure.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool HasSkeleton() const
|
||||
{
|
||||
return Skeleton.Bones.HasItems();
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Automatically calculates the screen size for every model LOD for a proper transitions.
|
||||
@@ -467,29 +435,4 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix to use for the transformation.</param>
|
||||
void TransformBuffer(const Matrix& matrix);
|
||||
|
||||
#if USE_EDITOR
|
||||
public:
|
||||
/// <summary>
|
||||
/// Pack mesh data to the header stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2ModelHeader(WriteStream* stream) const;
|
||||
|
||||
/// <summary>
|
||||
/// Pack skinned mesh data to the header stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2SkinnedModelHeader(WriteStream* stream) const;
|
||||
|
||||
/// <summary>
|
||||
/// Pack animation data to the header stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <param name="animIndex">Index of animation.</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2AnimationHeader(WriteStream* stream, int32 animIndex = 0) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SkinnedMesh.h"
|
||||
#include "MeshAccessor.h"
|
||||
#include "MeshDeformation.h"
|
||||
#include "ModelInstanceEntry.h"
|
||||
#include "Engine/Content/Assets/Material.h"
|
||||
@@ -13,12 +14,11 @@
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Threading/Task.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
|
||||
GPUVertexLayout* VB0SkinnedElementType2::GetLayout()
|
||||
{
|
||||
return GPUVertexLayout::Get({
|
||||
@@ -31,6 +31,125 @@ GPUVertexLayout* VB0SkinnedElementType2::GetLayout()
|
||||
});
|
||||
}
|
||||
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
namespace
|
||||
{
|
||||
bool UpdateMesh(MeshBase* mesh, uint32 vertexCount, uint32 triangleCount, PixelFormat indexFormat, const Float3* vertices, const void* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
|
||||
{
|
||||
auto model = mesh->GetModelBase();
|
||||
CHECK_RETURN(model && model->IsVirtual(), true);
|
||||
CHECK_RETURN(triangles && vertices, true);
|
||||
MeshAccessor accessor;
|
||||
|
||||
// Index Buffer
|
||||
{
|
||||
if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount, indexFormat))
|
||||
return true;
|
||||
auto indexStream = accessor.Index();
|
||||
ASSERT(indexStream.IsLinear(indexFormat));
|
||||
indexStream.SetLinear(triangles);
|
||||
}
|
||||
|
||||
// Vertex Buffer
|
||||
{
|
||||
GPUVertexLayout::Elements vb0elements;
|
||||
vb0elements.Add({ VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float });
|
||||
if (normals)
|
||||
{
|
||||
vb0elements.Add({ VertexElement::Types::Normal, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
if (tangents)
|
||||
vb0elements.Add({ VertexElement::Types::Tangent, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
}
|
||||
vb0elements.Add({ VertexElement::Types::BlendIndices, 0, 0, 0, PixelFormat::R8G8B8A8_UInt });
|
||||
vb0elements.Add({ VertexElement::Types::BlendWeights, 0, 0, 0, PixelFormat::R16G16B16A16_Float });
|
||||
if (uvs)
|
||||
vb0elements.Add({ VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R16G16_Float });
|
||||
if (colors)
|
||||
vb0elements.Add({ VertexElement::Types::Color, 0, 0, 0, PixelFormat::R8G8B8A8_UNorm });
|
||||
|
||||
GPUVertexLayout* vb0layout = GPUVertexLayout::Get(vb0elements);
|
||||
if (accessor.AllocateBuffer(MeshBufferType::Vertex0, vertexCount, vb0layout))
|
||||
return true;
|
||||
|
||||
auto positionStream = accessor.Position();
|
||||
ASSERT(positionStream.IsLinear(PixelFormat::R32G32B32_Float));
|
||||
positionStream.SetLinear(vertices);
|
||||
if (normals)
|
||||
{
|
||||
auto normalStream = accessor.Normal();
|
||||
if (tangents)
|
||||
{
|
||||
auto tangentStream = accessor.Tangent();
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
const Float3 normal = normals[i];
|
||||
const Float3 tangent = tangents[i];
|
||||
Float3 n;
|
||||
Float4 t;
|
||||
RenderTools::CalculateTangentFrame(n, t, normal, tangent);
|
||||
normalStream.SetFloat3(i, n);
|
||||
tangentStream.SetFloat4(i, t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
const Float3 normal = normals[i];
|
||||
Float3 n;
|
||||
Float4 t;
|
||||
RenderTools::CalculateTangentFrame(n, t, normal);
|
||||
normalStream.SetFloat3(i, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
auto blendIndicesStream = accessor.BlendIndices();
|
||||
auto blendWeightsStream = accessor.BlendWeights();
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
blendIndicesStream.SetFloat4(i, blendIndices[i]);
|
||||
blendWeightsStream.SetFloat4(i, blendWeights[i]);
|
||||
}
|
||||
}
|
||||
if (uvs)
|
||||
{
|
||||
auto uvsStream = accessor.TexCoord();
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
uvsStream.SetFloat2(i, uvs[i]);
|
||||
}
|
||||
if (colors)
|
||||
{
|
||||
auto colorStream = accessor.Color();
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
colorStream.SetFloat4(i, Float4(Color(colors[i]))); // TODO: optimize with direct memory copy
|
||||
}
|
||||
}
|
||||
|
||||
return accessor.UpdateMesh(mesh);
|
||||
}
|
||||
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
template<typename IndexType>
|
||||
bool UpdateMesh(SkinnedMesh* mesh, uint32 vertexCount, uint32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
|
||||
{
|
||||
ASSERT((uint32)MCore::Array::GetLength(verticesObj) >= vertexCount);
|
||||
ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount);
|
||||
auto vertices = MCore::Array::GetAddress<Float3>(verticesObj);
|
||||
auto triangles = MCore::Array::GetAddress<IndexType>(trianglesObj);
|
||||
const PixelFormat indexFormat = sizeof(IndexType) == 4 ? PixelFormat::R32_UInt : PixelFormat::R16_UInt;
|
||||
const auto blendIndices = MCore::Array::GetAddress<Int4>(blendIndicesObj);
|
||||
const auto blendWeights = MCore::Array::GetAddress<Float4>(blendWeightsObj);
|
||||
const auto normals = normalsObj ? MCore::Array::GetAddress<Float3>(normalsObj) : nullptr;
|
||||
const auto tangents = tangentsObj ? MCore::Array::GetAddress<Float3>(tangentsObj) : nullptr;
|
||||
const auto uvs = uvObj ? MCore::Array::GetAddress<Float2>(uvObj) : nullptr;
|
||||
const auto colors = colorsObj ? MCore::Array::GetAddress<Color32>(colorsObj) : nullptr;
|
||||
return UpdateMesh(mesh, vertexCount, triangleCount, indexFormat, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SkeletonData::Swap(SkeletonData& other)
|
||||
{
|
||||
Nodes.Swap(other.Nodes);
|
||||
@@ -99,14 +218,39 @@ bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const
|
||||
Array<const void*, FixedAllocation<3>> vbData;
|
||||
vbData.Add(vb0);
|
||||
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
vbLayout.Add(VB0SkinnedElementType::GetLayout());
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib)
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib)
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib)
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, true);
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices)
|
||||
{
|
||||
// Setup GPU resources
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices);
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
if (!failed)
|
||||
{
|
||||
// Calculate mesh bounds
|
||||
@@ -117,6 +261,16 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0
|
||||
return failed;
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
|
||||
{
|
||||
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
|
||||
{
|
||||
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
|
||||
}
|
||||
|
||||
void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const
|
||||
{
|
||||
const auto& entry = info.Buffer->At(_materialSlotIndex);
|
||||
@@ -212,297 +366,70 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI
|
||||
void SkinnedMesh::Release()
|
||||
{
|
||||
MeshBase::Release();
|
||||
|
||||
|
||||
BlendShapes.Clear();
|
||||
}
|
||||
|
||||
bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const
|
||||
{
|
||||
if (_cachedVertexBuffers[0].IsEmpty())
|
||||
{
|
||||
PROFILE_CPU();
|
||||
auto model = GetSkinnedModel();
|
||||
ScopeLock lock(model->Locker);
|
||||
if (model->IsVirtual())
|
||||
{
|
||||
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fetch chunk with data from drive/memory
|
||||
const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(_lodIndex);
|
||||
if (model->LoadChunk(chunkIndex))
|
||||
return true;
|
||||
const auto chunk = model->GetChunk(chunkIndex);
|
||||
if (!chunk)
|
||||
{
|
||||
LOG(Error, "Missing chunk.");
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
|
||||
// Seek to find mesh location
|
||||
byte version = stream.ReadByte();
|
||||
for (int32 i = 0; i <= _index; i++)
|
||||
{
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.ReadUint16(&blendShapesCount);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
|
||||
{
|
||||
uint32 minVertexIndex, maxVertexIndex;
|
||||
bool useNormals = stream.ReadBool();
|
||||
stream.ReadUint32(&minVertexIndex);
|
||||
stream.ReadUint32(&maxVertexIndex);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
auto blendShapeVerticesData = stream.Move<byte>(blendShapeVertices * sizeof(BlendShapeVertex));
|
||||
}
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Error, "Invalid mesh data.");
|
||||
return true;
|
||||
}
|
||||
auto vb0 = stream.Move<VB0SkinnedElementType>(vertices);
|
||||
auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
|
||||
if (i != _index)
|
||||
continue;
|
||||
|
||||
// Cache mesh data
|
||||
_cachedIndexBufferCount = indicesCount;
|
||||
_cachedIndexBuffer.Set(ib, indicesCount * ibStride);
|
||||
_cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0SkinnedElementType));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
result.Link(_cachedIndexBuffer);
|
||||
count = _cachedIndexBufferCount;
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
result.Link(_cachedVertexBuffers[0]);
|
||||
count = _cachedVertexBuffers[0].Count() / sizeof(VB0SkinnedElementType);
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
|
||||
template<typename IndexType>
|
||||
bool UpdateMesh(SkinnedMesh* mesh, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
|
||||
bool SkinnedMesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
|
||||
{
|
||||
auto model = mesh->GetSkinnedModel();
|
||||
ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj && blendIndicesObj && blendWeightsObj);
|
||||
|
||||
// Get buffers data
|
||||
const auto vertexCount = (uint32)MCore::Array::GetLength(verticesObj);
|
||||
const auto triangleCount = (uint32)MCore::Array::GetLength(trianglesObj) / 3;
|
||||
auto vertices = MCore::Array::GetAddress<Float3>(verticesObj);
|
||||
auto ib = MCore::Array::GetAddress<IndexType>(trianglesObj);
|
||||
auto blendIndices = MCore::Array::GetAddress<Int4>(blendIndicesObj);
|
||||
auto blendWeights = MCore::Array::GetAddress<Float4>(blendWeightsObj);
|
||||
Array<VB0SkinnedElementType> vb;
|
||||
vb.Resize(vertexCount);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb.Get()[i].Position = vertices[i];
|
||||
if (normalsObj)
|
||||
{
|
||||
const auto normals = MCore::Array::GetAddress<Float3>(normalsObj);
|
||||
if (tangentsObj)
|
||||
{
|
||||
const auto tangents = MCore::Array::GetAddress<Float3>(tangentsObj);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
const Float3 normal = normals[i];
|
||||
const Float3 tangent = tangents[i];
|
||||
auto& v = vb.Get()[i];
|
||||
RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
const Float3 normal = normals[i];
|
||||
auto& v = vb.Get()[i];
|
||||
RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto n = Float1010102(Float3::UnitZ);
|
||||
const auto t = Float1010102(Float3::UnitX);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vb[i].Normal = n;
|
||||
vb[i].Tangent = t;
|
||||
}
|
||||
}
|
||||
if (uvObj)
|
||||
{
|
||||
const auto uvs = MCore::Array::GetAddress<Float2>(uvObj);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb[i].TexCoord = Half2(uvs[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto v = Half2::Zero;
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb[i].TexCoord = v;
|
||||
}
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
auto v = blendIndices[i];
|
||||
vb[i].BlendIndices = Color32(v.X, v.Y, v.Z, v.W);
|
||||
}
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
auto v = blendWeights[i];
|
||||
vb[i].BlendWeights = Half4(v);
|
||||
}
|
||||
|
||||
return mesh->UpdateMesh(vertexCount, triangleCount, vb.Get(), ib);
|
||||
return ::UpdateMesh<uint32>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
|
||||
bool SkinnedMesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
|
||||
{
|
||||
return ::UpdateMesh<uint32>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
|
||||
{
|
||||
return ::UpdateMesh<uint16>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
|
||||
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||
}
|
||||
|
||||
// [Deprecated in v1.10]
|
||||
enum class InternalBufferType
|
||||
{
|
||||
VB0 = 0,
|
||||
IB16 = 3,
|
||||
IB32 = 4,
|
||||
};
|
||||
|
||||
MArray* SkinnedMesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI)
|
||||
{
|
||||
SkinnedMesh* mesh = this;
|
||||
InternalBufferType type = (InternalBufferType)typeI;
|
||||
auto model = mesh->GetSkinnedModel();
|
||||
ScopeLock lock(model->Locker);
|
||||
// [Deprecated in v1.10]
|
||||
ScopeLock lock(GetModelBase()->Locker);
|
||||
|
||||
// Virtual assets always fetch from GPU memory
|
||||
forceGpu |= model->IsVirtual();
|
||||
|
||||
if (!mesh->IsInitialized() && forceGpu)
|
||||
{
|
||||
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
|
||||
// Get vertex buffers data from the mesh (CPU or GPU)
|
||||
MeshAccessor accessor;
|
||||
MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 };
|
||||
if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1)))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MeshBufferType bufferType;
|
||||
switch (type)
|
||||
{
|
||||
case InternalBufferType::VB0:
|
||||
bufferType = MeshBufferType::Vertex0;
|
||||
break;
|
||||
case InternalBufferType::IB16:
|
||||
case InternalBufferType::IB32:
|
||||
bufferType = MeshBufferType::Index;
|
||||
break;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
BytesContainer data;
|
||||
int32 dataCount;
|
||||
if (forceGpu)
|
||||
{
|
||||
// Get data from GPU
|
||||
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
|
||||
auto task = mesh->DownloadDataGPUAsync(bufferType, data);
|
||||
if (task == nullptr)
|
||||
return nullptr;
|
||||
task->Start();
|
||||
model->Locker.Unlock();
|
||||
if (task->Wait())
|
||||
{
|
||||
LOG(Error, "Task failed.");
|
||||
return nullptr;
|
||||
}
|
||||
model->Locker.Lock();
|
||||
|
||||
// Extract elements count from result data
|
||||
switch (bufferType)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
dataCount = data.Length() / (Use16BitIndexBuffer() ? sizeof(uint16) : sizeof(uint32));
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
dataCount = data.Length() / sizeof(VB0SkinnedElementType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get data from CPU
|
||||
if (DownloadDataCPU(bufferType, data, dataCount))
|
||||
return nullptr;
|
||||
}
|
||||
auto positionStream = accessor.Position();
|
||||
auto texCoordStream = accessor.TexCoord();
|
||||
auto normalStream = accessor.Normal();
|
||||
auto tangentStream = accessor.Tangent();
|
||||
auto blendIndicesStream = accessor.BlendIndices();
|
||||
auto BlendWeightsStream = accessor.BlendWeights();
|
||||
auto count = GetVertexCount();
|
||||
|
||||
// Convert into managed array
|
||||
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), dataCount);
|
||||
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), count);
|
||||
void* managedArrayPtr = MCore::Array::GetAddress(result);
|
||||
const int32 elementSize = data.Length() / dataCount;
|
||||
switch (type)
|
||||
switch ((InternalBufferType)typeI)
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
case InternalBufferType::VB0:
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::IB16:
|
||||
{
|
||||
if (elementSize == sizeof(uint16))
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dst = (uint16*)managedArrayPtr;
|
||||
auto src = (uint32*)data.Get();
|
||||
for (int32 i = 0; i < dataCount; i++)
|
||||
dst[i] = src[i];
|
||||
auto& dst = ((VB0SkinnedElementType*)managedArrayPtr)[i];
|
||||
dst.Position = positionStream.GetFloat3(i);
|
||||
if (texCoordStream.IsValid())
|
||||
dst.TexCoord = texCoordStream.GetFloat2(i);
|
||||
if (normalStream.IsValid())
|
||||
dst.Normal = normalStream.GetFloat3(i);
|
||||
if (tangentStream.IsValid())
|
||||
dst.Tangent = tangentStream.GetFloat4(i);
|
||||
if (blendIndicesStream.IsValid())
|
||||
dst.BlendIndices = Color32(blendIndicesStream.GetFloat4(i));
|
||||
if (BlendWeightsStream.IsValid())
|
||||
dst.BlendWeights = Half4(BlendWeightsStream.GetFloat4(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::IB32:
|
||||
{
|
||||
if (elementSize == sizeof(uint16))
|
||||
{
|
||||
auto dst = (uint32*)managedArrayPtr;
|
||||
auto src = (uint16*)data.Get();
|
||||
for (int32 i = 0; i < dataCount; i++)
|
||||
dst[i] = src[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
||||
}
|
||||
break;
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -8,7 +8,9 @@ namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// The Vertex Buffer 0 structure format.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public struct Vertex0
|
||||
{
|
||||
/// <summary>
|
||||
@@ -44,7 +46,9 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// The raw Vertex Buffer structure format.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public struct Vertex
|
||||
{
|
||||
/// <summary>
|
||||
@@ -100,7 +104,8 @@ namespace FlaxEngine
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
public void UpdateMesh(Float3[] vertices, int[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null)
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
public void UpdateMesh(Float3[] vertices, int[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null)
|
||||
{
|
||||
if (!ParentSkinnedModel.IsVirtual)
|
||||
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
|
||||
@@ -118,8 +123,10 @@ namespace FlaxEngine
|
||||
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||
if (uv != null && uv.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
||||
if (colors != null && colors.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(colors));
|
||||
|
||||
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
|
||||
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors))
|
||||
throw new Exception("Failed to update mesh data.");
|
||||
}
|
||||
|
||||
@@ -135,7 +142,8 @@ namespace FlaxEngine
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
public void UpdateMesh(Float3[] vertices, uint[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null)
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
public void UpdateMesh(Float3[] vertices, uint[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null)
|
||||
{
|
||||
if (!ParentSkinnedModel.IsVirtual)
|
||||
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
|
||||
@@ -153,8 +161,10 @@ namespace FlaxEngine
|
||||
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||
if (uv != null && uv.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
||||
if (colors != null && colors.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(colors));
|
||||
|
||||
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
|
||||
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors))
|
||||
throw new Exception("Failed to update mesh data.");
|
||||
}
|
||||
|
||||
@@ -170,7 +180,8 @@ namespace FlaxEngine
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
public void UpdateMesh(Float3[] vertices, ushort[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null)
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
public void UpdateMesh(Float3[] vertices, ushort[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null)
|
||||
{
|
||||
if (!ParentSkinnedModel.IsVirtual)
|
||||
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
|
||||
@@ -188,8 +199,10 @@ namespace FlaxEngine
|
||||
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||
if (uv != null && uv.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
||||
if (colors != null && colors.Length != vertices.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(colors));
|
||||
|
||||
if (Internal_UpdateMeshUShort(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
|
||||
if (Internal_UpdateMeshUShort(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors))
|
||||
throw new Exception("Failed to update mesh data.");
|
||||
}
|
||||
|
||||
@@ -250,18 +263,22 @@ namespace FlaxEngine
|
||||
UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
internal enum InternalBufferType
|
||||
{
|
||||
VB0 = 0,
|
||||
IB16 = 3,
|
||||
IB32 = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the first vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false)
|
||||
{
|
||||
var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0);
|
||||
@@ -272,13 +289,13 @@ namespace FlaxEngine
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the raw vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||
public Vertex[] DownloadVertexBuffer(bool forceGpu = false)
|
||||
{
|
||||
// TODO: perform data conversion on C++ side to make it faster
|
||||
|
||||
var vb0 = DownloadVertexBuffer0(forceGpu);
|
||||
|
||||
var vertices = vb0.Length;
|
||||
@@ -299,33 +316,5 @@ namespace FlaxEngine
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// </summary>
|
||||
/// <remarks>If mesh index buffer format (see <see cref="MeshBase.IndexBufferFormat"/>) is <see cref="PixelFormat.R16_UInt"/> then it's faster to call .</remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
public uint[] DownloadIndexBuffer(bool forceGpu = false)
|
||||
{
|
||||
var result = (uint[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(uint), (int)InternalBufferType.IB32);
|
||||
if (result == null)
|
||||
throw new Exception("Failed to download mesh data.");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
|
||||
/// </summary>
|
||||
/// <remarks>If mesh index buffer format (see <see cref="MeshBase.IndexBufferFormat"/>) is <see cref="PixelFormat.R32_UInt"/> then data won't be downloaded.</remarks>
|
||||
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
|
||||
/// <returns>The gathered data.</returns>
|
||||
public ushort[] DownloadIndexBufferUShort(bool forceGpu = false)
|
||||
{
|
||||
var result = (ushort[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(ushort), (int)InternalBufferType.IB16);
|
||||
if (result == null)
|
||||
throw new Exception("Failed to download mesh data.");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,45 +51,43 @@ public:
|
||||
public:
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb">The vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
||||
}
|
||||
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb">The vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
||||
}
|
||||
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb">The vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer, clockwise order.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, true);
|
||||
}
|
||||
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
@@ -97,8 +95,45 @@ public:
|
||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
DEPRECATED("Use MeshAccessor or Load with separate vertex attribute arrays instead.")
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
|
||||
/// Mesh data will be cached and uploaded to the GPU with a delay.
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vertices">The mesh vertices positions. Cannot be null.</param>
|
||||
/// <param name="triangles">The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.</param>
|
||||
/// <param name="blendIndices">The skeletal bones indices to use for skinning.</param>
|
||||
/// <param name="blendWeights">The skeletal bones weights to use for skinning (matches blendIndices).</param>
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uvs">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
|
||||
/// Mesh data will be cached and uploaded to the GPU with a delay.
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vertices">The mesh vertices positions. Cannot be null.</param>
|
||||
/// <param name="triangles">The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.</param>
|
||||
/// <param name="blendIndices">The skeletal bones indices to use for skinning.</param>
|
||||
/// <param name="blendWeights">The skeletal bones weights to use for skinning (matches blendIndices).</param>
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uvs">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Draws the mesh.
|
||||
@@ -119,13 +154,12 @@ public:
|
||||
public:
|
||||
// [MeshBase]
|
||||
void Release() override;
|
||||
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
|
||||
|
||||
private:
|
||||
// Internal bindings
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
|
||||
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ API_ENUM(Attributes="HideInEditor") enum class ModelLightmapUVsSource
|
||||
/// <summary>
|
||||
/// The mesh buffer types.
|
||||
/// </summary>
|
||||
enum class MeshBufferType
|
||||
API_ENUM(Attributes="HideInEditor") enum class MeshBufferType
|
||||
{
|
||||
/// <summary>
|
||||
/// The index buffer.
|
||||
@@ -73,7 +73,7 @@ enum class MeshBufferType
|
||||
|
||||
// Vertex structure for all models (versioned)
|
||||
// [Deprecated in v1.10]
|
||||
PACK_STRUCT(struct ModelVertex19
|
||||
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") ModelVertex19
|
||||
{
|
||||
Float3 Position;
|
||||
Half2 TexCoord;
|
||||
@@ -83,11 +83,13 @@ PACK_STRUCT(struct ModelVertex19
|
||||
Color32 Color;
|
||||
});
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
// [Deprecated in v1.10]
|
||||
typedef ModelVertex19 ModelVertex;
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
// [Deprecated in v1.10]
|
||||
struct RawModelVertex
|
||||
struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") RawModelVertex
|
||||
{
|
||||
Float3 Position;
|
||||
Float2 TexCoord;
|
||||
@@ -100,7 +102,7 @@ struct RawModelVertex
|
||||
|
||||
// For vertex data we use three buffers: one with positions, one with other attributes, and one with colors
|
||||
// [Deprecated in v1.10]
|
||||
PACK_STRUCT(struct VB0ElementType18
|
||||
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB0ElementType18
|
||||
{
|
||||
Float3 Position;
|
||||
|
||||
@@ -108,7 +110,7 @@ PACK_STRUCT(struct VB0ElementType18
|
||||
});
|
||||
|
||||
// [Deprecated in v1.10]
|
||||
PACK_STRUCT(struct VB1ElementType18
|
||||
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB1ElementType18
|
||||
{
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
@@ -119,23 +121,25 @@ PACK_STRUCT(struct VB1ElementType18
|
||||
});
|
||||
|
||||
// [Deprecated in v1.10]
|
||||
PACK_STRUCT(struct VB2ElementType18
|
||||
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB2ElementType18
|
||||
{
|
||||
Color32 Color;
|
||||
|
||||
static GPUVertexLayout* GetLayout();
|
||||
});
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
// [Deprecated in v1.10]
|
||||
typedef VB0ElementType18 VB0ElementType;
|
||||
// [Deprecated in v1.10]
|
||||
typedef VB1ElementType18 VB1ElementType;
|
||||
// [Deprecated in v1.10]
|
||||
typedef VB2ElementType18 VB2ElementType;
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
// Vertex structure for all skinned models (versioned)
|
||||
// [Deprecated in v1.10]
|
||||
PACK_STRUCT(struct SkinnedModelVertex1
|
||||
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") SkinnedModelVertex1
|
||||
{
|
||||
Float3 Position;
|
||||
Half2 TexCoord;
|
||||
@@ -145,11 +149,13 @@ PACK_STRUCT(struct SkinnedModelVertex1
|
||||
Color32 BlendWeights;
|
||||
});
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
// [Deprecated in v1.10]
|
||||
typedef SkinnedModelVertex1 SkinnedModelVertex;
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
// [Deprecated in v1.10]
|
||||
struct RawSkinnedModelVertex
|
||||
struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") RawSkinnedModelVertex
|
||||
{
|
||||
Float3 Position;
|
||||
Float2 TexCoord;
|
||||
@@ -161,7 +167,7 @@ struct RawSkinnedModelVertex
|
||||
};
|
||||
|
||||
// [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||
PACK_STRUCT(struct VB0SkinnedElementType1
|
||||
PACK_STRUCT(struct DEPRECATED("Use newer format.") VB0SkinnedElementType1
|
||||
{
|
||||
Float3 Position;
|
||||
Half2 TexCoord;
|
||||
@@ -172,7 +178,7 @@ PACK_STRUCT(struct VB0SkinnedElementType1
|
||||
});
|
||||
|
||||
// [Deprecated in v1.10]
|
||||
PACK_STRUCT(struct VB0SkinnedElementType2
|
||||
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB0SkinnedElementType2
|
||||
{
|
||||
Float3 Position;
|
||||
Half2 TexCoord;
|
||||
@@ -184,5 +190,7 @@ PACK_STRUCT(struct VB0SkinnedElementType2
|
||||
static GPUVertexLayout* GetLayout();
|
||||
});
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
// [Deprecated in v1.10]
|
||||
typedef VB0SkinnedElementType2 VB0SkinnedElementType;
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
@@ -1496,8 +1496,8 @@ void PixelFormatExtensions::GetSamplerInternal(PixelFormat format, int32& pixelS
|
||||
if (const PixelFormatSampler* sampler = PixelFormatSampler::Get(format))
|
||||
{
|
||||
pixelSize = sampler->PixelSize;
|
||||
*read = sampler->Read;
|
||||
*write = sampler->Write;
|
||||
*read = (void*)sampler->Read;
|
||||
*write = (void*)sampler->Write;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -557,6 +557,26 @@ float RenderTools::ComputeTemporalTime()
|
||||
}
|
||||
|
||||
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal)
|
||||
{
|
||||
// [Deprecated in v1.10]
|
||||
Float3 n;
|
||||
Float4 t;
|
||||
CalculateTangentFrame(n, t, normal);
|
||||
resultNormal = Float1010102(n, 0);
|
||||
resultTangent = Float1010102(t);
|
||||
}
|
||||
|
||||
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent)
|
||||
{
|
||||
// [Deprecated in v1.10]
|
||||
Float3 n;
|
||||
Float4 t;
|
||||
CalculateTangentFrame(n, t, normal, tangent);
|
||||
resultNormal = Float1010102(n, 0);
|
||||
resultTangent = Float1010102(t);
|
||||
}
|
||||
|
||||
void RenderTools::CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal)
|
||||
{
|
||||
// Calculate tangent
|
||||
const Float3 c1 = Float3::Cross(normal, Float3::UnitZ);
|
||||
@@ -568,19 +588,19 @@ void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10
|
||||
const byte sign = static_cast<byte>(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||
|
||||
// Set tangent frame
|
||||
resultNormal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
||||
resultNormal = normal * 0.5f + 0.5f;
|
||||
resultTangent = Float4(tangent * 0.5f + 0.5f, sign);
|
||||
}
|
||||
|
||||
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent)
|
||||
void RenderTools::CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent)
|
||||
{
|
||||
// Calculate bitangent sign
|
||||
const Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent));
|
||||
const byte sign = static_cast<byte>(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||
|
||||
// Set tangent frame
|
||||
resultNormal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
||||
resultNormal = normal * 0.5f + 0.5f;
|
||||
resultTangent = Float4(tangent * 0.5f + 0.5f, sign);
|
||||
}
|
||||
|
||||
void RenderTools::ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside)
|
||||
|
||||
@@ -130,8 +130,14 @@ public:
|
||||
// Returns 0-1 value based on unscaled draw time for temporal effects to reduce artifacts from screen-space dithering when using Temporal Anti-Aliasing.
|
||||
static float ComputeTemporalTime();
|
||||
|
||||
static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal);
|
||||
static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent);
|
||||
// [Deprecated in v1.10]
|
||||
DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal);
|
||||
// [Deprecated in v1.10]
|
||||
DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent);
|
||||
|
||||
// Result normal/tangent are already packed into [0;1] range.
|
||||
static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal);
|
||||
static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent);
|
||||
|
||||
static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "GPUVertexLayout.h"
|
||||
#if GPU_ENABLE_ASSERTION_LOW_LAYERS
|
||||
#include "Engine/Core/Log.h"
|
||||
#endif
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Types/Span.h"
|
||||
|
||||
@@ -20,10 +20,10 @@ GPUShaderProgramVSDX11::~GPUShaderProgramVSDX11()
|
||||
ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* vertexLayout)
|
||||
{
|
||||
ID3D11InputLayout* inputLayout = nullptr;
|
||||
if (!vertexLayout)
|
||||
vertexLayout = (GPUVertexLayoutDX11*)Layout;
|
||||
if (!_cache.TryGet(vertexLayout, inputLayout))
|
||||
{
|
||||
if (!vertexLayout)
|
||||
vertexLayout = (GPUVertexLayoutDX11*)Layout;
|
||||
if (vertexLayout && vertexLayout->InputElementsCount)
|
||||
{
|
||||
auto mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/Models/MeshAccessor.h"
|
||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Level/SceneObjectsFactory.h"
|
||||
@@ -628,30 +629,34 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
|
||||
|
||||
// Blend all blend shapes
|
||||
auto vertexCount = (uint32)mesh->GetVertexCount();
|
||||
auto data = (VB0SkinnedElementType*)deformation.VertexBuffer.Data.Get();
|
||||
MeshAccessor accessor;
|
||||
if (deformation.LoadMeshAccessor(accessor))
|
||||
return;
|
||||
auto positionStream = accessor.Position();
|
||||
auto normalStream = accessor.Normal();
|
||||
CHECK(positionStream.IsValid());
|
||||
useNormals &= normalStream.IsValid();
|
||||
for (const auto& q : blendShapes)
|
||||
{
|
||||
// TODO: use SIMD
|
||||
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
|
||||
{
|
||||
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
|
||||
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
|
||||
|
||||
Float3 position = positionStream.GetFloat3(blendShapeVertex.VertexIndex);
|
||||
position = position + blendShapeVertex.PositionDelta * q.Second;
|
||||
positionStream.SetFloat3(blendShapeVertex.VertexIndex, position);
|
||||
}
|
||||
if (useNormals)
|
||||
{
|
||||
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
|
||||
{
|
||||
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
|
||||
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
|
||||
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
|
||||
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
|
||||
Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta * q.Second;
|
||||
vertex.Normal = normal * 0.5f + 0.5f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
|
||||
{
|
||||
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
|
||||
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
|
||||
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
|
||||
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
|
||||
|
||||
Float3 normal = normalStream.GetFloat3(blendShapeVertex.VertexIndex) * 2.0f - 1.0f;
|
||||
normal = normal + blendShapeVertex.NormalDelta * q.Second;
|
||||
normal = normal * 0.5f + 0.5f; // TODO: optimize unpacking and packing to just apply it to the normal delta
|
||||
normalStream.SetFloat3(blendShapeVertex.VertexIndex, normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -659,21 +664,23 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
|
||||
if (useNormals)
|
||||
{
|
||||
// Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range)
|
||||
// TODO: use SIMD
|
||||
auto tangentStream = accessor.Tangent();
|
||||
for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++)
|
||||
{
|
||||
VB0SkinnedElementType& vertex = *(data + vertexIndex);
|
||||
|
||||
Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f;
|
||||
Float3 normal = normalStream.GetFloat3(vertexIndex) * 2.0f - 1.0f;
|
||||
normal.Normalize();
|
||||
vertex.Normal = normal * 0.5f + 0.5f;
|
||||
normal = normal * 0.5f + 0.5f;
|
||||
normalStream.SetFloat3(vertexIndex, normal);
|
||||
|
||||
Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f;
|
||||
tangent = tangent - ((tangent | normal) * normal);
|
||||
tangent.Normalize();
|
||||
const auto tangentSign = vertex.Tangent.W;
|
||||
vertex.Tangent = tangent * 0.5f + 0.5f;
|
||||
vertex.Tangent.W = tangentSign;
|
||||
if (tangentStream.IsValid())
|
||||
{
|
||||
Float4 tangentRaw = normalStream.GetFloat4(vertexIndex);
|
||||
Float3 tangent = Float3(tangentRaw) * 2.0f - 1.0f;
|
||||
tangent = tangent - ((tangent | normal) * normal);
|
||||
tangent.Normalize();
|
||||
tangentRaw = Float4(tangent * 0.5f + 0.5f, tangentRaw.W);
|
||||
tangentStream.SetFloat4(vertexIndex, tangentRaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1221,16 +1228,17 @@ bool AnimatedModel::IntersectsEntry(const Ray& ray, Real& distance, Vector3& nor
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AnimatedModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const
|
||||
bool AnimatedModel::GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const
|
||||
{
|
||||
count = 0;
|
||||
if (mesh.LODIndex < 0 || mesh.MeshIndex < 0)
|
||||
if (ref.LODIndex < 0 || ref.MeshIndex < 0)
|
||||
return true;
|
||||
const auto model = SkinnedModel.Get();
|
||||
if (!model || model->WaitForLoaded())
|
||||
return true;
|
||||
auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)];
|
||||
return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count);
|
||||
auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)];
|
||||
auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)];
|
||||
return mesh.DownloadDataCPU(type, result, count, layout);
|
||||
}
|
||||
|
||||
MeshDeformation* AnimatedModel::GetMeshDeformation() const
|
||||
|
||||
@@ -433,7 +433,7 @@ public:
|
||||
MaterialBase* GetMaterial(int32 entryIndex) override;
|
||||
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
|
||||
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
|
||||
bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override;
|
||||
bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override;
|
||||
void UpdateBounds() override;
|
||||
MeshDeformation* GetMeshDeformation() const override;
|
||||
void OnDeleteObject() override;
|
||||
|
||||
@@ -112,12 +112,13 @@ public:
|
||||
/// <summary>
|
||||
/// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel).
|
||||
/// </summary>
|
||||
/// <param name="mesh">Mesh reference.</param>
|
||||
/// <param name="ref">Mesh reference.</param>
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <param name="count">The amount of items inside the result buffer.</param>
|
||||
/// <param name="layout">The result layout of the result buffer (for vertex buffers). Optional, pass null to ignore it.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
virtual bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const
|
||||
virtual bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout = nullptr) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Level/Prefabs/PrefabManager.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
@@ -300,7 +301,8 @@ void StaticModel::FlushVertexColors()
|
||||
vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors"));
|
||||
if (vertexColorsBuffer->GetSize() != size)
|
||||
{
|
||||
if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(VB2ElementType::GetLayout(), sizeof(Color32), vertexColorsData.Count(), nullptr)))
|
||||
auto layout = GPUVertexLayout::Get({{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }});
|
||||
if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(layout, sizeof(Color32), vertexColorsData.Count(), nullptr)))
|
||||
break;
|
||||
}
|
||||
GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size);
|
||||
@@ -627,16 +629,17 @@ bool StaticModel::IntersectsEntry(const Ray& ray, Real& distance, Vector3& norma
|
||||
return result;
|
||||
}
|
||||
|
||||
bool StaticModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const
|
||||
bool StaticModel::GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const
|
||||
{
|
||||
count = 0;
|
||||
if (mesh.LODIndex < 0 || mesh.MeshIndex < 0)
|
||||
if (ref.LODIndex < 0 || ref.MeshIndex < 0)
|
||||
return true;
|
||||
const auto model = Model.Get();
|
||||
if (!model || model->WaitForLoaded())
|
||||
return true;
|
||||
auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)];
|
||||
return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count);
|
||||
auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)];
|
||||
auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)];
|
||||
return mesh.DownloadDataCPU(type, result, count, layout);
|
||||
}
|
||||
|
||||
MeshDeformation* StaticModel::GetMeshDeformation() const
|
||||
|
||||
@@ -171,7 +171,7 @@ public:
|
||||
MaterialBase* GetMaterial(int32 entryIndex) override;
|
||||
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
|
||||
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
|
||||
bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override;
|
||||
bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override;
|
||||
MeshDeformation* GetMeshDeformation() const override;
|
||||
void UpdateBounds() override;
|
||||
|
||||
|
||||
@@ -221,6 +221,7 @@ Asset::LoadResult ParticleSystem::load()
|
||||
#endif
|
||||
switch (version)
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
case 1:
|
||||
{
|
||||
// [Deprecated on 23.07.2019, expires on 27.04.2021]
|
||||
@@ -369,6 +370,7 @@ Asset::LoadResult ParticleSystem::load()
|
||||
|
||||
break;
|
||||
}
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023]
|
||||
case 4:
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Engine/Core/Math/Ray.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Models/MeshAccessor.h"
|
||||
#include "Engine/Graphics/Models/MeshBase.h"
|
||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||
#include "Engine/Physics/PhysicsBackend.h"
|
||||
@@ -50,6 +51,7 @@ void Cloth::SetMesh(const ModelInstanceActor::MeshReference& value)
|
||||
Function<void(const MeshBase*, MeshDeformationData&)> deformer;
|
||||
deformer.Bind<Cloth, &Cloth::RunClothDeformer>(this);
|
||||
_meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, deformer);
|
||||
_meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex1, deformer);
|
||||
_meshDeformation = nullptr;
|
||||
}
|
||||
|
||||
@@ -556,6 +558,7 @@ bool Cloth::CreateCloth()
|
||||
int32 count;
|
||||
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, data, count))
|
||||
return true;
|
||||
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0
|
||||
desc.VerticesData = data.Get();
|
||||
desc.VerticesCount = count;
|
||||
desc.VerticesStride = data.Length() / count;
|
||||
@@ -656,6 +659,7 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
|
||||
const int32 i0 = indicesData.Get<uint16>()[index];
|
||||
const int32 i1 = indicesData.Get<uint16>()[index + 1];
|
||||
const int32 i2 = indicesData.Get<uint16>()[index + 2];
|
||||
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0
|
||||
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride)
|
||||
const Float3 v0(GET_POS(i0));
|
||||
const Float3 v1(GET_POS(i1));
|
||||
@@ -675,6 +679,7 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
|
||||
const int32 i0 = indicesData.Get<uint32>()[index];
|
||||
const int32 i1 = indicesData.Get<uint32>()[index + 1];
|
||||
const int32 i2 = indicesData.Get<uint32>()[index + 2];
|
||||
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0
|
||||
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride)
|
||||
const Float3 v0(GET_POS(i0));
|
||||
const Float3 v1(GET_POS(i1));
|
||||
@@ -770,11 +775,18 @@ bool Cloth::OnPreUpdate()
|
||||
return false;
|
||||
BytesContainer verticesData;
|
||||
int32 verticesCount;
|
||||
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount))
|
||||
GPUVertexLayout* layout;
|
||||
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount, &layout))
|
||||
return false;
|
||||
MeshAccessor accessor;
|
||||
if (accessor.LoadBuffer(MeshBufferType::Vertex0, verticesData, layout))
|
||||
return false;
|
||||
auto positionStream = accessor.Position();
|
||||
auto blendIndicesStream = accessor.BlendIndices();
|
||||
auto blendWeightsStream = accessor.BlendWeights();
|
||||
if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid())
|
||||
return false;
|
||||
PROFILE_CPU_NAMED("Skinned Pose");
|
||||
auto vbStride = (uint32)verticesData.Length() / verticesCount;
|
||||
ASSERT(vbStride == sizeof(VB0SkinnedElementType));
|
||||
PhysicsBackend::LockClothParticles(_cloth);
|
||||
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
|
||||
// TODO: optimize memory allocs (eg. write directly to nvCloth mapped range or use shared allocator)
|
||||
@@ -794,43 +806,46 @@ bool Cloth::OnPreUpdate()
|
||||
{
|
||||
if (paint[i] > ZeroTolerance)
|
||||
continue;
|
||||
VB0SkinnedElementType& vb0 = verticesData.Get<VB0SkinnedElementType>()[i];
|
||||
|
||||
// Load vertex
|
||||
Float3 position = positionStream.GetFloat3(i);
|
||||
const Int4 blendIndices = blendIndicesStream.GetFloat4(i);
|
||||
const Float4 blendWeights = blendWeightsStream.GetFloat4(i);
|
||||
|
||||
// Calculate skinned vertex matrix from bones blending
|
||||
const Float4 blendWeights = vb0.BlendWeights.ToFloat4();
|
||||
// TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly
|
||||
Matrix matrix;
|
||||
const SkeletonBone& bone0 = bones[vb0.BlendIndices.R];
|
||||
const SkeletonBone& bone0 = bones[blendIndices.X];
|
||||
Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix);
|
||||
Matrix boneMatrix = matrix * blendWeights.X;
|
||||
if (blendWeights.Y > 0.0f)
|
||||
{
|
||||
const SkeletonBone& bone1 = bones[vb0.BlendIndices.G];
|
||||
const SkeletonBone& bone1 = bones[blendIndices.Y];
|
||||
Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix);
|
||||
boneMatrix += matrix * blendWeights.Y;
|
||||
}
|
||||
if (blendWeights.Z > 0.0f)
|
||||
{
|
||||
const SkeletonBone& bone2 = bones[vb0.BlendIndices.B];
|
||||
const SkeletonBone& bone2 = bones[blendIndices.Z];
|
||||
Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix);
|
||||
boneMatrix += matrix * blendWeights.Z;
|
||||
}
|
||||
if (blendWeights.W > 0.0f)
|
||||
{
|
||||
const SkeletonBone& bone3 = bones[vb0.BlendIndices.A];
|
||||
const SkeletonBone& bone3 = bones[blendIndices.W];
|
||||
Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix);
|
||||
boneMatrix += matrix * blendWeights.W;
|
||||
}
|
||||
|
||||
// Skin vertex position (similar to GPU vertex shader)
|
||||
Float3 pos = Float3::Transform(vb0.Position, boneMatrix);
|
||||
position = Float3::Transform(position, boneMatrix);
|
||||
|
||||
// Transform back to the cloth space
|
||||
// TODO: skip when using identity?
|
||||
pos = _localTransform.WorldToLocal(pos);
|
||||
position = _localTransform.WorldToLocal(position);
|
||||
|
||||
// Override fixed particle position
|
||||
particlesSkinned[i] = Float4(pos, 0.0f);
|
||||
particlesSkinned[i] = Float4(position, 0.0f);
|
||||
anyFixed = true;
|
||||
}
|
||||
|
||||
@@ -891,11 +906,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
|
||||
PROFILE_CPU_NAMED("Cloth");
|
||||
PhysicsBackend::LockClothParticles(_cloth);
|
||||
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
|
||||
|
||||
auto vbData = deformation.VertexBuffer.Data.Get();
|
||||
auto vbCount = (uint32)mesh->GetVertexCount();
|
||||
auto vbStride = (uint32)deformation.VertexBuffer.Data.Count() / vbCount;
|
||||
// TODO: add support for mesh vertex data layout descriptor instead hardcoded position data at the beginning of VB0
|
||||
ASSERT((uint32)particles.Length() >= vbCount);
|
||||
|
||||
// Calculate normals
|
||||
@@ -949,6 +960,12 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
|
||||
}
|
||||
|
||||
// Update mesh vertices based on the cloth particles positions
|
||||
MeshAccessor accessor;
|
||||
if (deformation.LoadMeshAccessor(accessor))
|
||||
{
|
||||
PhysicsBackend::UnlockClothParticles(_cloth);
|
||||
return;
|
||||
}
|
||||
if (auto* animatedModel = Cast<AnimatedModel>(GetParent()))
|
||||
{
|
||||
if (animatedModel->GraphInstance.NodesPose.IsEmpty())
|
||||
@@ -965,39 +982,44 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
|
||||
const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton;
|
||||
|
||||
// Animated model uses skinning thus requires to set vertex position inverse to skeleton bones
|
||||
ASSERT(vbStride == sizeof(VB0SkinnedElementType));
|
||||
auto positionStream = accessor.Position();
|
||||
auto blendIndicesStream = accessor.BlendIndices();
|
||||
auto blendWeightsStream = accessor.BlendWeights();
|
||||
if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid())
|
||||
{
|
||||
PhysicsBackend::UnlockClothParticles(_cloth);
|
||||
return;
|
||||
}
|
||||
const float* paint = _paint.Count() >= particles.Length() ? _paint.Get() : nullptr;
|
||||
for (uint32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
VB0SkinnedElementType& vb = *(VB0SkinnedElementType*)vbData;
|
||||
vbData += vbStride;
|
||||
|
||||
// Skip fixed vertices
|
||||
if (paint && paint[i] < ZeroTolerance)
|
||||
continue;
|
||||
|
||||
// Calculate skinned vertex matrix from bones blending
|
||||
const Float4 blendWeights = vb.BlendWeights.ToFloat4();
|
||||
const Int4 blendIndices = blendIndicesStream.GetFloat4(i);
|
||||
const Float4 blendWeights = blendWeightsStream.GetFloat4(i);
|
||||
// TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly
|
||||
Matrix matrix;
|
||||
const SkeletonBone& bone0 = skeleton.Bones[vb.BlendIndices.R];
|
||||
const SkeletonBone& bone0 = skeleton.Bones[blendIndices.X];
|
||||
Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix);
|
||||
Matrix boneMatrix = matrix * blendWeights.X;
|
||||
if (blendWeights.Y > 0.0f)
|
||||
{
|
||||
const SkeletonBone& bone1 = skeleton.Bones[vb.BlendIndices.G];
|
||||
const SkeletonBone& bone1 = skeleton.Bones[blendIndices.Y];
|
||||
Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix);
|
||||
boneMatrix += matrix * blendWeights.Y;
|
||||
}
|
||||
if (blendWeights.Z > 0.0f)
|
||||
{
|
||||
const SkeletonBone& bone2 = skeleton.Bones[vb.BlendIndices.B];
|
||||
const SkeletonBone& bone2 = skeleton.Bones[blendIndices.Z];
|
||||
Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix);
|
||||
boneMatrix += matrix * blendWeights.Z;
|
||||
}
|
||||
if (blendWeights.W > 0.0f)
|
||||
{
|
||||
const SkeletonBone& bone3 = skeleton.Bones[vb.BlendIndices.A];
|
||||
const SkeletonBone& bone3 = skeleton.Bones[blendIndices.W];
|
||||
Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix);
|
||||
boneMatrix += matrix * blendWeights.W;
|
||||
}
|
||||
@@ -1006,43 +1028,59 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
|
||||
Matrix boneMatrixInv;
|
||||
Matrix::Invert(boneMatrix, boneMatrixInv);
|
||||
Float3 pos = *(Float3*)&particles.Get()[i];
|
||||
vb.Position = Float3::Transform(pos, boneMatrixInv);
|
||||
pos = Float3::Transform(pos, boneMatrixInv);
|
||||
positionStream.SetFloat3(i, pos);
|
||||
}
|
||||
|
||||
if (_simulationSettings.ComputeNormals)
|
||||
{
|
||||
// Write normals
|
||||
for (uint32 i = 0; i < vbCount; i++)
|
||||
auto normalStream = accessor.Normal();
|
||||
auto tangentStream = accessor.Tangent();
|
||||
if (normalStream.IsValid() && tangentStream.IsValid())
|
||||
{
|
||||
Float3 normal = normals.Get()[i];
|
||||
normal.Normalize();
|
||||
VB0SkinnedElementType& vb = *(VB0SkinnedElementType*)vbData;
|
||||
vbData += vbStride;
|
||||
RenderTools::CalculateTangentFrame(vb.Normal, vb.Tangent, normal);
|
||||
for (uint32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
Float3 normal = normals.Get()[i];
|
||||
normal.Normalize();
|
||||
Float3 n;
|
||||
Float4 t;
|
||||
RenderTools::CalculateTangentFrame(n, t, normal);
|
||||
normalStream.SetFloat3(i, n);
|
||||
tangentStream.SetFloat4(i, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (deformation.Type == MeshBufferType::Vertex0)
|
||||
{
|
||||
// Copy particle positions to the mesh data
|
||||
ASSERT(vbStride == sizeof(VB0ElementType));
|
||||
for (uint32 i = 0; i < vbCount; i++)
|
||||
auto positionStream = accessor.Position();
|
||||
if (positionStream.IsValid())
|
||||
{
|
||||
*(Float3*)vbData = *(Float3*)&particles.Get()[i];
|
||||
vbData += vbStride;
|
||||
for (uint32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
positionStream.SetFloat3(i, *(const Float3*)&particles.Get()[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write normals for the modified vertices by the cloth
|
||||
ASSERT(vbStride == sizeof(VB1ElementType));
|
||||
for (uint32 i = 0; i < vbCount; i++)
|
||||
auto normalStream = accessor.Normal();
|
||||
auto tangentStream = accessor.Tangent();
|
||||
if (normalStream.IsValid() && tangentStream.IsValid())
|
||||
{
|
||||
Float3 normal = normals.Get()[i];
|
||||
normal.Normalize();
|
||||
VB1ElementType& vb = *(VB1ElementType*)vbData;
|
||||
vbData += vbStride;
|
||||
RenderTools::CalculateTangentFrame(vb.Normal, vb.Tangent, normal);
|
||||
for (uint32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
Float3 normal = normals.Get()[i];
|
||||
normal.Normalize();
|
||||
Float3 n;
|
||||
Float4 t;
|
||||
RenderTools::CalculateTangentFrame(n, t, normal);
|
||||
normalStream.SetFloat3(i, n);
|
||||
tangentStream.SetFloat4(i, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Engine/Serialization/FileWriteStream.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Graphics/PixelFormatSampler.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Utilities/AnsiPathTempFile.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
@@ -199,7 +200,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
#endif
|
||||
|
||||
// Convert into RGBA8
|
||||
const auto sampler = GetSampler(texture->Format);
|
||||
const auto sampler = PixelFormatSampler::Get(texture->Format);
|
||||
if (sampler == nullptr)
|
||||
{
|
||||
LOG(Warning, "Texture data format {0} is not supported.", (int32)textureData.Format);
|
||||
@@ -218,7 +219,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
{
|
||||
for (int32 x = 0; x < texture->Width; x++)
|
||||
{
|
||||
Color color = SamplePoint(sampler, x, y, srcData->Data.Get(), srcData->RowPitch);
|
||||
Color color = sampler->SamplePoint(srcData->Data.Get(), x, y, srcData->RowPitch);
|
||||
if (sRGB)
|
||||
color = Color::SrgbToLinear(color);
|
||||
*(ptr + x + y * texture->Width) = color.ToFloat4();
|
||||
@@ -234,7 +235,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
{
|
||||
for (int32 x = 0; x < texture->Width; x++)
|
||||
{
|
||||
Color color = SamplePoint(sampler, x, y, srcData->Data.Get(), srcData->RowPitch);
|
||||
Color color = sampler->SamplePoint(srcData->Data.Get(), x, y, srcData->RowPitch);
|
||||
if (sRGB)
|
||||
color = Color::SrgbToLinear(color);
|
||||
*(ptr + x + y * texture->Width) = Color32(color);
|
||||
@@ -623,7 +624,7 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix
|
||||
dst.Items.Resize(arraySize, false);
|
||||
auto formatSize = PixelFormatExtensions::SizeInBytes(textureData->Format);
|
||||
auto components = PixelFormatExtensions::ComputeComponentsCount(textureData->Format);
|
||||
auto sampler = TextureTool::GetSampler(textureData->Format);
|
||||
auto sampler = PixelFormatSampler::Get(textureData->Format);
|
||||
if (!sampler)
|
||||
{
|
||||
LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast<int32>(textureData->Format));
|
||||
@@ -747,7 +748,7 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix
|
||||
#endif
|
||||
{
|
||||
int32 bytesPerPixel = PixelFormatExtensions::SizeInBytes(dstFormat);
|
||||
auto dstSampler = TextureTool::GetSampler(dstFormat);
|
||||
auto dstSampler = PixelFormatSampler::Get(dstFormat);
|
||||
if (!dstSampler)
|
||||
{
|
||||
LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast<int32>(dstFormat));
|
||||
@@ -782,10 +783,10 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix
|
||||
for (int32 x = 0; x < mipWidth; x++)
|
||||
{
|
||||
// Sample source texture
|
||||
Color color = TextureTool::SamplePoint(sampler, x, y, srcMip.Data.Get(), srcMip.RowPitch);
|
||||
Color color = sampler->SamplePoint(srcMip.Data.Get(), x, y, srcMip.RowPitch);
|
||||
|
||||
// Store destination texture
|
||||
TextureTool::Store(dstSampler, x, y, dstMip.Data.Get(), dstMip.RowPitch, color);
|
||||
sampler->Store(dstMip.Data.Get(), x, y, dstMip.RowPitch, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -802,7 +803,7 @@ bool TextureTool::ResizeStb(PixelFormat format, TextureMipData& dstMip, const Te
|
||||
auto components = PixelFormatExtensions::ComputeComponentsCount(format);
|
||||
auto srcMipWidth = srcMip.RowPitch / formatSize;
|
||||
auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch;
|
||||
auto sampler = GetSampler(format);
|
||||
auto sampler = PixelFormatSampler::Get(format);
|
||||
|
||||
// Allocate memory
|
||||
dstMip.RowPitch = dstMipWidth * formatSize;
|
||||
@@ -869,8 +870,8 @@ bool TextureTool::ResizeStb(PixelFormat format, TextureMipData& dstMip, const Te
|
||||
for (int32 x = 0; x < dstMipWidth; x++)
|
||||
{
|
||||
const Float2 uv((float)x / dstMipWidth, (float)y / dstMipHeight);
|
||||
Color color = SamplePoint(sampler, uv, srcMip.Data.Get(), srcSize, srcMip.RowPitch);
|
||||
Store(sampler, x, y, dstMip.Data.Get(), dstMip.RowPitch, color);
|
||||
Color color = sampler->SamplePoint(srcMip.Data.Get(), uv, srcSize, srcMip.RowPitch);
|
||||
sampler->Store(dstMip.Data.Get(), x, y, dstMip.RowPitch, color);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -18,18 +18,37 @@
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Core/Types/Variant.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#include "Engine/Localization/Localization.h"
|
||||
#if USE_EDITOR
|
||||
#include "Editor/Editor.h"
|
||||
#endif
|
||||
|
||||
PACK_STRUCT(struct TextRenderVertex
|
||||
{
|
||||
Float3 Position;
|
||||
Color32 Color;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
Half2 TexCoord;
|
||||
|
||||
static GPUVertexLayout* GetLayout()
|
||||
{
|
||||
return GPUVertexLayout::Get({
|
||||
{ VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float },
|
||||
{ VertexElement::Types::Color, 0, 0, 0, PixelFormat::R8G8B8A8_UNorm },
|
||||
{ VertexElement::Types::Normal, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm },
|
||||
{ VertexElement::Types::Tangent, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm },
|
||||
{ VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R16G16_Float },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
TextRender::TextRender(const SpawnParams& params)
|
||||
: Actor(params)
|
||||
, _size(32)
|
||||
, _ib(0, sizeof(uint16))
|
||||
, _vb0(0, sizeof(VB0ElementType), String::Empty, VB0ElementType::GetLayout())
|
||||
, _vb1(0, sizeof(VB1ElementType), String::Empty, VB1ElementType::GetLayout())
|
||||
, _vb2(0, sizeof(VB2ElementType), String::Empty, VB2ElementType::GetLayout())
|
||||
, _vb(0, sizeof(TextRenderVertex), String::Empty, TextRenderVertex::GetLayout())
|
||||
{
|
||||
_color = Color::White;
|
||||
_localBox = BoundingBox(Vector3::Zero);
|
||||
@@ -102,9 +121,7 @@ void TextRender::UpdateLayout()
|
||||
{
|
||||
// Clear
|
||||
_ib.Clear();
|
||||
_vb0.Clear();
|
||||
_vb1.Clear();
|
||||
_vb2.Clear();
|
||||
_vb.Clear();
|
||||
_localBox = BoundingBox(Vector3::Zero);
|
||||
BoundingBox::Transform(_localBox, _transform, _box);
|
||||
BoundingSphere::FromBox(_box, _sphere);
|
||||
@@ -163,9 +180,7 @@ void TextRender::UpdateLayout()
|
||||
|
||||
// Prepare buffers capacity
|
||||
_ib.Data.EnsureCapacity(text.Length() * 6 * sizeof(uint16));
|
||||
_vb0.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB0ElementType));
|
||||
_vb1.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB1ElementType));
|
||||
_vb2.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB2ElementType));
|
||||
_vb.Data.EnsureCapacity(text.Length() * 4 * sizeof(TextRenderVertex));
|
||||
_buffersDirty = true;
|
||||
|
||||
// Init draw chunks data
|
||||
@@ -268,20 +283,15 @@ void TextRender::UpdateLayout()
|
||||
byte sign = 0;
|
||||
|
||||
// Write vertices
|
||||
VB0ElementType vb0;
|
||||
VB1ElementType vb1;
|
||||
VB2ElementType vb2;
|
||||
TextRenderVertex v;
|
||||
#define WRITE_VB(pos, uv) \
|
||||
vb0.Position = Float3(-pos, 0.0f); \
|
||||
box.Merge(vb0.Position); \
|
||||
_vb0.Write(vb0); \
|
||||
vb1.TexCoord = Half2(uv); \
|
||||
vb1.Normal = Float1010102(normal * 0.5f + 0.5f, 0); \
|
||||
vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); \
|
||||
vb1.LightmapUVs = Half2::Zero; \
|
||||
_vb1.Write(vb1); \
|
||||
vb2.Color = color; \
|
||||
_vb2.Write(vb2)
|
||||
v.Position = Float3(-pos, 0.0f); \
|
||||
box.Merge(v.Position); \
|
||||
v.TexCoord = Half2(uv); \
|
||||
v.Normal = Float1010102(normal * 0.5f + 0.5f, 0); \
|
||||
v.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); \
|
||||
v.Color = color; \
|
||||
_vb.Write(v)
|
||||
//
|
||||
WRITE_VB(charRect.GetBottomRight(), rightBottomUV);
|
||||
WRITE_VB(charRect.GetBottomLeft(), Float2(upperLeftUV.X, rightBottomUV.Y));
|
||||
@@ -317,7 +327,7 @@ void TextRender::UpdateLayout()
|
||||
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
||||
// Setup collision proxy for detailed collision detection for triangles
|
||||
const int32 totalIndicesCount = _ib.Data.Count() / sizeof(uint16);
|
||||
_collisionProxy.Init(_vb0.Data.Count() / sizeof(Float3), totalIndicesCount / 3, (Float3*)_vb0.Data.Get(), (uint16*)_ib.Data.Get());
|
||||
_collisionProxy.Init(_vb.Data.Count() / sizeof(TextRenderVertex), totalIndicesCount / 3, (const Float3*)_vb.Data.Get(), (const uint16*)_ib.Data.Get(), sizeof(TextRenderVertex));
|
||||
#endif
|
||||
|
||||
// Update text bounds (from build vertex positions)
|
||||
@@ -351,16 +361,14 @@ void TextRender::Draw(RenderContext& renderContext)
|
||||
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
|
||||
|
||||
const DrawPass drawModes = DrawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(ShadowsMode);
|
||||
if (_vb0.Data.Count() > 0 && drawModes != DrawPass::None)
|
||||
if (_vb.Data.Count() > 0 && drawModes != DrawPass::None)
|
||||
{
|
||||
// Flush buffers
|
||||
if (_buffersDirty)
|
||||
{
|
||||
_buffersDirty = false;
|
||||
_ib.Flush();
|
||||
_vb0.Flush();
|
||||
_vb1.Flush();
|
||||
_vb2.Flush();
|
||||
_vb.Flush();
|
||||
}
|
||||
|
||||
// Setup draw call
|
||||
@@ -373,9 +381,7 @@ void TextRender::Draw(RenderContext& renderContext)
|
||||
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
|
||||
drawCall.PerInstanceRandom = GetPerInstanceRandom();
|
||||
drawCall.Geometry.IndexBuffer = _ib.GetBuffer();
|
||||
drawCall.Geometry.VertexBuffers[0] = _vb0.GetBuffer();
|
||||
drawCall.Geometry.VertexBuffers[1] = _vb1.GetBuffer();
|
||||
drawCall.Geometry.VertexBuffers[2] = _vb2.GetBuffer();
|
||||
drawCall.Geometry.VertexBuffers[0] = _vb.GetBuffer();
|
||||
drawCall.InstanceCount = 1;
|
||||
|
||||
// Submit draw calls
|
||||
|
||||
@@ -44,9 +44,7 @@ private:
|
||||
BoundingBox _localBox;
|
||||
GeometryDrawStateData _drawState;
|
||||
DynamicIndexBuffer _ib;
|
||||
DynamicVertexBuffer _vb0;
|
||||
DynamicVertexBuffer _vb1;
|
||||
DynamicVertexBuffer _vb2;
|
||||
DynamicVertexBuffer _vb;
|
||||
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
||||
CollisionProxy _collisionProxy;
|
||||
#endif
|
||||
|
||||
@@ -24,11 +24,18 @@ namespace FlaxEngine.Utilities
|
||||
|
||||
/// <summary>
|
||||
/// The vertex buffer.
|
||||
/// [Deprecated in v1.10]
|
||||
/// </summary>
|
||||
[Obsolete("Use new VertexAccessor.")]
|
||||
public Mesh.Vertex[] VertexBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// The vertex buffer accessor (with all available vertex buffers loaded in).
|
||||
/// </summary>
|
||||
public MeshAccessor VertexAccessor;
|
||||
}
|
||||
|
||||
private Model _model;
|
||||
private ModelBase _model;
|
||||
private MeshData[][] _meshDatas;
|
||||
private bool _inProgress;
|
||||
private bool _cancel;
|
||||
@@ -46,9 +53,9 @@ namespace FlaxEngine.Utilities
|
||||
/// <summary>
|
||||
/// Requests the mesh data.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to get it's data.</param>
|
||||
/// <param name="model">The model to get its data.</param>
|
||||
/// <returns>True if has valid data to access, otherwise false if it's during downloading.</returns>
|
||||
public bool RequestMeshData(Model model)
|
||||
public bool RequestMeshData(ModelBase model)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException();
|
||||
@@ -110,23 +117,29 @@ namespace FlaxEngine.Utilities
|
||||
if (_model.WaitForLoaded())
|
||||
throw new Exception("WaitForLoaded failed");
|
||||
|
||||
var lods = _model.LODs;
|
||||
_meshDatas = new MeshData[lods.Length][];
|
||||
var lodsCount = _model.LODsCount;
|
||||
_meshDatas = new MeshData[lodsCount][];
|
||||
|
||||
for (int lodIndex = 0; lodIndex < lods.Length && !_cancel; lodIndex++)
|
||||
Span<MeshBufferType> vertexBufferTypes = stackalloc MeshBufferType[3] { MeshBufferType.Vertex0, MeshBufferType.Vertex1, MeshBufferType.Vertex2 };
|
||||
for (int lodIndex = 0; lodIndex < lodsCount && !_cancel; lodIndex++)
|
||||
{
|
||||
var lod = lods[lodIndex];
|
||||
var meshes = lod.Meshes;
|
||||
_model.GetMeshes(out var meshes, lodIndex);
|
||||
_meshDatas[lodIndex] = new MeshData[meshes.Length];
|
||||
|
||||
for (int meshIndex = 0; meshIndex < meshes.Length && !_cancel; meshIndex++)
|
||||
{
|
||||
var mesh = meshes[meshIndex];
|
||||
_meshDatas[lodIndex][meshIndex] = new MeshData
|
||||
var meshData = new MeshData
|
||||
{
|
||||
IndexBuffer = mesh.DownloadIndexBuffer(),
|
||||
VertexBuffer = mesh.DownloadVertexBuffer()
|
||||
#pragma warning disable 0618
|
||||
VertexBuffer = mesh is Mesh m ? m.DownloadVertexBuffer() : null,
|
||||
#pragma warning restore 0618
|
||||
VertexAccessor = new MeshAccessor(),
|
||||
};
|
||||
if (meshData.VertexAccessor.LoadMesh(mesh, false, vertexBufferTypes))
|
||||
throw new Exception("MeshAccessor.LoadMesh failed");
|
||||
_meshDatas[lodIndex][meshIndex] = meshData;
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
|
||||
Reference in New Issue
Block a user