**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
|
// Get vertex data for each mesh
|
||||||
var meshes = model.LODs[0].Meshes;
|
var meshes = model.LODs[0].Meshes;
|
||||||
var meshesData = new SkinnedMesh.Vertex0[meshes.Length][];
|
var bonesVertices = new List<Float3>[bones.Length];
|
||||||
var bonesVertices = new List<SkinnedMesh.Vertex0>[bones.Length];
|
|
||||||
var indicesLimit = new Int4(bones.Length - 1);
|
var indicesLimit = new Int4(bones.Length - 1);
|
||||||
for (int i = 0; i < meshes.Length; i++)
|
for (int i = 0; i < meshes.Length; i++)
|
||||||
{
|
{
|
||||||
meshesData[i] = meshes[i].DownloadVertexBuffer0();
|
var assessor = new MeshAccessor();
|
||||||
|
if (assessor.LoadMesh(meshes[i]))
|
||||||
var meshData = meshes[i].DownloadVertexBuffer0();
|
return;
|
||||||
for (int j = 0; j < meshData.Length; j++)
|
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 = blendWeightsStream.GetFloat4(j);
|
||||||
var weights = (Float4)v.BlendWeights;
|
var indices = Int4.Min((Int4)blendIndicesStream.GetFloat4(j), indicesLimit);
|
||||||
var indices = Int4.Min((Int4)v.BlendIndices, indicesLimit);
|
|
||||||
|
|
||||||
// Find the bone with the highest influence on the vertex
|
// Find the bone with the highest influence on the vertex
|
||||||
var maxWeightIndex = 0;
|
var maxWeightIndex = 0;
|
||||||
@@ -104,17 +108,18 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
var maxWeightBone = indices[maxWeightIndex];
|
var maxWeightBone = indices[maxWeightIndex];
|
||||||
|
|
||||||
// Skin vertex position with the current pose
|
// Skin vertex position with the current pose
|
||||||
Float3.Transform(ref v.Position, ref skinningMatrices[indices[0]], out Float3 pos0);
|
var position = positionStream.GetFloat3(j);
|
||||||
Float3.Transform(ref v.Position, ref skinningMatrices[indices[1]], out Float3 pos1);
|
Float3.Transform(ref position, ref skinningMatrices[indices[0]], out Float3 pos0);
|
||||||
Float3.Transform(ref v.Position, ref skinningMatrices[indices[2]], out Float3 pos2);
|
Float3.Transform(ref position, ref skinningMatrices[indices[1]], out Float3 pos1);
|
||||||
Float3.Transform(ref v.Position, ref skinningMatrices[indices[3]], out Float3 pos3);
|
Float3.Transform(ref position, ref skinningMatrices[indices[2]], out Float3 pos2);
|
||||||
v.Position = pos0 * weights[0] + pos1 * weights[1] + pos2 * weights[2] + pos3 * weights[3];
|
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
|
// Add vertex to the bone list
|
||||||
ref var boneVertices = ref bonesVertices[maxWeightBone];
|
ref var boneVertices = ref bonesVertices[maxWeightBone];
|
||||||
if (boneVertices == null)
|
if (boneVertices == null)
|
||||||
boneVertices = new List<SkinnedMesh.Vertex0>();
|
boneVertices = new List<Float3>();
|
||||||
boneVertices.Add(v);
|
boneVertices.Add(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,10 +133,10 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
continue; // Skip not used bones
|
continue; // Skip not used bones
|
||||||
|
|
||||||
// Compute bounds of the vertices using this bone (in local space of the actor)
|
// 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++)
|
for (int i = 1; i < boneVertices.Count; i++)
|
||||||
{
|
{
|
||||||
var pos = boneVertices[i].Position;
|
var pos = boneVertices[i];
|
||||||
boneBoundsMin = Float3.Min(boneBoundsMin, pos);
|
boneBoundsMin = Float3.Min(boneBoundsMin, pos);
|
||||||
boneBoundsMax = Float3.Max(boneBoundsMax, pos);
|
boneBoundsMax = Float3.Max(boneBoundsMax, pos);
|
||||||
}
|
}
|
||||||
@@ -165,10 +170,10 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
var boneBounds = BoundingBox.Zero;
|
var boneBounds = BoundingBox.Zero;
|
||||||
if (boneVertices != null)
|
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++)
|
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.Min(boneBounds.Minimum, pos);
|
||||||
boneBounds.Minimum = Float3.Max(boneBounds.Maximum, pos);
|
boneBounds.Minimum = Float3.Max(boneBounds.Maximum, pos);
|
||||||
}
|
}
|
||||||
@@ -263,7 +268,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
var boneLocalBounds = BoundingBox.Zero;
|
var boneLocalBounds = BoundingBox.Zero;
|
||||||
for (int i = 0; i < boneVertices.Count; i++)
|
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.Min(ref boneLocalBounds.Minimum, ref pos, out boneLocalBounds.Minimum);
|
||||||
Vector3.Max(ref boneLocalBounds.Maximum, ref pos, out boneLocalBounds.Maximum);
|
Vector3.Max(ref boneLocalBounds.Maximum, ref pos, out boneLocalBounds.Maximum);
|
||||||
}
|
}
|
||||||
@@ -360,20 +365,20 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
|
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]
|
// [Reference: https://en.wikipedia.org/wiki/Covariance_matrix]
|
||||||
|
|
||||||
// Calculate average point
|
// Calculate average point
|
||||||
var avg = Float3.Zero;
|
var avg = Float3.Zero;
|
||||||
for (int i = 0; i < vertices.Count; i++)
|
for (int i = 0; i < vertices.Count; i++)
|
||||||
avg += vertices[i].Position;
|
avg += vertices[i];
|
||||||
avg /= vertices.Count;
|
avg /= vertices.Count;
|
||||||
|
|
||||||
// Calculate distance to average for every point
|
// Calculate distance to average for every point
|
||||||
var errors = new Float3[vertices.Count];
|
var errors = new Float3[vertices.Count];
|
||||||
for (int i = 0; i < vertices.Count; i++)
|
for (int i = 0; i < vertices.Count; i++)
|
||||||
errors[i] = vertices[i].Position - avg;
|
errors[i] = vertices[i] - avg;
|
||||||
|
|
||||||
var covariance = Matrix.Identity;
|
var covariance = Matrix.Identity;
|
||||||
var cj = stackalloc float[3];
|
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);
|
var row = new Float4(cj[0], cj[1], cj[2], 0.0f);
|
||||||
switch (j)
|
switch (j)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0: covariance.Row1 = row; break;
|
||||||
covariance.Row1 = row;
|
case 1: covariance.Row2 = row; break;
|
||||||
break;
|
case 2: covariance.Row3 = row; break;
|
||||||
case 1:
|
|
||||||
covariance.Row2 = row;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
covariance.Row3 = row;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return covariance;
|
return covariance;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
[HideInEditor]
|
[HideInEditor]
|
||||||
public sealed class StaticModelNode : ActorNode
|
public sealed class StaticModelNode : ActorNode
|
||||||
{
|
{
|
||||||
private Dictionary<IntPtr, Mesh.Vertex[]> _vertices;
|
private Dictionary<IntPtr, Float3[]> _vertices;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public StaticModelNode(Actor actor)
|
public StaticModelNode(Actor actor)
|
||||||
@@ -53,14 +53,17 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
var key = FlaxEngine.Object.GetUnmanagedPtr(mesh);
|
var key = FlaxEngine.Object.GetUnmanagedPtr(mesh);
|
||||||
if (!_vertices.TryGetValue(key, out var verts))
|
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)
|
if (verts == null)
|
||||||
continue;
|
continue;
|
||||||
_vertices.Add(key, verts);
|
_vertices.Add(key, verts);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < verts.Length; i++)
|
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);
|
var distance = Float3.DistanceSquared(ref pointLocal, ref v);
|
||||||
if (distance <= minDistance)
|
if (distance <= minDistance)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -414,10 +414,13 @@ namespace FlaxEditor.Tools
|
|||||||
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
||||||
{
|
{
|
||||||
var meshData = lodData[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, colors[vertexIndex]);
|
||||||
_selectedModel.SetVertexColor(lodIndex, meshIndex, vertexIndex, v.Color);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,10 +433,13 @@ namespace FlaxEditor.Tools
|
|||||||
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
||||||
{
|
{
|
||||||
var meshData = lodData[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(positionStream.GetFloat3(vertexIndex));
|
||||||
var pos = instanceTransform.LocalToWorld(v.Position);
|
|
||||||
var dst = Vector3.Distance(ref pos, ref brushSphere.Center);
|
var dst = Vector3.Distance(ref pos, ref brushSphere.Center);
|
||||||
if (dst > brushSphere.Radius)
|
if (dst > brushSphere.Radius)
|
||||||
continue;
|
continue;
|
||||||
@@ -590,12 +596,13 @@ namespace FlaxEditor.Tools
|
|||||||
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
||||||
{
|
{
|
||||||
var meshData = lodData[meshIndex];
|
var meshData = lodData[meshIndex];
|
||||||
if (meshData.VertexBuffer == null)
|
var positionStream = meshData.VertexAccessor.Position();
|
||||||
|
if (!positionStream.IsValid)
|
||||||
continue;
|
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(positionStream.GetFloat3(vertexIndex));
|
||||||
var pos = instanceTransform.LocalToWorld(v.Position);
|
|
||||||
if (brushSphere.Contains(ref pos) == ContainmentType.Disjoint)
|
if (brushSphere.Contains(ref pos) == ContainmentType.Disjoint)
|
||||||
continue;
|
continue;
|
||||||
Matrix transform = modelScaleMatrix * Matrix.Translation(pos - viewOrigin);
|
Matrix transform = modelScaleMatrix * Matrix.Translation(pos - viewOrigin);
|
||||||
|
|||||||
@@ -296,10 +296,17 @@ namespace FlaxEditor.Viewport.Previews
|
|||||||
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
|
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
|
||||||
{
|
{
|
||||||
var meshData = lod[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];
|
var count = positionStream.Count;
|
||||||
DebugDraw.DrawLine(v.Position, v.Position + v.Normal * 4.0f, Color.Blue);
|
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++)
|
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
|
||||||
{
|
{
|
||||||
var meshData = lod[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];
|
var count = positionStream.Count;
|
||||||
DebugDraw.DrawLine(v.Position, v.Position + v.Tangent * 4.0f, Color.Red);
|
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++)
|
for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
|
||||||
{
|
{
|
||||||
var meshData = lod[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];
|
var count = positionStream.Count;
|
||||||
DebugDraw.DrawLine(v.Position, v.Position + v.Bitangent * 4.0f, Color.Green);
|
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 FlaxEditor.GUI.Tabs;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
|
using FlaxEngine.Utilities;
|
||||||
|
|
||||||
#pragma warning disable 1591
|
#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 SplitPanel _split;
|
||||||
protected readonly Tabs _tabs;
|
protected readonly Tabs _tabs;
|
||||||
protected readonly ToolStripButton _saveButton;
|
protected readonly ToolStripButton _saveButton;
|
||||||
@@ -101,6 +266,8 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
protected int _isolateIndex = -1;
|
protected int _isolateIndex = -1;
|
||||||
protected int _highlightIndex = -1;
|
protected int _highlightIndex = -1;
|
||||||
|
|
||||||
|
protected MeshDataCache _meshData;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected ModelBaseWindow(Editor editor, AssetItem item)
|
protected ModelBaseWindow(Editor editor, AssetItem item)
|
||||||
: base(editor, item)
|
: base(editor, item)
|
||||||
@@ -142,6 +309,7 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void UnlinkItem()
|
protected override void UnlinkItem()
|
||||||
{
|
{
|
||||||
|
_meshData?.WaitForMeshDataRequestEnd();
|
||||||
foreach (var child in _tabs.Children)
|
foreach (var child in _tabs.Children)
|
||||||
{
|
{
|
||||||
if (child is Tab tab && tab.Proxy.Window != null)
|
if (child is Tab tab && tab.Proxy.Window != null)
|
||||||
@@ -170,6 +338,9 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnItemReimported(ContentItem item)
|
public override void OnItemReimported(ContentItem item)
|
||||||
{
|
{
|
||||||
|
// Discard any old mesh data cache
|
||||||
|
_meshData?.Dispose();
|
||||||
|
|
||||||
// Refresh the properties (will get new data in OnAssetLoaded)
|
// Refresh the properties (will get new data in OnAssetLoaded)
|
||||||
foreach (var child in _tabs.Children)
|
foreach (var child in _tabs.Children)
|
||||||
{
|
{
|
||||||
@@ -185,6 +356,16 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
base.OnItemReimported(item);
|
base.OnItemReimported(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnDestroy()
|
||||||
|
{
|
||||||
|
// Free mesh memory
|
||||||
|
_meshData?.Dispose();
|
||||||
|
_meshData = null;
|
||||||
|
|
||||||
|
base.OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override bool UseLayoutData => true;
|
public override bool UseLayoutData => true;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using FlaxEngine;
|
|||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
using FlaxEngine.Json;
|
using FlaxEngine.Json;
|
||||||
using FlaxEngine.Tools;
|
using FlaxEngine.Tools;
|
||||||
using FlaxEngine.Utilities;
|
|
||||||
using Object = FlaxEngine.Object;
|
using Object = FlaxEngine.Object;
|
||||||
|
|
||||||
namespace FlaxEditor.Windows.Assets
|
namespace FlaxEditor.Windows.Assets
|
||||||
@@ -495,7 +494,7 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
base.Initialize(layout);
|
base.Initialize(layout);
|
||||||
|
|
||||||
_uvsPreview = layout.Custom<UVsLayoutPreviewControl>().CustomControl;
|
_uvsPreview = layout.Custom<UVsLayoutPreviewControl>().CustomControl;
|
||||||
_uvsPreview.Proxy = proxy;
|
_uvsPreview.Window = proxy.Window;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -505,11 +504,17 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
|
|
||||||
if (_uvsPreview != null)
|
if (_uvsPreview != null)
|
||||||
{
|
{
|
||||||
_uvsPreview.Channel = _uvsPreview.Proxy._uvChannel;
|
var proxy = (UVsPropertiesProxy)Values[0];
|
||||||
_uvsPreview.LOD = _uvsPreview.Proxy.LOD;
|
switch (proxy._uvChannel)
|
||||||
_uvsPreview.Mesh = _uvsPreview.Proxy.Mesh;
|
{
|
||||||
_uvsPreview.HighlightIndex = _uvsPreview.Proxy.Window._highlightIndex;
|
case UVChannel.TexCoord: _uvsPreview.Channel = 0; break;
|
||||||
_uvsPreview.IsolateIndex = _uvsPreview.Proxy.Window._isolateIndex;
|
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();
|
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))]
|
[CustomEditor(typeof(ProxyEditor))]
|
||||||
@@ -802,7 +617,6 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
|
|
||||||
private readonly ModelPreview _preview;
|
private readonly ModelPreview _preview;
|
||||||
private StaticModel _highlightActor;
|
private StaticModel _highlightActor;
|
||||||
private MeshDataCache _meshData;
|
|
||||||
private ModelImportSettings _importSettings = new ModelImportSettings();
|
private ModelImportSettings _importSettings = new ModelImportSettings();
|
||||||
private ModelSdfOptions _sdfOptions;
|
private ModelSdfOptions _sdfOptions;
|
||||||
private ToolStripButton _showCurrentLODButton;
|
private ToolStripButton _showCurrentLODButton;
|
||||||
@@ -941,7 +755,6 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void UnlinkItem()
|
protected override void UnlinkItem()
|
||||||
{
|
{
|
||||||
_meshData?.WaitForMeshDataRequestEnd();
|
|
||||||
_preview.Model = null;
|
_preview.Model = null;
|
||||||
_highlightActor.Model = null;
|
_highlightActor.Model = null;
|
||||||
|
|
||||||
@@ -970,21 +783,9 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
base.OnAssetLoaded();
|
base.OnAssetLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void OnItemReimported(ContentItem item)
|
|
||||||
{
|
|
||||||
// Discard any old mesh data cache
|
|
||||||
_meshData?.Dispose();
|
|
||||||
|
|
||||||
base.OnItemReimported(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnDestroy()
|
public override void OnDestroy()
|
||||||
{
|
{
|
||||||
_meshData?.Dispose();
|
|
||||||
_meshData = null;
|
|
||||||
|
|
||||||
base.OnDestroy();
|
base.OnDestroy();
|
||||||
|
|
||||||
Object.Destroy(ref _highlightActor);
|
Object.Destroy(ref _highlightActor);
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FlaxEditor.Content;
|
using FlaxEditor.Content;
|
||||||
using FlaxEditor.Content.Import;
|
using FlaxEditor.Content.Import;
|
||||||
using FlaxEditor.CustomEditors;
|
using FlaxEditor.CustomEditors;
|
||||||
@@ -182,8 +180,6 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
proxy._highlightCheckBoxes.Clear();
|
proxy._highlightCheckBoxes.Clear();
|
||||||
var lods = proxy.Asset.LODs;
|
var lods = proxy.Asset.LODs;
|
||||||
var loadedLODs = proxy.Asset.LoadedLODs;
|
var loadedLODs = proxy.Asset.LoadedLODs;
|
||||||
var nodes = proxy.Asset.Nodes;
|
|
||||||
var bones = proxy.Asset.Bones;
|
|
||||||
|
|
||||||
// General properties
|
// General properties
|
||||||
{
|
{
|
||||||
@@ -286,8 +282,6 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
var proxy = (SkeletonPropertiesProxy)Values[0];
|
var proxy = (SkeletonPropertiesProxy)Values[0];
|
||||||
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
|
if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset))
|
||||||
return;
|
return;
|
||||||
var lods = proxy.Asset.LODs;
|
|
||||||
var loadedLODs = proxy.Asset.LoadedLODs;
|
|
||||||
var nodes = proxy.Asset.Nodes;
|
var nodes = proxy.Asset.Nodes;
|
||||||
var bones = proxy.Asset.Bones;
|
var bones = proxy.Asset.Bones;
|
||||||
|
|
||||||
@@ -523,7 +517,7 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
if (_uvChannel == value)
|
if (_uvChannel == value)
|
||||||
return;
|
return;
|
||||||
_uvChannel = value;
|
_uvChannel = value;
|
||||||
Window.RequestMeshData();
|
Window._meshData?.RequestMeshData(Window._asset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,7 +552,7 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
base.Initialize(layout);
|
base.Initialize(layout);
|
||||||
|
|
||||||
_uvsPreview = layout.Custom<UVsLayoutPreviewControl>().CustomControl;
|
_uvsPreview = layout.Custom<UVsLayoutPreviewControl>().CustomControl;
|
||||||
_uvsPreview.Proxy = proxy;
|
_uvsPreview.Window = proxy.Window;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -568,11 +562,12 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
|
|
||||||
if (_uvsPreview != null)
|
if (_uvsPreview != null)
|
||||||
{
|
{
|
||||||
_uvsPreview.Channel = _uvsPreview.Proxy._uvChannel;
|
var proxy = (UVsPropertiesProxy)Values[0];
|
||||||
_uvsPreview.LOD = _uvsPreview.Proxy.LOD;
|
_uvsPreview.Channel = proxy._uvChannel == UVChannel.TexCoord ? 0 : -1;
|
||||||
_uvsPreview.Mesh = _uvsPreview.Proxy.Mesh;
|
_uvsPreview.LOD = proxy.LOD;
|
||||||
_uvsPreview.HighlightIndex = _uvsPreview.Proxy.Window._highlightIndex;
|
_uvsPreview.Mesh = proxy.Mesh;
|
||||||
_uvsPreview.IsolateIndex = _uvsPreview.Proxy.Window._isolateIndex;
|
_uvsPreview.HighlightIndex = proxy.Window._highlightIndex;
|
||||||
|
_uvsPreview.IsolateIndex = proxy.Window._isolateIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,171 +578,6 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
base.Deinitialize();
|
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))]
|
[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 Preview _preview;
|
||||||
private AnimatedModel _highlightActor;
|
private AnimatedModel _highlightActor;
|
||||||
private ToolStripButton _showNodesButton;
|
private ToolStripButton _showNodesButton;
|
||||||
private ToolStripButton _showCurrentLODButton;
|
private ToolStripButton _showCurrentLODButton;
|
||||||
|
|
||||||
private MeshData[][] _meshDatas;
|
|
||||||
private bool _meshDatasInProgress;
|
|
||||||
private bool _meshDatasCancel;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public SkinnedModelWindow(Editor editor, AssetItem item)
|
public SkinnedModelWindow(Editor editor, AssetItem item)
|
||||||
: base(editor, 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 />
|
/// <inheritdoc />
|
||||||
public override void Update(float deltaTime)
|
public override void Update(float deltaTime)
|
||||||
@@ -1373,7 +1132,6 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void UnlinkItem()
|
protected override void UnlinkItem()
|
||||||
{
|
{
|
||||||
WaitForMeshDataRequestEnd();
|
|
||||||
_preview.SkinnedModel = null;
|
_preview.SkinnedModel = null;
|
||||||
_highlightActor.SkinnedModel = null;
|
_highlightActor.SkinnedModel = null;
|
||||||
|
|
||||||
@@ -1404,22 +1162,9 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
base.OnAssetLoaded();
|
base.OnAssetLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void OnItemReimported(ContentItem item)
|
|
||||||
{
|
|
||||||
// Discard any old mesh data cache
|
|
||||||
WaitForMeshDataRequestEnd();
|
|
||||||
_meshDatas = null;
|
|
||||||
_meshDatasInProgress = false;
|
|
||||||
|
|
||||||
base.OnItemReimported(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnDestroy()
|
public override void OnDestroy()
|
||||||
{
|
{
|
||||||
WaitForMeshDataRequestEnd();
|
|
||||||
|
|
||||||
base.OnDestroy();
|
base.OnDestroy();
|
||||||
|
|
||||||
Object.Destroy(ref _highlightActor);
|
Object.Destroy(ref _highlightActor);
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ bool CSGBuilderImpl::buildInner(Scene* scene, BuildData& data)
|
|||||||
|
|
||||||
// Convert CSG meshes into raw triangles data
|
// Convert CSG meshes into raw triangles data
|
||||||
RawData meshData;
|
RawData meshData;
|
||||||
Array<RawModelVertex> vertexBuffer;
|
Array<MeshVertex> vertexBuffer;
|
||||||
combinedMesh->Triangulate(meshData, vertexBuffer);
|
combinedMesh->Triangulate(meshData, vertexBuffer);
|
||||||
meshData.RemoveEmptySlots();
|
meshData.RemoveEmptySlots();
|
||||||
if (meshData.Slots.HasItems())
|
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();
|
auto& surface = Surfaces.AddOne();
|
||||||
surface.ScaleInLightmap = scaleInLightmap;
|
surface.ScaleInLightmap = scaleInLightmap;
|
||||||
@@ -65,7 +65,7 @@ void RawData::Slot::AddSurface(float scaleInLightmap, const Rectangle& lightmapU
|
|||||||
surface.Vertices.Add(firstVertex, vertexCount);
|
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
|
// Add surface to slot
|
||||||
auto slot = GetOrAddSlot(surfaceMaterial);
|
auto slot = GetOrAddSlot(surfaceMaterial);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace CSG
|
|||||||
Rectangle LightmapUVsBox;
|
Rectangle LightmapUVsBox;
|
||||||
Float2 Size;
|
Float2 Size;
|
||||||
Rectangle UVsArea;
|
Rectangle UVsArea;
|
||||||
Array<RawModelVertex> Vertices;
|
Array<MeshVertex> Vertices;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SurfaceTriangle
|
struct SurfaceTriangle
|
||||||
@@ -68,7 +68,7 @@ namespace CSG
|
|||||||
return Surfaces.IsEmpty();
|
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:
|
public:
|
||||||
@@ -118,7 +118,7 @@ namespace CSG
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
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>
|
/// <summary>
|
||||||
/// Removes the empty slots.
|
/// Removes the empty slots.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "Engine/Core/Collections/Dictionary.h"
|
#include "Engine/Core/Collections/Dictionary.h"
|
||||||
#include "CSGData.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
|
// Reject empty meshes
|
||||||
int32 verticesCount = _vertices.Count();
|
int32 verticesCount = _vertices.Count();
|
||||||
@@ -19,7 +19,7 @@ bool CSG::Mesh::Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const
|
|||||||
// Mesh triangles data
|
// Mesh triangles data
|
||||||
cacheVB.Clear();
|
cacheVB.Clear();
|
||||||
cacheVB.EnsureCapacity(_polygons.Count() * 3);
|
cacheVB.EnsureCapacity(_polygons.Count() * 3);
|
||||||
Array<RawModelVertex> surfaceCacheVB(32);
|
Array<MeshVertex> surfaceCacheVB(32);
|
||||||
|
|
||||||
// Cache submeshes by material to lay them down
|
// 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)
|
// 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 firstI, secondI, thirdI;
|
||||||
int32 triangleIndices[3];
|
int32 triangleIndices[3];
|
||||||
Vector2 uvs[3];
|
Vector2 uvs[3];
|
||||||
RawModelVertex rawVertex;
|
MeshVertex rawVertex;
|
||||||
for (int32 i = 0; i < _polygons.Count(); i++)
|
for (int32 i = 0; i < _polygons.Count(); i++)
|
||||||
{
|
{
|
||||||
const Polygon& polygon = _polygons[i];
|
const Polygon& polygon = _polygons[i];
|
||||||
@@ -142,7 +142,6 @@ bool CSG::Mesh::Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const
|
|||||||
rawVertex.Tangent = tangent;
|
rawVertex.Tangent = tangent;
|
||||||
rawVertex.Bitangent = bitangent;
|
rawVertex.Bitangent = bitangent;
|
||||||
rawVertex.LightmapUVs = Vector2::Zero;
|
rawVertex.LightmapUVs = Vector2::Zero;
|
||||||
rawVertex.Color = Color::Black;
|
|
||||||
|
|
||||||
cacheVB.Add(rawVertex);
|
cacheVB.Add(rawVertex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ namespace CSG
|
|||||||
/// <param name="data">Result data</param>
|
/// <param name="data">Result data</param>
|
||||||
/// <param name="cacheVB">Cache data</param>
|
/// <param name="cacheVB">Cache data</param>
|
||||||
/// <returns>True if cannot generate valid mesh data (due to missing triangles etc.)</returns>
|
/// <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>
|
/// <summary>
|
||||||
/// Perform CSG operation with another mesh
|
/// Perform CSG operation with another mesh
|
||||||
|
|||||||
@@ -8,4 +8,14 @@
|
|||||||
namespace CSG
|
namespace CSG
|
||||||
{
|
{
|
||||||
typedef ::BrushMode Mode;
|
typedef ::BrushMode Mode;
|
||||||
|
|
||||||
|
struct MeshVertex
|
||||||
|
{
|
||||||
|
Float3 Position;
|
||||||
|
Float2 TexCoord;
|
||||||
|
Float3 Normal;
|
||||||
|
Float3 Tangent;
|
||||||
|
Float3 Bitangent;
|
||||||
|
Float2 LightmapUVs;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,8 +13,10 @@
|
|||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||||
#include "Engine/Serialization/JsonWriters.h"
|
#include "Engine/Serialization/JsonWriters.h"
|
||||||
|
#include "Engine/Graphics/Models/ModelData.h"
|
||||||
#include "Engine/Content/JsonAsset.h"
|
#include "Engine/Content/JsonAsset.h"
|
||||||
#include "Engine/Level/Level.h"
|
#include "Engine/Level/Level.h"
|
||||||
|
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false);
|
REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false);
|
||||||
@@ -490,6 +492,64 @@ bool Animation::Save(const StringView& path)
|
|||||||
return false;
|
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
|
void Animation::GetReferences(Array<Guid>& assets, Array<String>& files) const
|
||||||
{
|
{
|
||||||
BinaryAsset::GetReferences(assets, files);
|
BinaryAsset::GetReferences(assets, files);
|
||||||
|
|||||||
@@ -151,6 +151,12 @@ public:
|
|||||||
bool Save(const StringView& path = StringView::Empty);
|
bool Save(const StringView& path = StringView::Empty);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if USE_EDITOR
|
||||||
|
friend class ImportModel;
|
||||||
|
static bool SaveHeader(const class ModelData& modelData, WriteStream& stream, int32 animIndex);
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// [BinaryAsset]
|
// [BinaryAsset]
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "Engine/Content/WeakAssetReference.h"
|
#include "Engine/Content/WeakAssetReference.h"
|
||||||
#include "Engine/Content/Upgraders/ModelAssetUpgrader.h"
|
#include "Engine/Content/Upgraders/ModelAssetUpgrader.h"
|
||||||
#include "Engine/Content/Factories/BinaryAssetFactory.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/RenderTools.h"
|
||||||
#include "Engine/Graphics/RenderTask.h"
|
#include "Engine/Graphics/RenderTask.h"
|
||||||
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
|
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
|
||||||
@@ -18,13 +18,13 @@
|
|||||||
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
|
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
|
||||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||||
#include "Engine/Graphics/Textures/GPUTexture.h"
|
#include "Engine/Graphics/Textures/GPUTexture.h"
|
||||||
#include "Engine/Graphics/Textures/TextureData.h"
|
|
||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
#include "Engine/Profiler/ProfilerCPU.h"
|
||||||
#include "Engine/Renderer/DrawCall.h"
|
#include "Engine/Renderer/DrawCall.h"
|
||||||
#include "Engine/Threading/Threading.h"
|
#include "Engine/Threading/Threading.h"
|
||||||
#include "Engine/Tools/ModelTool/ModelTool.h"
|
#include "Engine/Tools/ModelTool/ModelTool.h"
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||||
|
#include "Engine/Graphics/Textures/TextureData.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase");
|
REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase");
|
||||||
@@ -246,330 +246,6 @@ bool Model::SetupLODs(const Span<int32>& meshesCountPerLod)
|
|||||||
return Init(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)
|
bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold, bool useGPU)
|
||||||
{
|
{
|
||||||
if (EnableModelSDF == 2)
|
if (EnableModelSDF == 2)
|
||||||
@@ -666,6 +342,172 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
|
|||||||
return false;
|
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)
|
void Model::SetupMaterialSlots(int32 slotsCount)
|
||||||
{
|
{
|
||||||
ModelBase::SetupMaterialSlots(slotsCount);
|
ModelBase::SetupMaterialSlots(slotsCount);
|
||||||
@@ -687,6 +529,26 @@ int32 Model::GetLODsCount() const
|
|||||||
return LODs.Count();
|
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)
|
void Model::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
|
||||||
{
|
{
|
||||||
auto& lod = LODs[lodIndex];
|
auto& lod = LODs[lodIndex];
|
||||||
@@ -722,91 +584,11 @@ Asset::LoadResult Model::load()
|
|||||||
if (chunk0 == nullptr || chunk0->IsMissing())
|
if (chunk0 == nullptr || chunk0->IsMissing())
|
||||||
return LoadResult::MissingDataChunk;
|
return LoadResult::MissingDataChunk;
|
||||||
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
||||||
ReadStream* stream = &headerStream;
|
|
||||||
|
// Load asset data (anything but mesh contents that use streaming)
|
||||||
// Min Screen Size
|
byte headerVersion;
|
||||||
stream->ReadFloat(&MinScreenSize);
|
if (LoadHeader(headerStream, headerVersion))
|
||||||
|
|
||||||
// Amount of material slots
|
|
||||||
int32 materialSlotsCount;
|
|
||||||
stream->ReadInt32(&materialSlotsCount);
|
|
||||||
if (materialSlotsCount <= 0 || materialSlotsCount > 4096)
|
|
||||||
return LoadResult::InvalidData;
|
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
|
// Load SDF
|
||||||
auto chunk15 = GetChunk(15);
|
auto chunk15 = GetChunk(15);
|
||||||
@@ -815,7 +597,7 @@ Asset::LoadResult Model::load()
|
|||||||
PROFILE_CPU_NAMED("SDF");
|
PROFILE_CPU_NAMED("SDF");
|
||||||
MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size());
|
MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size());
|
||||||
int32 version;
|
int32 version;
|
||||||
sdfStream.ReadInt32(&version);
|
sdfStream.Read(version);
|
||||||
switch (version)
|
switch (version)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
API_CLASS(NoSpawn) class FLAXENGINE_API Model : public ModelBase
|
API_CLASS(NoSpawn) class FLAXENGINE_API Model : public ModelBase
|
||||||
{
|
{
|
||||||
DECLARE_BINARY_ASSET_HEADER(Model, 25);
|
DECLARE_BINARY_ASSET_HEADER(Model, 30);
|
||||||
friend Mesh;
|
friend Mesh;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -274,19 +274,6 @@ public:
|
|||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <returns>True if failed, otherwise false.</returns>
|
||||||
API_FUNCTION() bool SetupLODs(const Span<int32>& meshesCountPerLod);
|
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>
|
/// <summary>
|
||||||
/// Generates the Sign Distant Field for this model.
|
/// Generates the Sign Distant Field for this model.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -312,10 +299,22 @@ private:
|
|||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <returns>True if failed, otherwise false.</returns>
|
||||||
bool Init(const Span<int32>& meshesCountPerLod);
|
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:
|
public:
|
||||||
// [ModelBase]
|
// [ModelBase]
|
||||||
void SetupMaterialSlots(int32 slotsCount) override;
|
void SetupMaterialSlots(int32 slotsCount) override;
|
||||||
int32 GetLODsCount() const 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 GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||||
void InitAsVirtual() override;
|
void InitAsVirtual() override;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
#include "Engine/Content/WeakAssetReference.h"
|
#include "Engine/Content/WeakAssetReference.h"
|
||||||
#include "Engine/Serialization/MemoryReadStream.h"
|
#include "Engine/Serialization/MemoryReadStream.h"
|
||||||
#include "Engine/Graphics/Config.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
|
#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
|
||||||
#include "Engine/Threading/ThreadPoolTask.h"
|
#include "Engine/Threading/ThreadPoolTask.h"
|
||||||
#define STREAM_TASK_BASE ThreadPoolTask
|
#define STREAM_TASK_BASE ThreadPoolTask
|
||||||
@@ -12,8 +15,11 @@
|
|||||||
#include "Engine/Threading/MainThreadTask.h"
|
#include "Engine/Threading/MainThreadTask.h"
|
||||||
#define STREAM_TASK_BASE MainThreadTask
|
#define STREAM_TASK_BASE MainThreadTask
|
||||||
#endif
|
#endif
|
||||||
#include "SkinnedModel.h" // TODO: remove this
|
#if USE_EDITOR
|
||||||
#include "Model.h" // TODO: remove this
|
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||||
|
#include "Engine/Graphics/Models/ModelData.h"
|
||||||
|
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Model LOD streaming task.
|
/// Model LOD streaming task.
|
||||||
@@ -54,93 +60,26 @@ public:
|
|||||||
}
|
}
|
||||||
MemoryReadStream stream(data.Get(), data.Length());
|
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 meshes data and pass data to the GPU buffers
|
||||||
|
|
||||||
// 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)
|
|
||||||
Array<MeshBase*> meshes;
|
Array<MeshBase*> meshes;
|
||||||
model->GetMeshes(meshes, _lodIndex);
|
model->GetMeshes(meshes, _lodIndex);
|
||||||
if (model->Is<SkinnedModel>())
|
byte meshVersion = stream.ReadByte();
|
||||||
|
if (meshVersion < 2 || meshVersion > ModelBase::MeshVersion)
|
||||||
{
|
{
|
||||||
byte version = stream.ReadByte();
|
LOG(Warning, "Unsupported mesh version {}", meshVersion);
|
||||||
for (int32 i = 0; i < meshes.Count(); i++)
|
return true;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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];
|
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'", meshIndex, _lodIndex, model->ToString());
|
||||||
|
return true;
|
||||||
// #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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update residency level
|
// 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->_loadedLODs++;
|
||||||
model->ResidencyChanged();
|
model->ResidencyChanged();
|
||||||
|
|
||||||
@@ -209,6 +148,638 @@ void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const
|
|||||||
GetChunkData(chunkIndex, data);
|
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()
|
void ModelBase::CancelStreaming()
|
||||||
{
|
{
|
||||||
BinaryAsset::CancelStreaming();
|
BinaryAsset::CancelStreaming();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1)
|
#define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1)
|
||||||
|
|
||||||
class MeshBase;
|
class MeshBase;
|
||||||
|
class StreamModelLODTask;
|
||||||
struct RenderContextBatch;
|
struct RenderContextBatch;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -28,9 +29,12 @@ API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset
|
|||||||
{
|
{
|
||||||
DECLARE_ASSET_HEADER(ModelBase);
|
DECLARE_ASSET_HEADER(ModelBase);
|
||||||
friend MeshBase;
|
friend MeshBase;
|
||||||
friend class StreamModelLODTask;
|
friend StreamModelLODTask;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
static constexpr byte HeaderVersion = 2;
|
||||||
|
static constexpr byte MeshVersion = 2;
|
||||||
|
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
int32 _loadedLODs = 0;
|
int32 _loadedLODs = 0;
|
||||||
StreamModelLODTask* _streamingTask = nullptr;
|
StreamModelLODTask* _streamingTask = nullptr;
|
||||||
@@ -174,12 +178,27 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets amount of the level of details in the model.
|
/// Gets amount of the level of details in the model.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets the meshes for a particular LOD index.
|
/// Gets the meshes for a particular LOD index.
|
||||||
/// </summary>
|
/// </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:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -196,6 +215,38 @@ public:
|
|||||||
/// <param name="data">The data (it may be missing if failed to get it).</param>
|
/// <param name="data">The data (it may be missing if failed to get it).</param>
|
||||||
void GetLODData(int32 lodIndex, BytesContainer& data) const;
|
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:
|
public:
|
||||||
// [BinaryAsset]
|
// [BinaryAsset]
|
||||||
void CancelStreaming() override;
|
void CancelStreaming() override;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
#include "Engine/Profiler/ProfilerCPU.h"
|
||||||
#include "Engine/Renderer/DrawCall.h"
|
#include "Engine/Renderer/DrawCall.h"
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
|
#include "Engine/Graphics/Models/ModelData.h"
|
||||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -121,9 +122,9 @@ SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bo
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if !BUILD_RELEASE
|
#if !BUILD_RELEASE
|
||||||
LOG(Error, "Missing asset {0} to use for skeleton mapping of {1}", retarget->SkeletonAsset, ToString());
|
LOG(Error, "Missing asset {0} to use for skeleton mapping of {1}", retarget->SkeletonAsset, ToString());
|
||||||
#endif
|
#endif
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +232,7 @@ FORCE_INLINE void SkinnedModelDraw(SkinnedModel* model, const RenderContext& ren
|
|||||||
if (!model->CanBeRendered())
|
if (!model->CanBeRendered())
|
||||||
return;
|
return;
|
||||||
if (!info.Buffer->IsValidFor(model))
|
if (!info.Buffer->IsValidFor(model))
|
||||||
info.Buffer->Setup(model);
|
info.Buffer->Setup(model);
|
||||||
const auto frame = Engine::FrameCount;
|
const auto frame = Engine::FrameCount;
|
||||||
const auto modelFrame = info.DrawState->PrevFrame + 1;
|
const auto modelFrame = info.DrawState->PrevFrame + 1;
|
||||||
|
|
||||||
@@ -395,7 +396,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
|||||||
// Validate input
|
// Validate input
|
||||||
if (nodes.Count() <= 0 || nodes.Count() > MAX_uint16)
|
if (nodes.Count() <= 0 || nodes.Count() > MAX_uint16)
|
||||||
return true;
|
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;
|
return true;
|
||||||
auto model = this;
|
auto model = this;
|
||||||
if (!model->IsVirtual())
|
if (!model->IsVirtual())
|
||||||
@@ -431,310 +432,6 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
|||||||
return false;
|
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)
|
bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
||||||
{
|
{
|
||||||
if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS)
|
if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS)
|
||||||
@@ -777,6 +474,329 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
|||||||
return false;
|
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()
|
void SkinnedModel::ClearSkeletonMapping()
|
||||||
{
|
{
|
||||||
for (auto& e : _skeletonMappingCache)
|
for (auto& e : _skeletonMappingCache)
|
||||||
@@ -841,6 +861,26 @@ int32 SkinnedModel::GetLODsCount() const
|
|||||||
return LODs.Count();
|
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)
|
void SkinnedModel::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
|
||||||
{
|
{
|
||||||
auto& lod = LODs[lodIndex];
|
auto& lod = LODs[lodIndex];
|
||||||
@@ -889,145 +929,11 @@ Asset::LoadResult SkinnedModel::load()
|
|||||||
if (chunk0 == nullptr || chunk0->IsMissing())
|
if (chunk0 == nullptr || chunk0->IsMissing())
|
||||||
return LoadResult::MissingDataChunk;
|
return LoadResult::MissingDataChunk;
|
||||||
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
||||||
ReadStream* stream = &headerStream;
|
|
||||||
|
|
||||||
// Header Version
|
// Load asset data (anything but mesh contents that use streaming)
|
||||||
byte version = stream->ReadByte();
|
byte headerVersion;
|
||||||
|
if (LoadHeader(headerStream, headerVersion))
|
||||||
// Min Screen Size
|
|
||||||
stream->ReadFloat(&MinScreenSize);
|
|
||||||
|
|
||||||
// Amount of material slots
|
|
||||||
int32 materialSlotsCount;
|
|
||||||
stream->ReadInt32(&materialSlotsCount);
|
|
||||||
if (materialSlotsCount < 0 || materialSlotsCount > 4096)
|
|
||||||
return LoadResult::InvalidData;
|
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
|
// Request resource streaming
|
||||||
StartStreaming(true);
|
StartStreaming(true);
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
|
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
|
||||||
{
|
{
|
||||||
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5);
|
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 30);
|
||||||
friend SkinnedMesh;
|
friend SkinnedMesh;
|
||||||
public:
|
public:
|
||||||
// Skeleton mapping descriptor.
|
// Skeleton mapping descriptor.
|
||||||
@@ -331,17 +331,6 @@ public:
|
|||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <returns>True if failed, otherwise false.</returns>
|
||||||
API_FUNCTION() bool SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<SkeletonBone>& bones, bool autoCalculateOffsetMatrix);
|
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:
|
private:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes this skinned model to an empty collection of meshes. Ensure to init SkeletonData manually after the call.
|
/// 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>
|
/// <returns>True if failed, otherwise false.</returns>
|
||||||
bool Init(const Span<int32>& meshesCountPerLod);
|
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 ClearSkeletonMapping();
|
||||||
void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
|
void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
|
||||||
|
|
||||||
@@ -384,6 +384,9 @@ public:
|
|||||||
uint64 GetMemoryUsage() const override;
|
uint64 GetMemoryUsage() const override;
|
||||||
void SetupMaterialSlots(int32 slotsCount) override;
|
void SetupMaterialSlots(int32 slotsCount) override;
|
||||||
int32 GetLODsCount() const 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 GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||||
void InitAsVirtual() override;
|
void InitAsVirtual() override;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
|
|
||||||
#include "BinaryAssetUpgrader.h"
|
#include "BinaryAssetUpgrader.h"
|
||||||
|
#include "Engine/Serialization/MemoryReadStream.h"
|
||||||
|
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||||
|
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Model Asset Upgrader
|
/// Model Asset Upgrader
|
||||||
@@ -13,17 +16,145 @@
|
|||||||
class ModelAssetUpgrader : public BinaryAssetUpgrader
|
class ModelAssetUpgrader : public BinaryAssetUpgrader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ModelAssetUpgrader"/> class.
|
|
||||||
/// </summary>
|
|
||||||
ModelAssetUpgrader()
|
ModelAssetUpgrader()
|
||||||
{
|
{
|
||||||
static const Upgrader upgraders[] =
|
Upgrader upgraders[] =
|
||||||
{
|
{
|
||||||
{},
|
{ 25, 30, Upgrade_25_To_30 }, // [Deprecated in v1.10]
|
||||||
};
|
};
|
||||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
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
|
#endif
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
|
|
||||||
#include "BinaryAssetUpgrader.h"
|
#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>
|
/// <summary>
|
||||||
/// Skinned Model Asset Upgrader
|
/// Skinned Model Asset Upgrader
|
||||||
@@ -13,17 +17,218 @@
|
|||||||
class SkinnedModelAssetUpgrader : public BinaryAssetUpgrader
|
class SkinnedModelAssetUpgrader : public BinaryAssetUpgrader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="SkinnedModelAssetUpgrader"/> class.
|
|
||||||
/// </summary>
|
|
||||||
SkinnedModelAssetUpgrader()
|
SkinnedModelAssetUpgrader()
|
||||||
{
|
{
|
||||||
static const Upgrader upgraders[] =
|
const Upgrader upgraders[] =
|
||||||
{
|
{
|
||||||
{},
|
{ 5, 30, Upgrade_5_To_30 }, // [Deprecated in v1.10]
|
||||||
};
|
};
|
||||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
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
|
#endif
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||||
#include "Engine/Serialization/FileWriteStream.h"
|
#include "Engine/Serialization/FileWriteStream.h"
|
||||||
#include "Engine/Serialization/MemoryReadStream.h"
|
#include "Engine/Serialization/MemoryReadStream.h"
|
||||||
|
#include "Engine/Graphics/Models/MeshAccessor.h"
|
||||||
#include "Engine/Core/DeleteMe.h"
|
#include "Engine/Core/DeleteMe.h"
|
||||||
|
|
||||||
ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||||
{
|
{
|
||||||
// Prepare
|
auto asset = (ModelBase*)context.Asset.Get();
|
||||||
auto asset = (Model*)context.Asset.Get();
|
|
||||||
auto lock = asset->Storage->LockSafe();
|
auto lock = asset->Storage->LockSafe();
|
||||||
auto path = GET_OUTPUT_PATH(context, "obj");
|
auto path = GET_OUTPUT_PATH(context, "obj");
|
||||||
const int32 lodIndex = 0;
|
const int32 lodIndex = 0;
|
||||||
@@ -26,7 +26,6 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
|||||||
const auto chunk = asset->GetChunk(chunkIndex);
|
const auto chunk = asset->GetChunk(chunkIndex);
|
||||||
if (!chunk)
|
if (!chunk)
|
||||||
return ExportAssetResult::CannotLoadData;
|
return ExportAssetResult::CannotLoadData;
|
||||||
|
|
||||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||||
FileWriteStream* output = FileWriteStream::Open(path);
|
FileWriteStream* output = FileWriteStream::Open(path);
|
||||||
if (output == nullptr)
|
if (output == nullptr)
|
||||||
@@ -37,62 +36,61 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
|||||||
output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get()));
|
output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get()));
|
||||||
|
|
||||||
// Extract all meshes
|
// Extract all meshes
|
||||||
const auto& lod = asset->LODs[lodIndex];
|
if (asset->GetLODsCount() <= lodIndex)
|
||||||
int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
|
return ExportAssetResult::Error;
|
||||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
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];
|
auto mesh = meshes[meshIndex];
|
||||||
|
if (asset->LoadMesh(stream, meshVersion, mesh, &meshData))
|
||||||
// #MODEL_DATA_FORMAT_USAGE
|
return ExportAssetResult::CannotLoadData;
|
||||||
uint32 vertices;
|
if (meshData.Vertices == 0 || meshData.Triangles == 0)
|
||||||
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 ExportAssetResult::Error;
|
return ExportAssetResult::Error;
|
||||||
auto vb0 = stream.Move<VB0ElementType>(vertices);
|
MeshAccessor accessor;
|
||||||
auto vb1 = stream.Move<VB1ElementType>(vertices);
|
if (accessor.LoadFromMeshData(&meshData))
|
||||||
bool hasColors = stream.ReadBool();
|
return ExportAssetResult::CannotLoadAsset;
|
||||||
VB2ElementType18* vb2 = nullptr;
|
|
||||||
if (hasColors)
|
|
||||||
{
|
|
||||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
|
||||||
}
|
|
||||||
auto ib = stream.Move<byte>(indicesCount * ibStride);
|
|
||||||
|
|
||||||
output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex));
|
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->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||||
}
|
}
|
||||||
|
|
||||||
output->WriteChar('\n');
|
output->WriteChar('\n');
|
||||||
|
|
||||||
for (uint32 i = 0; i < vertices; i++)
|
auto texCoordStream = accessor.TexCoord();
|
||||||
|
if (texCoordStream.IsValid())
|
||||||
{
|
{
|
||||||
auto v = vb1[i].TexCoord.ToFloat2();
|
for (uint32 i = 0; i < meshData.Vertices; i++)
|
||||||
output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y));
|
{
|
||||||
|
auto v = texCoordStream.GetFloat2(i);
|
||||||
|
output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y));
|
||||||
|
}
|
||||||
|
output->WriteChar('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
output->WriteChar('\n');
|
auto normalStream = accessor.Normal();
|
||||||
|
if (normalStream.IsValid())
|
||||||
for (uint32 i = 0; i < vertices; i++)
|
|
||||||
{
|
{
|
||||||
auto v = vb1[i].Normal.ToFloat3() * 2.0f - 1.0f;
|
for (uint32 i = 0; i < meshData.Vertices; i++)
|
||||||
output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z));
|
{
|
||||||
|
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 (meshData.IBStride == sizeof(uint16))
|
||||||
|
|
||||||
if (use16BitIndexBuffer)
|
|
||||||
{
|
{
|
||||||
auto t = (uint16*)ib;
|
auto t = (const uint16*)meshData.IBData;
|
||||||
for (uint32 i = 0; i < triangles; i++)
|
for (uint32 i = 0; i < meshData.Triangles; i++)
|
||||||
{
|
{
|
||||||
auto i0 = vertexStart + *t++;
|
auto i0 = vertexStart + *t++;
|
||||||
auto i1 = vertexStart + *t++;
|
auto i1 = vertexStart + *t++;
|
||||||
@@ -102,8 +100,8 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto t = (uint32*)ib;
|
auto t = (const uint32*)meshData.IBData;
|
||||||
for (uint32 i = 0; i < triangles; i++)
|
for (uint32 i = 0; i < meshData.Triangles; i++)
|
||||||
{
|
{
|
||||||
auto i0 = vertexStart + *t++;
|
auto i0 = vertexStart + *t++;
|
||||||
auto i1 = 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->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output->WriteChar('\n');
|
output->WriteChar('\n');
|
||||||
|
|
||||||
vertexStart += vertices;
|
vertexStart += meshData.Vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output->HasError())
|
if (output->HasError())
|
||||||
@@ -125,120 +122,8 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
|||||||
|
|
||||||
ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context)
|
ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context)
|
||||||
{
|
{
|
||||||
// Prepare
|
// The same code, except SkinnedModel::LoadMesh will be used to read Blend Shapes data
|
||||||
auto asset = (SkinnedModel*)context.Asset.Get();
|
return ExportModel(context);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ void RepackMeshLightmapUVs(ModelData& data)
|
|||||||
{
|
{
|
||||||
Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv);
|
Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv);
|
||||||
Float2 uvScale(entry.Slot->Width * atlasSizeInv, entry.Slot->Height * atlasSizeInv);
|
Float2 uvScale(entry.Slot->Width * atlasSizeInv, entry.Slot->Height * atlasSizeInv);
|
||||||
// TODO: SIMD
|
|
||||||
for (auto& uv : entry.Mesh->LightmapUVs)
|
for (auto& uv : entry.Mesh->LightmapUVs)
|
||||||
{
|
{
|
||||||
uv = uv * uvScale + uvOffset;
|
uv = uv * uvScale + uvOffset;
|
||||||
@@ -540,37 +539,28 @@ CreateAssetResult ImportModel::Create(CreateAssetContext& context)
|
|||||||
return CreateModel(context, modelData);
|
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();
|
PROFILE_CPU();
|
||||||
IMPORT_SETUP(Model, Model::SerializedVersion);
|
IMPORT_SETUP(Model, Model::SerializedVersion);
|
||||||
|
static_assert(Model::SerializedVersion == 30, "Update code.");
|
||||||
|
|
||||||
// Save model header
|
// Save model header
|
||||||
MemoryWriteStream stream(4096);
|
MemoryWriteStream stream(4096);
|
||||||
if (modelData.Pack2ModelHeader(&stream))
|
if (Model::SaveHeader(stream, modelData))
|
||||||
return CreateAssetResult::Error;
|
return CreateAssetResult::Error;
|
||||||
if (context.AllocateChunk(0))
|
if (context.AllocateChunk(0))
|
||||||
return CreateAssetResult::CannotAllocateChunk;
|
return CreateAssetResult::CannotAllocateChunk;
|
||||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||||
|
|
||||||
// Pack model LODs data
|
// Pack model LODs data
|
||||||
const auto lodCount = modelData.GetLODsCount();
|
const auto lodCount = modelData.LODs.Count();
|
||||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||||
{
|
{
|
||||||
stream.SetPosition(0);
|
stream.SetPosition(0);
|
||||||
|
if (Model::SaveLOD(stream, modelData, lodIndex))
|
||||||
// Pack meshes
|
return CreateAssetResult::Error;
|
||||||
auto& meshes = modelData.LODs[lodIndex].Meshes;
|
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
|
||||||
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 (context.AllocateChunk(chunkIndex))
|
if (context.AllocateChunk(chunkIndex))
|
||||||
return CreateAssetResult::CannotAllocateChunk;
|
return CreateAssetResult::CannotAllocateChunk;
|
||||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||||
@@ -591,40 +581,28 @@ CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelDat
|
|||||||
return CreateAssetResult::Ok;
|
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();
|
PROFILE_CPU();
|
||||||
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
|
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
|
||||||
|
static_assert(SkinnedModel::SerializedVersion == 30, "Update code.");
|
||||||
|
|
||||||
// Save skinned model header
|
// Save skinned model header
|
||||||
MemoryWriteStream stream(4096);
|
MemoryWriteStream stream(4096);
|
||||||
if (modelData.Pack2SkinnedModelHeader(&stream))
|
if (SkinnedModel::SaveHeader(stream, modelData))
|
||||||
return CreateAssetResult::Error;
|
return CreateAssetResult::Error;
|
||||||
if (context.AllocateChunk(0))
|
if (context.AllocateChunk(0))
|
||||||
return CreateAssetResult::CannotAllocateChunk;
|
return CreateAssetResult::CannotAllocateChunk;
|
||||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||||
|
|
||||||
// Pack model LODs data
|
// Pack model LODs data
|
||||||
const auto lodCount = modelData.GetLODsCount();
|
const auto lodCount = modelData.LODs.Count();
|
||||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||||
{
|
{
|
||||||
stream.SetPosition(0);
|
stream.SetPosition(0);
|
||||||
|
if (SkinnedModel::SaveLOD(stream, modelData, lodIndex, SkinnedModel::SaveMesh))
|
||||||
// Mesh Data Version
|
return CreateAssetResult::Error;
|
||||||
stream.WriteByte(1);
|
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
|
||||||
|
|
||||||
// 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 (context.AllocateChunk(chunkIndex))
|
if (context.AllocateChunk(chunkIndex))
|
||||||
return CreateAssetResult::CannotAllocateChunk;
|
return CreateAssetResult::CannotAllocateChunk;
|
||||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||||
@@ -633,10 +611,11 @@ CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, M
|
|||||||
return CreateAssetResult::Ok;
|
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();
|
PROFILE_CPU();
|
||||||
IMPORT_SETUP(Animation, Animation::SerializedVersion);
|
IMPORT_SETUP(Animation, Animation::SerializedVersion);
|
||||||
|
static_assert(Animation::SerializedVersion == 1, "Update code.");
|
||||||
|
|
||||||
// Save animation data
|
// Save animation data
|
||||||
MemoryWriteStream stream(8182);
|
MemoryWriteStream stream(8182);
|
||||||
@@ -651,7 +630,7 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode
|
|||||||
animIndex = i;
|
animIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (modelData.Pack2AnimationHeader(&stream, animIndex))
|
if (Animation::SaveHeader(modelData, stream, animIndex))
|
||||||
return CreateAssetResult::Error;
|
return CreateAssetResult::Error;
|
||||||
if (context.AllocateChunk(0))
|
if (context.AllocateChunk(0))
|
||||||
return CreateAssetResult::CannotAllocateChunk;
|
return CreateAssetResult::CannotAllocateChunk;
|
||||||
@@ -660,7 +639,7 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode
|
|||||||
return CreateAssetResult::Ok;
|
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();
|
PROFILE_CPU();
|
||||||
if (data.Nodes.Count() == 0)
|
if (data.Nodes.Count() == 0)
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ public:
|
|||||||
static CreateAssetResult Create(CreateAssetContext& context);
|
static CreateAssetResult Create(CreateAssetContext& context);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static CreateAssetResult CreateModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
static CreateAssetResult CreateModel(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr);
|
||||||
static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr);
|
||||||
static CreateAssetResult CreateAnimation(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
static CreateAssetResult CreateAnimation(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr);
|
||||||
static CreateAssetResult CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<struct PrefabObject>& prefabObjects);
|
static CreateAssetResult CreatePrefab(CreateAssetContext& context, const ModelData& data, const Options& options, const Array<struct PrefabObject>& prefabObjects);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -356,17 +356,29 @@ namespace FlaxEngine
|
|||||||
/// <returns>The newly constructed bounding box.</returns>
|
/// <returns>The newly constructed bounding box.</returns>
|
||||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="points" /> is <c>null</c>.</exception>
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="points" /> is <c>null</c>.</exception>
|
||||||
public static BoundingBox FromPoints(Vector3[] points)
|
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)
|
if (points == null)
|
||||||
throw new ArgumentNullException(nameof(points));
|
throw new ArgumentNullException(nameof(points));
|
||||||
var min = Vector3.Maximum;
|
var min = Float3.Maximum;
|
||||||
var max = Vector3.Minimum;
|
var max = Float3.Minimum;
|
||||||
for (var i = 0; i < points.Length; ++i)
|
for (var i = 0; i < points.Length; ++i)
|
||||||
{
|
{
|
||||||
Vector3.Min(ref min, ref points[i], out min);
|
Float3.Min(ref min, ref points[i], out min);
|
||||||
Vector3.Max(ref max, ref points[i], out max);
|
Float3.Max(ref max, ref points[i], out max);
|
||||||
}
|
}
|
||||||
return new BoundingBox(min, max);
|
result = new BoundingBox(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1343,6 +1343,16 @@ namespace FlaxEngine
|
|||||||
return new Float3(value.X, value.Y, value.Z);
|
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>
|
/// <summary>
|
||||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ int32 GameBase::LoadProduct()
|
|||||||
goto LOAD_GAME_HEAD_FAILED;
|
goto LOAD_GAME_HEAD_FAILED;
|
||||||
|
|
||||||
// Load game primary data
|
// Load game primary data
|
||||||
stream->ReadArray(&data);
|
stream->Read(data);
|
||||||
if (data.Count() != 808 + sizeof(Guid))
|
if (data.Count() != 808 + sizeof(Guid))
|
||||||
goto LOAD_GAME_HEAD_FAILED;
|
goto LOAD_GAME_HEAD_FAILED;
|
||||||
Encryption::DecryptBytes(data.Get(), data.Count());
|
Encryption::DecryptBytes(data.Get(), data.Count());
|
||||||
|
|||||||
@@ -400,6 +400,12 @@ namespace FlaxEngine
|
|||||||
Format = format;
|
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 />
|
/// <inheritdoc />
|
||||||
public bool Equals(VertexElement other)
|
public bool Equals(VertexElement other)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -68,4 +68,11 @@ public:
|
|||||||
/// The list of shape vertices.
|
/// The list of shape vertices.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Array<BlendShapeVertex> Vertices;
|
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>
|
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.Clear();
|
||||||
Triangles.EnsureCapacity(triangles, false);
|
Triangles.EnsureCapacity(triangles, false);
|
||||||
|
|
||||||
const IndexType* it = indices;
|
const IndexType* it = indices;
|
||||||
for (uint32 i = 0; i < triangles; i++)
|
for (uint32 i = 0; i < triangles; i++)
|
||||||
{
|
{
|
||||||
auto i0 = *(it++);
|
const IndexType i0 = *(it++);
|
||||||
auto i1 = *(it++);
|
const IndexType i1 = *(it++);
|
||||||
auto i2 = *(it++);
|
const IndexType i2 = *(it++);
|
||||||
|
|
||||||
if (i0 < vertices && i1 < vertices && i2 < vertices)
|
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
|
#define MODEL_MAX_MESHES 4096
|
||||||
|
|
||||||
// Maximum amount of texture channels (UVs) per vertex
|
// 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)
|
// 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)
|
#define MODEL_USE_PRECISE_MESH_INTERSECTS (USE_EDITOR)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
#include "Mesh.h"
|
#include "Mesh.h"
|
||||||
|
#include "MeshAccessor.h"
|
||||||
#include "MeshDeformation.h"
|
#include "MeshDeformation.h"
|
||||||
#include "ModelInstanceEntry.h"
|
#include "ModelInstanceEntry.h"
|
||||||
#include "Engine/Content/Assets/Material.h"
|
#include "Engine/Content/Assets/Material.h"
|
||||||
@@ -11,16 +12,14 @@
|
|||||||
#include "Engine/Graphics/RenderTask.h"
|
#include "Engine/Graphics/RenderTask.h"
|
||||||
#include "Engine/Graphics/RenderTools.h"
|
#include "Engine/Graphics/RenderTools.h"
|
||||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
|
||||||
#include "Engine/Renderer/RenderList.h"
|
#include "Engine/Renderer/RenderList.h"
|
||||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||||
#include "Engine/Serialization/MemoryReadStream.h"
|
|
||||||
#include "Engine/Threading/Task.h"
|
|
||||||
#include "Engine/Threading/Threading.h"
|
#include "Engine/Threading/Threading.h"
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
#include "Engine/Renderer/GBufferPass.h"
|
#include "Engine/Renderer/GBufferPass.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
GPUVertexLayout* VB0ElementType18::GetLayout()
|
GPUVertexLayout* VB0ElementType18::GetLayout()
|
||||||
{
|
{
|
||||||
return GPUVertexLayout::Get({
|
return GPUVertexLayout::Get({
|
||||||
@@ -44,77 +43,100 @@ GPUVertexLayout* VB2ElementType18::GetLayout()
|
|||||||
{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm },
|
{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
template<typename IndexType>
|
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)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
auto model = mesh->GetModel();
|
auto model = mesh->GetModelBase();
|
||||||
CHECK_RETURN(model && model->IsVirtual(), true);
|
CHECK_RETURN(model && model->IsVirtual(), true);
|
||||||
CHECK_RETURN(triangles && vertices, 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
|
// Vertex Buffer 0 (position-only)
|
||||||
Array<VB1ElementType> vb1;
|
{
|
||||||
Array<VB2ElementType> vb2;
|
GPUVertexLayout* vb0layout = GPUVertexLayout::Get({ { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float } });
|
||||||
vb1.Resize(vertexCount);
|
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)
|
if (normals)
|
||||||
{
|
{
|
||||||
|
vb1elements.Add({ VertexElement::Types::Normal, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||||
if (tangents)
|
if (tangents)
|
||||||
{
|
vb1elements.Add({ VertexElement::Types::Tangent, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (uvs)
|
if (uvs)
|
||||||
|
vb1elements.Add({ VertexElement::Types::TexCoord, 1, 0, 0, PixelFormat::R16G16_Float });
|
||||||
|
if (vb1elements.HasItems())
|
||||||
{
|
{
|
||||||
for (uint32 i = 0; i < vertexCount; i++)
|
GPUVertexLayout* vb1layout = GPUVertexLayout::Get(vb1elements);
|
||||||
vb1.Get()[i].TexCoord = Half2(uvs[i]);
|
if (accessor.AllocateBuffer(MeshBufferType::Vertex1, vertexCount, vb1layout))
|
||||||
}
|
return true;
|
||||||
else
|
if (normals)
|
||||||
{
|
{
|
||||||
auto v = Half2::Zero;
|
auto normalStream = accessor.Normal();
|
||||||
for (uint32 i = 0; i < vertexCount; i++)
|
if (tangents)
|
||||||
vb1.Get()[i].TexCoord = v;
|
{
|
||||||
}
|
auto tangentStream = accessor.Tangent();
|
||||||
{
|
for (uint32 i = 0; i < vertexCount; i++)
|
||||||
auto v = Half2::Zero;
|
{
|
||||||
for (uint32 i = 0; i < vertexCount; i++)
|
const Float3 normal = normals[i];
|
||||||
vb1.Get()[i].LightmapUVs = v;
|
const Float3 tangent = tangents[i];
|
||||||
}
|
Float3 n;
|
||||||
if (colors)
|
Float4 t;
|
||||||
{
|
RenderTools::CalculateTangentFrame(n, t, normal, tangent);
|
||||||
vb2.Resize(vertexCount);
|
normalStream.SetFloat3(i, n);
|
||||||
for (uint32 i = 0; i < vertexCount; i++)
|
tangentStream.SetFloat4(i, t);
|
||||||
vb2.Get()[i].Color = colors[i];
|
}
|
||||||
|
}
|
||||||
|
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
|
#if !COMPILE_WITHOUT_CSHARP
|
||||||
@@ -125,21 +147,38 @@ namespace
|
|||||||
ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount);
|
ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount);
|
||||||
auto vertices = MCore::Array::GetAddress<Float3>(verticesObj);
|
auto vertices = MCore::Array::GetAddress<Float3>(verticesObj);
|
||||||
auto triangles = MCore::Array::GetAddress<IndexType>(trianglesObj);
|
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 normals = normalsObj ? MCore::Array::GetAddress<Float3>(normalsObj) : nullptr;
|
||||||
const auto tangents = tangentsObj ? MCore::Array::GetAddress<Float3>(tangentsObj) : nullptr;
|
const auto tangents = tangentsObj ? MCore::Array::GetAddress<Float3>(tangentsObj) : nullptr;
|
||||||
const auto uvs = uvObj ? MCore::Array::GetAddress<Float2>(uvObj) : nullptr;
|
const auto uvs = uvObj ? MCore::Array::GetAddress<Float2>(uvObj) : nullptr;
|
||||||
const auto colors = colorsObj ? MCore::Array::GetAddress<Color32>(colorsObj) : 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
|
#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)
|
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices)
|
||||||
{
|
{
|
||||||
Release();
|
Release();
|
||||||
|
|
||||||
// Setup GPU resources
|
// Setup GPU resources
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices);
|
const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices);
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
if (!failed)
|
if (!failed)
|
||||||
{
|
{
|
||||||
// Calculate mesh bounds
|
// 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)
|
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)
|
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)
|
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)
|
if (vb2)
|
||||||
vbData.Add(vb2);
|
vbData.Add(vb2);
|
||||||
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
|
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
vbLayout.Add(VB0ElementType::GetLayout());
|
vbLayout.Add(VB0ElementType::GetLayout());
|
||||||
vbLayout.Add(VB1ElementType::GetLayout());
|
vbLayout.Add(VB1ElementType::GetLayout());
|
||||||
vbLayout.Add(VB2ElementType::GetLayout());
|
vbLayout.Add(VB2ElementType::GetLayout());
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
|
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++)
|
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
|
||||||
vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
|
vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
|
||||||
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
|
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.Draw.IndicesCount = _triangles * 3;
|
||||||
drawCall.InstanceCount = 1;
|
drawCall.InstanceCount = 1;
|
||||||
@@ -314,7 +355,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
|
|||||||
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
|
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
|
||||||
vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
|
vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
|
||||||
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
|
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.Draw.IndicesCount = _triangles * 3;
|
||||||
drawCall.InstanceCount = 1;
|
drawCall.InstanceCount = 1;
|
||||||
@@ -364,96 +405,6 @@ void Mesh::Release()
|
|||||||
MeshBase::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
|
#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)
|
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);
|
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Deprecated in v1.10]
|
||||||
enum class InternalBufferType
|
enum class InternalBufferType
|
||||||
{
|
{
|
||||||
VB0 = 0,
|
VB0 = 0,
|
||||||
VB1 = 1,
|
VB1 = 1,
|
||||||
VB2 = 2,
|
VB2 = 2,
|
||||||
IB16 = 3,
|
|
||||||
IB32 = 4,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI)
|
MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI)
|
||||||
{
|
{
|
||||||
auto mesh = this;
|
// [Deprecated in v1.10]
|
||||||
auto type = (InternalBufferType)typeI;
|
ScopeLock lock(GetModelBase()->Locker);
|
||||||
auto model = mesh->GetModel();
|
|
||||||
ScopeLock lock(model->Locker);
|
|
||||||
|
|
||||||
// Virtual assets always fetch from GPU memory
|
// Get vertex buffers data from the mesh (CPU or GPU)
|
||||||
forceGpu |= model->IsVirtual();
|
MeshAccessor accessor;
|
||||||
|
MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 };
|
||||||
if (!mesh->IsInitialized() && forceGpu)
|
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:
|
case InternalBufferType::VB1:
|
||||||
bufferType = MeshBufferType::Vertex1;
|
bufferTypes[0] = MeshBufferType::Vertex1;
|
||||||
break;
|
break;
|
||||||
case InternalBufferType::VB2:
|
case InternalBufferType::VB2:
|
||||||
bufferType = MeshBufferType::Vertex2;
|
bufferTypes[0] = MeshBufferType::Vertex2;
|
||||||
break;
|
break;
|
||||||
case InternalBufferType::IB16:
|
}
|
||||||
case InternalBufferType::IB32:
|
if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1)))
|
||||||
bufferType = MeshBufferType::Index;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
auto positionStream = accessor.Position();
|
||||||
BytesContainer data;
|
auto texCoordStream = accessor.TexCoord();
|
||||||
int32 dataCount;
|
auto lightmapUVsStream = accessor.TexCoord(1);
|
||||||
if (forceGpu)
|
auto normalStream = accessor.Normal();
|
||||||
{
|
auto tangentStream = accessor.Tangent();
|
||||||
// Get data from GPU
|
auto colorStream = accessor.Color();
|
||||||
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
|
auto count = GetVertexCount();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert into managed array
|
// 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);
|
void* managedArrayPtr = MCore::Array::GetAddress(result);
|
||||||
const int32 elementSize = data.Length() / dataCount;
|
switch ((InternalBufferType)typeI)
|
||||||
switch (type)
|
|
||||||
{
|
{
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
case InternalBufferType::VB0:
|
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:
|
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:
|
case InternalBufferType::VB2:
|
||||||
{
|
if (colorStream.IsValid())
|
||||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case InternalBufferType::IB16:
|
|
||||||
{
|
|
||||||
if (elementSize == sizeof(uint16))
|
|
||||||
{
|
{
|
||||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
for (int32 i = 0; i < count; i++)
|
||||||
}
|
{
|
||||||
else
|
auto& dst = ((VB2ElementType*)managedArrayPtr)[i];
|
||||||
{
|
dst.Color = Color32(colorStream.GetFloat4(i));
|
||||||
auto dst = (uint16*)managedArrayPtr;
|
}
|
||||||
auto src = (uint32*)data.Get();
|
|
||||||
for (int32 i = 0; i < dataCount; i++)
|
|
||||||
dst[i] = src[i];
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ namespace FlaxEngine
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Vertex Buffer 0 structure format.
|
/// The Vertex Buffer 0 structure format.
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public struct Vertex0
|
public struct Vertex0
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -20,7 +22,9 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Vertex Buffer 1 structure format.
|
/// The Vertex Buffer 1 structure format.
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public struct Vertex1
|
public struct Vertex1
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -46,7 +50,9 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Vertex Buffer 2 structure format.
|
/// The Vertex Buffer 2 structure format.
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public struct Vertex2
|
public struct Vertex2
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,7 +63,9 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The raw Vertex Buffer structure format.
|
/// The raw Vertex Buffer structure format.
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public struct Vertex
|
public struct Vertex
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -425,6 +433,10 @@ namespace FlaxEngine
|
|||||||
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
|
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
|
internal enum InternalBufferType
|
||||||
{
|
{
|
||||||
VB0 = 0,
|
VB0 = 0,
|
||||||
@@ -436,9 +448,11 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <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"/>).
|
/// 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>
|
/// </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>
|
/// <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>
|
/// <returns>The gathered data.</returns>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false)
|
public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false)
|
||||||
{
|
{
|
||||||
var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0);
|
var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0);
|
||||||
@@ -449,9 +463,11 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <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"/>).
|
/// 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>
|
/// </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>
|
/// <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>
|
/// <returns>The gathered data.</returns>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public Vertex1[] DownloadVertexBuffer1(bool forceGpu = false)
|
public Vertex1[] DownloadVertexBuffer1(bool forceGpu = false)
|
||||||
{
|
{
|
||||||
var result = (Vertex1[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex1), (int)InternalBufferType.VB1);
|
var result = (Vertex1[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex1), (int)InternalBufferType.VB1);
|
||||||
@@ -462,12 +478,14 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <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"/>).
|
/// 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>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If mesh has no vertex colors (stored in vertex buffer 2) the returned value is null.
|
/// If mesh has no vertex colors (stored in vertex buffer 2) the returned value is null.
|
||||||
/// </remarks>
|
/// </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>
|
/// <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>
|
/// <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)
|
public Vertex2[] DownloadVertexBuffer2(bool forceGpu = false)
|
||||||
{
|
{
|
||||||
if (!HasVertexColors)
|
if (!HasVertexColors)
|
||||||
@@ -480,14 +498,13 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <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"/>).
|
/// 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>
|
/// </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>
|
/// <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>
|
/// <returns>The gathered data.</returns>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public Vertex[] DownloadVertexBuffer(bool forceGpu = false)
|
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 vb0 = DownloadVertexBuffer0(forceGpu);
|
||||||
var vb1 = DownloadVertexBuffer1(forceGpu);
|
var vb1 = DownloadVertexBuffer1(forceGpu);
|
||||||
var vb2 = DownloadVertexBuffer2(forceGpu);
|
var vb2 = DownloadVertexBuffer2(forceGpu);
|
||||||
@@ -519,33 +536,5 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
return result;
|
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>
|
/// <summary>
|
||||||
/// Lightmap texture coordinates channel index.
|
/// Lightmap texture coordinates channel index. Value -1 indicates that channel is not available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD() int32 LightmapUVsIndex = -1;
|
API_FIELD() int32 LightmapUVsIndex = -1;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
/// <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="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="vb2">The third vertex buffer data.</param>
|
||||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <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)
|
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);
|
||||||
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
/// <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="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="vb2">The third vertex buffer data.</param>
|
||||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <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)
|
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);
|
||||||
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
/// 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}"/>).
|
/// 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.
|
/// Mesh data will be cached and uploaded to the GPU with a delay.
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
/// <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="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="ib">The index buffer in clockwise order.</param>
|
||||||
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <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);
|
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -174,7 +174,6 @@ public:
|
|||||||
// [MeshBase]
|
// [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;
|
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;
|
void Release() override;
|
||||||
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Internal bindings
|
// 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.
|
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
#include "MeshBase.h"
|
#include "MeshBase.h"
|
||||||
|
#include "MeshAccessor.h"
|
||||||
#include "Engine/Core/Log.h"
|
#include "Engine/Core/Log.h"
|
||||||
#include "Engine/Content/Assets/ModelBase.h"
|
#include "Engine/Content/Assets/ModelBase.h"
|
||||||
#include "Engine/Core/Math/Transform.h"
|
#include "Engine/Core/Math/Transform.h"
|
||||||
#include "Engine/Graphics/GPUBuffer.h"
|
#include "Engine/Graphics/GPUBuffer.h"
|
||||||
#include "Engine/Graphics/GPUContext.h"
|
#include "Engine/Graphics/GPUContext.h"
|
||||||
#include "Engine/Graphics/GPUDevice.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/Renderer/DrawCall.h"
|
||||||
|
#include "Engine/Scripting/Enums.h"
|
||||||
#include "Engine/Scripting/ManagedCLR/MCore.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
|
namespace
|
||||||
{
|
{
|
||||||
@@ -28,6 +37,252 @@ namespace
|
|||||||
#endif
|
#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()
|
MeshBase::~MeshBase()
|
||||||
{
|
{
|
||||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]);
|
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(vbData.HasItems() && vertices, true);
|
||||||
CHECK_RETURN(ibData, true);
|
CHECK_RETURN(ibData, true);
|
||||||
@@ -126,9 +386,10 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
|
|||||||
_triangles = triangles;
|
_triangles = triangles;
|
||||||
_vertices = vertices;
|
_vertices = vertices;
|
||||||
_use16BitIndexBuffer = use16BitIndexBuffer;
|
_use16BitIndexBuffer = use16BitIndexBuffer;
|
||||||
_cachedVertexBuffers[0].Clear();
|
_cachedVertexBuffers[0].Release();
|
||||||
_cachedVertexBuffers[1].Clear();
|
_cachedVertexBuffers[1].Release();
|
||||||
_cachedVertexBuffers[2].Clear();
|
_cachedVertexBuffers[2].Release();
|
||||||
|
Platform::MemoryClear(_cachedVertexLayouts, sizeof(_cachedVertexLayouts));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -154,10 +415,11 @@ void MeshBase::Release()
|
|||||||
_triangles = 0;
|
_triangles = 0;
|
||||||
_vertices = 0;
|
_vertices = 0;
|
||||||
_use16BitIndexBuffer = false;
|
_use16BitIndexBuffer = false;
|
||||||
_cachedIndexBuffer.Resize(0);
|
_cachedIndexBuffer.Release();
|
||||||
_cachedVertexBuffers[0].Clear();
|
_cachedVertexBuffers[0].Release();
|
||||||
_cachedVertexBuffers[1].Clear();
|
_cachedVertexBuffers[1].Release();
|
||||||
_cachedVertexBuffers[2].Clear();
|
_cachedVertexBuffers[2].Release();
|
||||||
|
Platform::MemoryClear(_cachedVertexLayouts, sizeof(_cachedVertexLayouts));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MeshBase::UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices)
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const
|
bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout) const
|
||||||
{
|
{
|
||||||
GPUBuffer* buffer = nullptr;
|
GPUBuffer* buffer = nullptr;
|
||||||
switch (type)
|
switch (type)
|
||||||
@@ -253,18 +515,24 @@ bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result) cons
|
|||||||
break;
|
break;
|
||||||
case MeshBufferType::Vertex0:
|
case MeshBufferType::Vertex0:
|
||||||
buffer = _vertexBuffers[0];
|
buffer = _vertexBuffers[0];
|
||||||
|
if (layout && buffer)
|
||||||
|
*layout = buffer->GetVertexLayout();
|
||||||
break;
|
break;
|
||||||
case MeshBufferType::Vertex1:
|
case MeshBufferType::Vertex1:
|
||||||
buffer = _vertexBuffers[1];
|
buffer = _vertexBuffers[1];
|
||||||
|
if (layout && buffer)
|
||||||
|
*layout = buffer->GetVertexLayout();
|
||||||
break;
|
break;
|
||||||
case MeshBufferType::Vertex2:
|
case MeshBufferType::Vertex2:
|
||||||
buffer = _vertexBuffers[2];
|
buffer = _vertexBuffers[2];
|
||||||
|
if (layout && buffer)
|
||||||
|
*layout = buffer->GetVertexLayout();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return buffer && buffer->DownloadData(result);
|
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;
|
GPUBuffer* buffer = nullptr;
|
||||||
switch (type)
|
switch (type)
|
||||||
@@ -274,17 +542,163 @@ Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result
|
|||||||
break;
|
break;
|
||||||
case MeshBufferType::Vertex0:
|
case MeshBufferType::Vertex0:
|
||||||
buffer = _vertexBuffers[0];
|
buffer = _vertexBuffers[0];
|
||||||
|
if (layout && buffer)
|
||||||
|
*layout = buffer->GetVertexLayout();
|
||||||
break;
|
break;
|
||||||
case MeshBufferType::Vertex1:
|
case MeshBufferType::Vertex1:
|
||||||
buffer = _vertexBuffers[1];
|
buffer = _vertexBuffers[1];
|
||||||
|
if (layout && buffer)
|
||||||
|
*layout = buffer->GetVertexLayout();
|
||||||
break;
|
break;
|
||||||
case MeshBufferType::Vertex2:
|
case MeshBufferType::Vertex2:
|
||||||
buffer = _vertexBuffers[2];
|
buffer = _vertexBuffers[2];
|
||||||
|
if (layout && buffer)
|
||||||
|
*layout = buffer->GetVertexLayout();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return buffer ? buffer->DownloadDataAsync(result) : nullptr;
|
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
|
void MeshBase::GetDrawCallGeometry(DrawCall& drawCall) const
|
||||||
{
|
{
|
||||||
drawCall.Geometry.IndexBuffer = _indexBuffer;
|
drawCall.Geometry.IndexBuffer = _indexBuffer;
|
||||||
@@ -324,4 +738,85 @@ bool MeshBase::UpdateTrianglesUShort(int32 triangleCount, const MArray* triangle
|
|||||||
return ::UpdateTriangles<uint16>(this, triangleCount, trianglesObj);
|
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
|
#endif
|
||||||
|
|||||||
@@ -92,5 +92,84 @@ namespace FlaxEngine
|
|||||||
if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles)))
|
if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles)))
|
||||||
throw new Exception("Failed to update mesh data.");
|
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/BoundingBox.h"
|
||||||
#include "Engine/Core/Math/BoundingSphere.h"
|
#include "Engine/Core/Math/BoundingSphere.h"
|
||||||
#include "Engine/Core/Types/DataContainer.h"
|
#include "Engine/Core/Types/DataContainer.h"
|
||||||
#include "Engine/Core/Collections/Array.h"
|
|
||||||
#include "Engine/Graphics/Enums.h"
|
#include "Engine/Graphics/Enums.h"
|
||||||
#include "Engine/Graphics/Models/Types.h"
|
#include "Engine/Graphics/Models/Types.h"
|
||||||
#include "Engine/Level/Types.h"
|
#include "Engine/Level/Types.h"
|
||||||
@@ -47,12 +46,13 @@ protected:
|
|||||||
bool _use16BitIndexBuffer = false;
|
bool _use16BitIndexBuffer = false;
|
||||||
bool _hasBounds = false;
|
bool _hasBounds = false;
|
||||||
|
|
||||||
GPUBuffer* _vertexBuffers[3] = {};
|
GPUBuffer* _vertexBuffers[MODEL_MAX_VB] = {};
|
||||||
GPUBuffer* _indexBuffer = nullptr;
|
GPUBuffer* _indexBuffer = nullptr;
|
||||||
|
|
||||||
mutable Array<byte> _cachedVertexBuffers[3];
|
mutable BytesContainer _cachedVertexBuffers[MODEL_MAX_VB];
|
||||||
mutable Array<byte> _cachedIndexBuffer;
|
mutable GPUVertexLayout* _cachedVertexLayouts[MODEL_MAX_VB] = {};
|
||||||
mutable int32 _cachedIndexBufferCount = 0;
|
mutable BytesContainer _cachedIndexBuffer;
|
||||||
|
mutable int32 _cachedIndexBufferCount = 0, _cachedVertexBufferCount = 0;
|
||||||
|
|
||||||
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
||||||
CollisionProxy _collisionProxy;
|
CollisionProxy _collisionProxy;
|
||||||
@@ -177,19 +177,19 @@ public:
|
|||||||
/// Sets the mesh bounds.
|
/// Sets the mesh bounds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="box">The bounding box.</param>
|
/// <param name="box">The bounding box.</param>
|
||||||
void SetBounds(const BoundingBox& box);
|
API_FUNCTION() void SetBounds(API_PARAM(ref) const BoundingBox& box);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the mesh bounds.
|
/// Sets the mesh bounds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="box">The bounding box.</param>
|
/// <param name="box">The bounding box.</param>
|
||||||
/// <param name="sphere">The bounding sphere.</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>
|
/// <summary>
|
||||||
/// Gets the index buffer.
|
/// Gets the index buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE GPUBuffer* GetIndexBuffer() const
|
API_FUNCTION() FORCE_INLINE GPUBuffer* GetIndexBuffer() const
|
||||||
{
|
{
|
||||||
return _indexBuffer;
|
return _indexBuffer;
|
||||||
}
|
}
|
||||||
@@ -199,23 +199,29 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">The bind slot index.</param>
|
/// <param name="index">The bind slot index.</param>
|
||||||
/// <returns>The buffer or null if not used.</returns>
|
/// <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];
|
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:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the mesh buffers.
|
/// Initializes the mesh buffers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertices">Amount of vertices in the vertex buffer.</param>
|
/// <param name="vertices">Amount of vertices in the vertex buffer.</param>
|
||||||
/// <param name="triangles">Amount of triangles in the index 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="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="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>
|
/// <param name="vbLayout">Layout descriptors for the vertex buffers attributes (one for each vertex buffer).</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <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>
|
/// <summary>
|
||||||
/// Releases the mesh data (GPU buffers and local cache).
|
/// Releases the mesh data (GPU buffers and local cache).
|
||||||
@@ -290,16 +296,18 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">Buffer type</param>
|
/// <param name="type">Buffer type</param>
|
||||||
/// <param name="result">The result data</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>
|
/// <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>
|
/// <summary>
|
||||||
/// Extracts mesh buffer data from GPU in the async task.
|
/// Extracts mesh buffer data from GPU in the async task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">Buffer type</param>
|
/// <param name="type">Buffer type</param>
|
||||||
/// <param name="result">The result data</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>
|
/// <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>
|
/// <summary>
|
||||||
/// Extract mesh buffer data from CPU. Cached internally.
|
/// Extract mesh buffer data from CPU. Cached internally.
|
||||||
@@ -307,8 +315,19 @@ public:
|
|||||||
/// <param name="type">Buffer type</param>
|
/// <param name="type">Buffer type</param>
|
||||||
/// <param name="result">The result data</param>
|
/// <param name="result">The result data</param>
|
||||||
/// <param name="count">The amount of items inside the result buffer.</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>
|
/// <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:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -423,5 +442,7 @@ private:
|
|||||||
#if !COMPILE_WITHOUT_CSHARP
|
#if !COMPILE_WITHOUT_CSHARP
|
||||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj);
|
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj);
|
||||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(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
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
#include "MeshDeformation.h"
|
#include "MeshDeformation.h"
|
||||||
|
#include "MeshAccessor.h"
|
||||||
#include "Engine/Graphics/Models/MeshBase.h"
|
#include "Engine/Graphics/Models/MeshBase.h"
|
||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
#include "Engine/Profiler/ProfilerCPU.h"
|
||||||
|
|
||||||
@@ -29,6 +30,11 @@ FORCE_INLINE static uint32 GetKey(int32 lodIndex, int32 meshIndex, MeshBufferTyp
|
|||||||
return key.Value;
|
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
|
void MeshDeformation::GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const
|
||||||
{
|
{
|
||||||
const auto key = GetKey(lodIndex, meshIndex, MeshBufferType::Vertex0);
|
const auto key = GetKey(lodIndex, meshIndex, MeshBufferType::Vertex0);
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ struct MeshDeformationData
|
|||||||
~MeshDeformationData()
|
~MeshDeformationData()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LoadMeshAccessor(class MeshAccessor& accessor) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
#include "Engine/Core/Math/BoundingSphere.h"
|
#include "Engine/Core/Math/BoundingSphere.h"
|
||||||
#include "Engine/Animations/CurveSerialization.h"
|
#include "Engine/Animations/CurveSerialization.h"
|
||||||
#include "Engine/Serialization/WriteStream.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()
|
void MeshData::Clear()
|
||||||
{
|
{
|
||||||
@@ -71,6 +68,7 @@ void MeshData::Release()
|
|||||||
BlendShapes.Resize(0);
|
BlendShapes.Resize(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount)
|
void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount)
|
||||||
{
|
{
|
||||||
Positions.Resize(verticesCount, false);
|
Positions.Resize(verticesCount, false);
|
||||||
@@ -160,6 +158,7 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb
|
|||||||
vb1++;
|
vb1++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
|
|
||||||
void MeshData::SetIndexBuffer(void* data, uint32 indicesCount)
|
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
|
void MeshData::CalculateBox(BoundingBox& result) const
|
||||||
{
|
{
|
||||||
if (Positions.HasItems())
|
if (Positions.HasItems())
|
||||||
BoundingBox::FromPoints(Positions.Get(), Positions.Count(), result);
|
BoundingBox::FromPoints(Positions.Get(), Positions.Count(), result);
|
||||||
|
else
|
||||||
|
result = BoundingBox::Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshData::CalculateSphere(BoundingSphere& result) const
|
void MeshData::CalculateSphere(BoundingSphere& result) const
|
||||||
{
|
{
|
||||||
if (Positions.HasItems())
|
if (Positions.HasItems())
|
||||||
BoundingSphere::FromPoints(Positions.Get(), Positions.Count(), result);
|
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)
|
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;
|
Array<Float3> Positions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Texture coordinates
|
/// Texture coordinates (list of channels)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
// TODO: multiple UVs
|
||||||
Array<Float2> UVs;
|
Array<Float2> UVs;
|
||||||
|
Array<Float2> LightmapUVs; // TODO: remove this and move to UVs
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normals vector
|
/// Normals vector
|
||||||
@@ -65,11 +67,6 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Array<uint32> Indices;
|
Array<uint32> Indices;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lightmap UVs
|
|
||||||
/// </summary>
|
|
||||||
Array<Float2> LightmapUVs;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Vertex colors
|
/// Vertex colors
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -91,12 +88,17 @@ public:
|
|||||||
Array<BlendShape> BlendShapes;
|
Array<BlendShape> BlendShapes;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
Vector3 OriginTranslation = Vector3::Zero;
|
Vector3 OriginTranslation = Vector3::Zero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Orientation for this mesh at it's local origin.
|
/// Orientation for this mesh at its local origin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Quaternion OriginOrientation = Quaternion::Identity;
|
Quaternion OriginOrientation = Quaternion::Identity;
|
||||||
|
|
||||||
@@ -105,15 +107,6 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Vector3 Scaling = Vector3::One;
|
Vector3 Scaling = Vector3::One;
|
||||||
|
|
||||||
public:
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether this instance has any mesh data.
|
|
||||||
/// </summary>
|
|
||||||
FORCE_INLINE bool HasData() const
|
|
||||||
{
|
|
||||||
return Indices.HasItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clear arrays
|
/// Clear arrays
|
||||||
@@ -142,6 +135,7 @@ public:
|
|||||||
void Release();
|
void Release();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Init from model vertices array
|
/// Init from model vertices array
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -165,6 +159,7 @@ public:
|
|||||||
/// <param name="vb2">Array of data for vertex buffer 2</param>
|
/// <param name="vb2">Array of data for vertex buffer 2</param>
|
||||||
/// <param name="verticesCount">Amount of vertices</param>
|
/// <param name="verticesCount">Amount of vertices</param>
|
||||||
void InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount);
|
void InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount);
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the index buffer data.
|
/// Sets the index buffer data.
|
||||||
@@ -174,20 +169,6 @@ public:
|
|||||||
void SetIndexBuffer(void* data, uint32 indicesCount);
|
void SetIndexBuffer(void* data, uint32 indicesCount);
|
||||||
|
|
||||||
public:
|
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>
|
/// <summary>
|
||||||
/// Calculate bounding box for the mesh
|
/// Calculate bounding box for the mesh
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -200,9 +181,14 @@ public:
|
|||||||
/// <param name="result">Output sphere</param>
|
/// <param name="result">Output sphere</param>
|
||||||
void CalculateSphere(BoundingSphere& result) const;
|
void CalculateSphere(BoundingSphere& result) const;
|
||||||
|
|
||||||
public:
|
/// <summary>
|
||||||
#if COMPILE_WITH_MODEL_TOOL
|
/// 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>
|
/// <summary>
|
||||||
/// Generate lightmap uvs for the mesh entry
|
/// Generate lightmap uvs for the mesh entry
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -246,7 +232,6 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The area sum of all mesh triangles.</returns>
|
/// <returns>The area sum of all mesh triangles.</returns>
|
||||||
float CalculateTrianglesArea() const;
|
float CalculateTrianglesArea() const;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -439,23 +424,6 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Array<AnimationData> Animations;
|
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:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Automatically calculates the screen size for every model LOD for a proper transitions.
|
/// Automatically calculates the screen size for every model LOD for a proper transitions.
|
||||||
@@ -467,29 +435,4 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="matrix">The matrix to use for the transformation.</param>
|
/// <param name="matrix">The matrix to use for the transformation.</param>
|
||||||
void TransformBuffer(const Matrix& matrix);
|
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.
|
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
#include "SkinnedMesh.h"
|
#include "SkinnedMesh.h"
|
||||||
|
#include "MeshAccessor.h"
|
||||||
#include "MeshDeformation.h"
|
#include "MeshDeformation.h"
|
||||||
#include "ModelInstanceEntry.h"
|
#include "ModelInstanceEntry.h"
|
||||||
#include "Engine/Content/Assets/Material.h"
|
#include "Engine/Content/Assets/Material.h"
|
||||||
@@ -13,12 +14,11 @@
|
|||||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||||
#include "Engine/Level/Scene/Scene.h"
|
#include "Engine/Level/Scene/Scene.h"
|
||||||
#include "Engine/Renderer/RenderList.h"
|
#include "Engine/Renderer/RenderList.h"
|
||||||
#include "Engine/Serialization/MemoryReadStream.h"
|
|
||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
|
||||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||||
#include "Engine/Threading/Task.h"
|
|
||||||
#include "Engine/Threading/Threading.h"
|
#include "Engine/Threading/Threading.h"
|
||||||
|
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
|
|
||||||
GPUVertexLayout* VB0SkinnedElementType2::GetLayout()
|
GPUVertexLayout* VB0SkinnedElementType2::GetLayout()
|
||||||
{
|
{
|
||||||
return GPUVertexLayout::Get({
|
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)
|
void SkeletonData::Swap(SkeletonData& other)
|
||||||
{
|
{
|
||||||
Nodes.Swap(other.Nodes);
|
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;
|
Array<const void*, FixedAllocation<3>> vbData;
|
||||||
vbData.Add(vb0);
|
vbData.Add(vb0);
|
||||||
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
|
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
vbLayout.Add(VB0SkinnedElementType::GetLayout());
|
vbLayout.Add(VB0SkinnedElementType::GetLayout());
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
|
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)
|
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices)
|
||||||
{
|
{
|
||||||
// Setup GPU resources
|
// Setup GPU resources
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices);
|
const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices);
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
if (!failed)
|
if (!failed)
|
||||||
{
|
{
|
||||||
// Calculate mesh bounds
|
// Calculate mesh bounds
|
||||||
@@ -117,6 +261,16 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0
|
|||||||
return failed;
|
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
|
void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const
|
||||||
{
|
{
|
||||||
const auto& entry = info.Buffer->At(_materialSlotIndex);
|
const auto& entry = info.Buffer->At(_materialSlotIndex);
|
||||||
@@ -212,297 +366,70 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI
|
|||||||
void SkinnedMesh::Release()
|
void SkinnedMesh::Release()
|
||||||
{
|
{
|
||||||
MeshBase::Release();
|
MeshBase::Release();
|
||||||
|
|
||||||
BlendShapes.Clear();
|
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
|
#if !COMPILE_WITHOUT_CSHARP
|
||||||
|
|
||||||
template<typename IndexType>
|
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)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
auto model = mesh->GetSkinnedModel();
|
return ::UpdateMesh<uint32>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Deprecated in v1.10]
|
||||||
enum class InternalBufferType
|
enum class InternalBufferType
|
||||||
{
|
{
|
||||||
VB0 = 0,
|
VB0 = 0,
|
||||||
IB16 = 3,
|
|
||||||
IB32 = 4,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MArray* SkinnedMesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI)
|
MArray* SkinnedMesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI)
|
||||||
{
|
{
|
||||||
SkinnedMesh* mesh = this;
|
// [Deprecated in v1.10]
|
||||||
InternalBufferType type = (InternalBufferType)typeI;
|
ScopeLock lock(GetModelBase()->Locker);
|
||||||
auto model = mesh->GetSkinnedModel();
|
|
||||||
ScopeLock lock(model->Locker);
|
|
||||||
|
|
||||||
// Virtual assets always fetch from GPU memory
|
// Get vertex buffers data from the mesh (CPU or GPU)
|
||||||
forceGpu |= model->IsVirtual();
|
MeshAccessor accessor;
|
||||||
|
MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 };
|
||||||
if (!mesh->IsInitialized() && forceGpu)
|
if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1)))
|
||||||
{
|
|
||||||
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
auto positionStream = accessor.Position();
|
||||||
|
auto texCoordStream = accessor.TexCoord();
|
||||||
MeshBufferType bufferType;
|
auto normalStream = accessor.Normal();
|
||||||
switch (type)
|
auto tangentStream = accessor.Tangent();
|
||||||
{
|
auto blendIndicesStream = accessor.BlendIndices();
|
||||||
case InternalBufferType::VB0:
|
auto BlendWeightsStream = accessor.BlendWeights();
|
||||||
bufferType = MeshBufferType::Vertex0;
|
auto count = GetVertexCount();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert into managed array
|
// 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);
|
void* managedArrayPtr = MCore::Array::GetAddress(result);
|
||||||
const int32 elementSize = data.Length() / dataCount;
|
switch ((InternalBufferType)typeI)
|
||||||
switch (type)
|
|
||||||
{
|
{
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
case InternalBufferType::VB0:
|
case InternalBufferType::VB0:
|
||||||
{
|
for (int32 i = 0; i < count; i++)
|
||||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case InternalBufferType::IB16:
|
|
||||||
{
|
|
||||||
if (elementSize == sizeof(uint16))
|
|
||||||
{
|
{
|
||||||
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
|
auto& dst = ((VB0SkinnedElementType*)managedArrayPtr)[i];
|
||||||
}
|
dst.Position = positionStream.GetFloat3(i);
|
||||||
else
|
if (texCoordStream.IsValid())
|
||||||
{
|
dst.TexCoord = texCoordStream.GetFloat2(i);
|
||||||
auto dst = (uint16*)managedArrayPtr;
|
if (normalStream.IsValid())
|
||||||
auto src = (uint32*)data.Get();
|
dst.Normal = normalStream.GetFloat3(i);
|
||||||
for (int32 i = 0; i < dataCount; i++)
|
if (tangentStream.IsValid())
|
||||||
dst[i] = src[i];
|
dst.Tangent = tangentStream.GetFloat4(i);
|
||||||
|
if (blendIndicesStream.IsValid())
|
||||||
|
dst.BlendIndices = Color32(blendIndicesStream.GetFloat4(i));
|
||||||
|
if (BlendWeightsStream.IsValid())
|
||||||
|
dst.BlendWeights = Half4(BlendWeightsStream.GetFloat4(i));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ namespace FlaxEngine
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Vertex Buffer 0 structure format.
|
/// The Vertex Buffer 0 structure format.
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public struct Vertex0
|
public struct Vertex0
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,7 +46,9 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The raw Vertex Buffer structure format.
|
/// The raw Vertex Buffer structure format.
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public struct Vertex
|
public struct Vertex
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,7 +104,8 @@ namespace FlaxEngine
|
|||||||
/// <param name="normals">The normal vectors (per vertex).</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="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||||
/// <param name="uv">The texture coordinates (per vertex).</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)
|
if (!ParentSkinnedModel.IsVirtual)
|
||||||
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
|
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.");
|
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||||
if (uv != null && uv.Length != vertices.Length)
|
if (uv != null && uv.Length != vertices.Length)
|
||||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
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.");
|
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="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="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||||
/// <param name="uv">The texture coordinates (per vertex).</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)
|
if (!ParentSkinnedModel.IsVirtual)
|
||||||
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
|
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.");
|
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||||
if (uv != null && uv.Length != vertices.Length)
|
if (uv != null && uv.Length != vertices.Length)
|
||||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
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.");
|
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="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="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||||
/// <param name="uv">The texture coordinates (per vertex).</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)
|
if (!ParentSkinnedModel.IsVirtual)
|
||||||
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
|
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.");
|
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
|
||||||
if (uv != null && uv.Length != vertices.Length)
|
if (uv != null && uv.Length != vertices.Length)
|
||||||
throw new ArgumentOutOfRangeException(nameof(uv));
|
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.");
|
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));
|
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
|
internal enum InternalBufferType
|
||||||
{
|
{
|
||||||
VB0 = 0,
|
VB0 = 0,
|
||||||
IB16 = 3,
|
|
||||||
IB32 = 4,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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"/>).
|
/// 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>
|
/// </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>
|
/// <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>
|
/// <returns>The gathered data.</returns>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false)
|
public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false)
|
||||||
{
|
{
|
||||||
var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0);
|
var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0);
|
||||||
@@ -272,13 +289,13 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
/// <summary>
|
/// <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"/>).
|
/// 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>
|
/// </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>
|
/// <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>
|
/// <returns>The gathered data.</returns>
|
||||||
|
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
|
||||||
public Vertex[] DownloadVertexBuffer(bool forceGpu = false)
|
public Vertex[] DownloadVertexBuffer(bool forceGpu = false)
|
||||||
{
|
{
|
||||||
// TODO: perform data conversion on C++ side to make it faster
|
|
||||||
|
|
||||||
var vb0 = DownloadVertexBuffer0(forceGpu);
|
var vb0 = DownloadVertexBuffer0(forceGpu);
|
||||||
|
|
||||||
var vertices = vb0.Length;
|
var vertices = vb0.Length;
|
||||||
@@ -299,33 +316,5 @@ namespace FlaxEngine
|
|||||||
|
|
||||||
return result;
|
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:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
/// <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="triangleCount">The amount of triangles in the index buffer.</param>
|
||||||
/// <param name="vb">The vertex buffer data.</param>
|
/// <param name="vb">The vertex buffer data.</param>
|
||||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <returns>True if failed, otherwise false.</returns>
|
||||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib)
|
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||||
{
|
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib);
|
||||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
/// <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="triangleCount">The amount of triangles in the index buffer.</param>
|
||||||
/// <param name="vb">The vertex buffer data.</param>
|
/// <param name="vb">The vertex buffer data.</param>
|
||||||
/// <param name="ib">The index buffer in clockwise order.</param>
|
/// <param name="ib">The index buffer in clockwise order.</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <returns>True if failed, otherwise false.</returns>
|
||||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib)
|
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||||
{
|
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib);
|
||||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
/// <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="triangleCount">The amount of triangles in the index buffer.</param>
|
||||||
/// <param name="vb">The vertex buffer data.</param>
|
/// <param name="vb">The vertex buffer data.</param>
|
||||||
/// <param name="ib">The index buffer, clockwise order.</param>
|
/// <param name="ib">The index buffer, clockwise order.</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <returns>True if failed, otherwise false.</returns>
|
||||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib)
|
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
|
||||||
{
|
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib);
|
||||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
/// <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="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="ib">The index buffer in clockwise order.</param>
|
||||||
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <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);
|
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:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draws the mesh.
|
/// Draws the mesh.
|
||||||
@@ -119,13 +154,12 @@ public:
|
|||||||
public:
|
public:
|
||||||
// [MeshBase]
|
// [MeshBase]
|
||||||
void Release() override;
|
void Release() override;
|
||||||
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Internal bindings
|
// Internal bindings
|
||||||
#if !COMPILE_WITHOUT_CSHARP
|
#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 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(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(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);
|
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ API_ENUM(Attributes="HideInEditor") enum class ModelLightmapUVsSource
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mesh buffer types.
|
/// The mesh buffer types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
enum class MeshBufferType
|
API_ENUM(Attributes="HideInEditor") enum class MeshBufferType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The index buffer.
|
/// The index buffer.
|
||||||
@@ -73,7 +73,7 @@ enum class MeshBufferType
|
|||||||
|
|
||||||
// Vertex structure for all models (versioned)
|
// Vertex structure for all models (versioned)
|
||||||
// [Deprecated in v1.10]
|
// [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;
|
Float3 Position;
|
||||||
Half2 TexCoord;
|
Half2 TexCoord;
|
||||||
@@ -83,11 +83,13 @@ PACK_STRUCT(struct ModelVertex19
|
|||||||
Color32 Color;
|
Color32 Color;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
// [Deprecated in v1.10]
|
// [Deprecated in v1.10]
|
||||||
typedef ModelVertex19 ModelVertex;
|
typedef ModelVertex19 ModelVertex;
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
|
|
||||||
// [Deprecated in v1.10]
|
// [Deprecated in v1.10]
|
||||||
struct RawModelVertex
|
struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") RawModelVertex
|
||||||
{
|
{
|
||||||
Float3 Position;
|
Float3 Position;
|
||||||
Float2 TexCoord;
|
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
|
// For vertex data we use three buffers: one with positions, one with other attributes, and one with colors
|
||||||
// [Deprecated in v1.10]
|
// [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;
|
Float3 Position;
|
||||||
|
|
||||||
@@ -108,7 +110,7 @@ PACK_STRUCT(struct VB0ElementType18
|
|||||||
});
|
});
|
||||||
|
|
||||||
// [Deprecated in v1.10]
|
// [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;
|
Half2 TexCoord;
|
||||||
Float1010102 Normal;
|
Float1010102 Normal;
|
||||||
@@ -119,23 +121,25 @@ PACK_STRUCT(struct VB1ElementType18
|
|||||||
});
|
});
|
||||||
|
|
||||||
// [Deprecated in v1.10]
|
// [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;
|
Color32 Color;
|
||||||
|
|
||||||
static GPUVertexLayout* GetLayout();
|
static GPUVertexLayout* GetLayout();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
// [Deprecated in v1.10]
|
// [Deprecated in v1.10]
|
||||||
typedef VB0ElementType18 VB0ElementType;
|
typedef VB0ElementType18 VB0ElementType;
|
||||||
// [Deprecated in v1.10]
|
// [Deprecated in v1.10]
|
||||||
typedef VB1ElementType18 VB1ElementType;
|
typedef VB1ElementType18 VB1ElementType;
|
||||||
// [Deprecated in v1.10]
|
// [Deprecated in v1.10]
|
||||||
typedef VB2ElementType18 VB2ElementType;
|
typedef VB2ElementType18 VB2ElementType;
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
|
|
||||||
// Vertex structure for all skinned models (versioned)
|
// Vertex structure for all skinned models (versioned)
|
||||||
// [Deprecated in v1.10]
|
// [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;
|
Float3 Position;
|
||||||
Half2 TexCoord;
|
Half2 TexCoord;
|
||||||
@@ -145,11 +149,13 @@ PACK_STRUCT(struct SkinnedModelVertex1
|
|||||||
Color32 BlendWeights;
|
Color32 BlendWeights;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
// [Deprecated in v1.10]
|
// [Deprecated in v1.10]
|
||||||
typedef SkinnedModelVertex1 SkinnedModelVertex;
|
typedef SkinnedModelVertex1 SkinnedModelVertex;
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
|
|
||||||
// [Deprecated in v1.10]
|
// [Deprecated in v1.10]
|
||||||
struct RawSkinnedModelVertex
|
struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") RawSkinnedModelVertex
|
||||||
{
|
{
|
||||||
Float3 Position;
|
Float3 Position;
|
||||||
Float2 TexCoord;
|
Float2 TexCoord;
|
||||||
@@ -161,7 +167,7 @@ struct RawSkinnedModelVertex
|
|||||||
};
|
};
|
||||||
|
|
||||||
// [Deprecated on 28.04.2023, expires on 01.01.2024]
|
// [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||||
PACK_STRUCT(struct VB0SkinnedElementType1
|
PACK_STRUCT(struct DEPRECATED("Use newer format.") VB0SkinnedElementType1
|
||||||
{
|
{
|
||||||
Float3 Position;
|
Float3 Position;
|
||||||
Half2 TexCoord;
|
Half2 TexCoord;
|
||||||
@@ -172,7 +178,7 @@ PACK_STRUCT(struct VB0SkinnedElementType1
|
|||||||
});
|
});
|
||||||
|
|
||||||
// [Deprecated in v1.10]
|
// [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;
|
Float3 Position;
|
||||||
Half2 TexCoord;
|
Half2 TexCoord;
|
||||||
@@ -184,5 +190,7 @@ PACK_STRUCT(struct VB0SkinnedElementType2
|
|||||||
static GPUVertexLayout* GetLayout();
|
static GPUVertexLayout* GetLayout();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
// [Deprecated in v1.10]
|
// [Deprecated in v1.10]
|
||||||
typedef VB0SkinnedElementType2 VB0SkinnedElementType;
|
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))
|
if (const PixelFormatSampler* sampler = PixelFormatSampler::Get(format))
|
||||||
{
|
{
|
||||||
pixelSize = sampler->PixelSize;
|
pixelSize = sampler->PixelSize;
|
||||||
*read = sampler->Read;
|
*read = (void*)sampler->Read;
|
||||||
*write = sampler->Write;
|
*write = (void*)sampler->Write;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -557,6 +557,26 @@ float RenderTools::ComputeTemporalTime()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal)
|
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
|
// Calculate tangent
|
||||||
const Float3 c1 = Float3::Cross(normal, Float3::UnitZ);
|
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);
|
const byte sign = static_cast<byte>(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||||
|
|
||||||
// Set tangent frame
|
// Set tangent frame
|
||||||
resultNormal = Float1010102(normal * 0.5f + 0.5f, 0);
|
resultNormal = normal * 0.5f + 0.5f;
|
||||||
resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
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
|
// Calculate bitangent sign
|
||||||
const Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent));
|
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);
|
const byte sign = static_cast<byte>(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||||
|
|
||||||
// Set tangent frame
|
// Set tangent frame
|
||||||
resultNormal = Float1010102(normal * 0.5f + 0.5f, 0);
|
resultNormal = normal * 0.5f + 0.5f;
|
||||||
resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
resultTangent = Float4(tangent * 0.5f + 0.5f, sign);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderTools::ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside)
|
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.
|
// 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 float ComputeTemporalTime();
|
||||||
|
|
||||||
static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal);
|
// [Deprecated in v1.10]
|
||||||
static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent);
|
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);
|
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.
|
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
#include "GPUVertexLayout.h"
|
#include "GPUVertexLayout.h"
|
||||||
#if GPU_ENABLE_ASSERTION_LOW_LAYERS
|
|
||||||
#include "Engine/Core/Log.h"
|
#include "Engine/Core/Log.h"
|
||||||
#endif
|
|
||||||
#include "Engine/Core/Collections/Dictionary.h"
|
#include "Engine/Core/Collections/Dictionary.h"
|
||||||
#include "Engine/Core/Math/Math.h"
|
#include "Engine/Core/Math/Math.h"
|
||||||
#include "Engine/Core/Types/Span.h"
|
#include "Engine/Core/Types/Span.h"
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ GPUShaderProgramVSDX11::~GPUShaderProgramVSDX11()
|
|||||||
ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* vertexLayout)
|
ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* vertexLayout)
|
||||||
{
|
{
|
||||||
ID3D11InputLayout* inputLayout = nullptr;
|
ID3D11InputLayout* inputLayout = nullptr;
|
||||||
|
if (!vertexLayout)
|
||||||
|
vertexLayout = (GPUVertexLayoutDX11*)Layout;
|
||||||
if (!_cache.TryGet(vertexLayout, inputLayout))
|
if (!_cache.TryGet(vertexLayout, inputLayout))
|
||||||
{
|
{
|
||||||
if (!vertexLayout)
|
|
||||||
vertexLayout = (GPUVertexLayoutDX11*)Layout;
|
|
||||||
if (vertexLayout && vertexLayout->InputElementsCount)
|
if (vertexLayout && vertexLayout->InputElementsCount)
|
||||||
{
|
{
|
||||||
auto mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout);
|
auto mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "Engine/Graphics/GPUContext.h"
|
#include "Engine/Graphics/GPUContext.h"
|
||||||
#include "Engine/Graphics/GPUDevice.h"
|
#include "Engine/Graphics/GPUDevice.h"
|
||||||
#include "Engine/Graphics/RenderTask.h"
|
#include "Engine/Graphics/RenderTask.h"
|
||||||
|
#include "Engine/Graphics/Models/MeshAccessor.h"
|
||||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||||
#include "Engine/Level/Scene/Scene.h"
|
#include "Engine/Level/Scene/Scene.h"
|
||||||
#include "Engine/Level/SceneObjectsFactory.h"
|
#include "Engine/Level/SceneObjectsFactory.h"
|
||||||
@@ -628,30 +629,34 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
|
|||||||
|
|
||||||
// Blend all blend shapes
|
// Blend all blend shapes
|
||||||
auto vertexCount = (uint32)mesh->GetVertexCount();
|
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)
|
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)
|
if (useNormals)
|
||||||
{
|
{
|
||||||
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
|
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
|
||||||
{
|
{
|
||||||
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
|
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
|
||||||
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
|
|
||||||
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
|
Float3 normal = normalStream.GetFloat3(blendShapeVertex.VertexIndex) * 2.0f - 1.0f;
|
||||||
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
|
normal = normal + blendShapeVertex.NormalDelta * q.Second;
|
||||||
Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta * q.Second;
|
normal = normal * 0.5f + 0.5f; // TODO: optimize unpacking and packing to just apply it to the normal delta
|
||||||
vertex.Normal = normal * 0.5f + 0.5f;
|
normalStream.SetFloat3(blendShapeVertex.VertexIndex, normal);
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -659,21 +664,23 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
|
|||||||
if (useNormals)
|
if (useNormals)
|
||||||
{
|
{
|
||||||
// Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range)
|
// 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++)
|
for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++)
|
||||||
{
|
{
|
||||||
VB0SkinnedElementType& vertex = *(data + vertexIndex);
|
Float3 normal = normalStream.GetFloat3(vertexIndex) * 2.0f - 1.0f;
|
||||||
|
|
||||||
Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f;
|
|
||||||
normal.Normalize();
|
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;
|
if (tangentStream.IsValid())
|
||||||
tangent = tangent - ((tangent | normal) * normal);
|
{
|
||||||
tangent.Normalize();
|
Float4 tangentRaw = normalStream.GetFloat4(vertexIndex);
|
||||||
const auto tangentSign = vertex.Tangent.W;
|
Float3 tangent = Float3(tangentRaw) * 2.0f - 1.0f;
|
||||||
vertex.Tangent = tangent * 0.5f + 0.5f;
|
tangent = tangent - ((tangent | normal) * normal);
|
||||||
vertex.Tangent.W = tangentSign;
|
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;
|
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;
|
count = 0;
|
||||||
if (mesh.LODIndex < 0 || mesh.MeshIndex < 0)
|
if (ref.LODIndex < 0 || ref.MeshIndex < 0)
|
||||||
return true;
|
return true;
|
||||||
const auto model = SkinnedModel.Get();
|
const auto model = SkinnedModel.Get();
|
||||||
if (!model || model->WaitForLoaded())
|
if (!model || model->WaitForLoaded())
|
||||||
return true;
|
return true;
|
||||||
auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)];
|
auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)];
|
||||||
return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count);
|
auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)];
|
||||||
|
return mesh.DownloadDataCPU(type, result, count, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
MeshDeformation* AnimatedModel::GetMeshDeformation() const
|
MeshDeformation* AnimatedModel::GetMeshDeformation() const
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ public:
|
|||||||
MaterialBase* GetMaterial(int32 entryIndex) override;
|
MaterialBase* GetMaterial(int32 entryIndex) override;
|
||||||
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) 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 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;
|
void UpdateBounds() override;
|
||||||
MeshDeformation* GetMeshDeformation() const override;
|
MeshDeformation* GetMeshDeformation() const override;
|
||||||
void OnDeleteObject() override;
|
void OnDeleteObject() override;
|
||||||
|
|||||||
@@ -112,12 +112,13 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel).
|
/// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mesh">Mesh reference.</param>
|
/// <param name="ref">Mesh reference.</param>
|
||||||
/// <param name="type">Buffer type</param>
|
/// <param name="type">Buffer type</param>
|
||||||
/// <param name="result">The result data</param>
|
/// <param name="result">The result data</param>
|
||||||
/// <param name="count">The amount of items inside the result buffer.</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>
|
/// <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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "Engine/Graphics/GPUDevice.h"
|
#include "Engine/Graphics/GPUDevice.h"
|
||||||
#include "Engine/Graphics/RenderTask.h"
|
#include "Engine/Graphics/RenderTask.h"
|
||||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||||
|
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||||
#include "Engine/Serialization/Serialization.h"
|
#include "Engine/Serialization/Serialization.h"
|
||||||
#include "Engine/Level/Prefabs/PrefabManager.h"
|
#include "Engine/Level/Prefabs/PrefabManager.h"
|
||||||
#include "Engine/Level/Scene/Scene.h"
|
#include "Engine/Level/Scene/Scene.h"
|
||||||
@@ -300,7 +301,8 @@ void StaticModel::FlushVertexColors()
|
|||||||
vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors"));
|
vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors"));
|
||||||
if (vertexColorsBuffer->GetSize() != size)
|
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;
|
break;
|
||||||
}
|
}
|
||||||
GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size);
|
GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size);
|
||||||
@@ -627,16 +629,17 @@ bool StaticModel::IntersectsEntry(const Ray& ray, Real& distance, Vector3& norma
|
|||||||
return result;
|
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;
|
count = 0;
|
||||||
if (mesh.LODIndex < 0 || mesh.MeshIndex < 0)
|
if (ref.LODIndex < 0 || ref.MeshIndex < 0)
|
||||||
return true;
|
return true;
|
||||||
const auto model = Model.Get();
|
const auto model = Model.Get();
|
||||||
if (!model || model->WaitForLoaded())
|
if (!model || model->WaitForLoaded())
|
||||||
return true;
|
return true;
|
||||||
auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)];
|
auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)];
|
||||||
return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count);
|
auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)];
|
||||||
|
return mesh.DownloadDataCPU(type, result, count, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
MeshDeformation* StaticModel::GetMeshDeformation() const
|
MeshDeformation* StaticModel::GetMeshDeformation() const
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ public:
|
|||||||
MaterialBase* GetMaterial(int32 entryIndex) override;
|
MaterialBase* GetMaterial(int32 entryIndex) override;
|
||||||
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) 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 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;
|
MeshDeformation* GetMeshDeformation() const override;
|
||||||
void UpdateBounds() override;
|
void UpdateBounds() override;
|
||||||
|
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ Asset::LoadResult ParticleSystem::load()
|
|||||||
#endif
|
#endif
|
||||||
switch (version)
|
switch (version)
|
||||||
{
|
{
|
||||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
// [Deprecated on 23.07.2019, expires on 27.04.2021]
|
// [Deprecated on 23.07.2019, expires on 27.04.2021]
|
||||||
@@ -369,6 +370,7 @@ Asset::LoadResult ParticleSystem::load()
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023]
|
case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023]
|
||||||
case 4:
|
case 4:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "Engine/Core/Math/Ray.h"
|
#include "Engine/Core/Math/Ray.h"
|
||||||
#include "Engine/Graphics/RenderTask.h"
|
#include "Engine/Graphics/RenderTask.h"
|
||||||
#include "Engine/Graphics/RenderTools.h"
|
#include "Engine/Graphics/RenderTools.h"
|
||||||
|
#include "Engine/Graphics/Models/MeshAccessor.h"
|
||||||
#include "Engine/Graphics/Models/MeshBase.h"
|
#include "Engine/Graphics/Models/MeshBase.h"
|
||||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||||
#include "Engine/Physics/PhysicsBackend.h"
|
#include "Engine/Physics/PhysicsBackend.h"
|
||||||
@@ -50,6 +51,7 @@ void Cloth::SetMesh(const ModelInstanceActor::MeshReference& value)
|
|||||||
Function<void(const MeshBase*, MeshDeformationData&)> deformer;
|
Function<void(const MeshBase*, MeshDeformationData&)> deformer;
|
||||||
deformer.Bind<Cloth, &Cloth::RunClothDeformer>(this);
|
deformer.Bind<Cloth, &Cloth::RunClothDeformer>(this);
|
||||||
_meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, deformer);
|
_meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, deformer);
|
||||||
|
_meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex1, deformer);
|
||||||
_meshDeformation = nullptr;
|
_meshDeformation = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,6 +558,7 @@ bool Cloth::CreateCloth()
|
|||||||
int32 count;
|
int32 count;
|
||||||
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, data, count))
|
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, data, count))
|
||||||
return true;
|
return true;
|
||||||
|
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0
|
||||||
desc.VerticesData = data.Get();
|
desc.VerticesData = data.Get();
|
||||||
desc.VerticesCount = count;
|
desc.VerticesCount = count;
|
||||||
desc.VerticesStride = data.Length() / 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 i0 = indicesData.Get<uint16>()[index];
|
||||||
const int32 i1 = indicesData.Get<uint16>()[index + 1];
|
const int32 i1 = indicesData.Get<uint16>()[index + 1];
|
||||||
const int32 i2 = indicesData.Get<uint16>()[index + 2];
|
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)
|
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride)
|
||||||
const Float3 v0(GET_POS(i0));
|
const Float3 v0(GET_POS(i0));
|
||||||
const Float3 v1(GET_POS(i1));
|
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 i0 = indicesData.Get<uint32>()[index];
|
||||||
const int32 i1 = indicesData.Get<uint32>()[index + 1];
|
const int32 i1 = indicesData.Get<uint32>()[index + 1];
|
||||||
const int32 i2 = indicesData.Get<uint32>()[index + 2];
|
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)
|
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride)
|
||||||
const Float3 v0(GET_POS(i0));
|
const Float3 v0(GET_POS(i0));
|
||||||
const Float3 v1(GET_POS(i1));
|
const Float3 v1(GET_POS(i1));
|
||||||
@@ -770,11 +775,18 @@ bool Cloth::OnPreUpdate()
|
|||||||
return false;
|
return false;
|
||||||
BytesContainer verticesData;
|
BytesContainer verticesData;
|
||||||
int32 verticesCount;
|
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;
|
return false;
|
||||||
PROFILE_CPU_NAMED("Skinned Pose");
|
PROFILE_CPU_NAMED("Skinned Pose");
|
||||||
auto vbStride = (uint32)verticesData.Length() / verticesCount;
|
|
||||||
ASSERT(vbStride == sizeof(VB0SkinnedElementType));
|
|
||||||
PhysicsBackend::LockClothParticles(_cloth);
|
PhysicsBackend::LockClothParticles(_cloth);
|
||||||
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
|
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
|
||||||
// TODO: optimize memory allocs (eg. write directly to nvCloth mapped range or use shared allocator)
|
// 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)
|
if (paint[i] > ZeroTolerance)
|
||||||
continue;
|
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
|
// 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
|
// TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly
|
||||||
Matrix matrix;
|
Matrix matrix;
|
||||||
const SkeletonBone& bone0 = bones[vb0.BlendIndices.R];
|
const SkeletonBone& bone0 = bones[blendIndices.X];
|
||||||
Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix);
|
Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix);
|
||||||
Matrix boneMatrix = matrix * blendWeights.X;
|
Matrix boneMatrix = matrix * blendWeights.X;
|
||||||
if (blendWeights.Y > 0.0f)
|
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);
|
Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix);
|
||||||
boneMatrix += matrix * blendWeights.Y;
|
boneMatrix += matrix * blendWeights.Y;
|
||||||
}
|
}
|
||||||
if (blendWeights.Z > 0.0f)
|
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);
|
Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix);
|
||||||
boneMatrix += matrix * blendWeights.Z;
|
boneMatrix += matrix * blendWeights.Z;
|
||||||
}
|
}
|
||||||
if (blendWeights.W > 0.0f)
|
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);
|
Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix);
|
||||||
boneMatrix += matrix * blendWeights.W;
|
boneMatrix += matrix * blendWeights.W;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skin vertex position (similar to GPU vertex shader)
|
// 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
|
// Transform back to the cloth space
|
||||||
// TODO: skip when using identity?
|
// TODO: skip when using identity?
|
||||||
pos = _localTransform.WorldToLocal(pos);
|
position = _localTransform.WorldToLocal(position);
|
||||||
|
|
||||||
// Override fixed particle position
|
// Override fixed particle position
|
||||||
particlesSkinned[i] = Float4(pos, 0.0f);
|
particlesSkinned[i] = Float4(position, 0.0f);
|
||||||
anyFixed = true;
|
anyFixed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,11 +906,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
|
|||||||
PROFILE_CPU_NAMED("Cloth");
|
PROFILE_CPU_NAMED("Cloth");
|
||||||
PhysicsBackend::LockClothParticles(_cloth);
|
PhysicsBackend::LockClothParticles(_cloth);
|
||||||
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
|
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
|
||||||
|
|
||||||
auto vbData = deformation.VertexBuffer.Data.Get();
|
|
||||||
auto vbCount = (uint32)mesh->GetVertexCount();
|
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);
|
ASSERT((uint32)particles.Length() >= vbCount);
|
||||||
|
|
||||||
// Calculate normals
|
// Calculate normals
|
||||||
@@ -949,6 +960,12 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update mesh vertices based on the cloth particles positions
|
// 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 (auto* animatedModel = Cast<AnimatedModel>(GetParent()))
|
||||||
{
|
{
|
||||||
if (animatedModel->GraphInstance.NodesPose.IsEmpty())
|
if (animatedModel->GraphInstance.NodesPose.IsEmpty())
|
||||||
@@ -965,39 +982,44 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
|
|||||||
const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton;
|
const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton;
|
||||||
|
|
||||||
// Animated model uses skinning thus requires to set vertex position inverse to skeleton bones
|
// 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;
|
const float* paint = _paint.Count() >= particles.Length() ? _paint.Get() : nullptr;
|
||||||
for (uint32 i = 0; i < vbCount; i++)
|
for (uint32 i = 0; i < vbCount; i++)
|
||||||
{
|
{
|
||||||
VB0SkinnedElementType& vb = *(VB0SkinnedElementType*)vbData;
|
|
||||||
vbData += vbStride;
|
|
||||||
|
|
||||||
// Skip fixed vertices
|
// Skip fixed vertices
|
||||||
if (paint && paint[i] < ZeroTolerance)
|
if (paint && paint[i] < ZeroTolerance)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Calculate skinned vertex matrix from bones blending
|
// 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
|
// TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly
|
||||||
Matrix matrix;
|
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::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix);
|
||||||
Matrix boneMatrix = matrix * blendWeights.X;
|
Matrix boneMatrix = matrix * blendWeights.X;
|
||||||
if (blendWeights.Y > 0.0f)
|
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);
|
Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix);
|
||||||
boneMatrix += matrix * blendWeights.Y;
|
boneMatrix += matrix * blendWeights.Y;
|
||||||
}
|
}
|
||||||
if (blendWeights.Z > 0.0f)
|
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);
|
Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix);
|
||||||
boneMatrix += matrix * blendWeights.Z;
|
boneMatrix += matrix * blendWeights.Z;
|
||||||
}
|
}
|
||||||
if (blendWeights.W > 0.0f)
|
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);
|
Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix);
|
||||||
boneMatrix += matrix * blendWeights.W;
|
boneMatrix += matrix * blendWeights.W;
|
||||||
}
|
}
|
||||||
@@ -1006,43 +1028,59 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
|
|||||||
Matrix boneMatrixInv;
|
Matrix boneMatrixInv;
|
||||||
Matrix::Invert(boneMatrix, boneMatrixInv);
|
Matrix::Invert(boneMatrix, boneMatrixInv);
|
||||||
Float3 pos = *(Float3*)&particles.Get()[i];
|
Float3 pos = *(Float3*)&particles.Get()[i];
|
||||||
vb.Position = Float3::Transform(pos, boneMatrixInv);
|
pos = Float3::Transform(pos, boneMatrixInv);
|
||||||
|
positionStream.SetFloat3(i, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_simulationSettings.ComputeNormals)
|
if (_simulationSettings.ComputeNormals)
|
||||||
{
|
{
|
||||||
// Write normals
|
// 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];
|
for (uint32 i = 0; i < vbCount; i++)
|
||||||
normal.Normalize();
|
{
|
||||||
VB0SkinnedElementType& vb = *(VB0SkinnedElementType*)vbData;
|
Float3 normal = normals.Get()[i];
|
||||||
vbData += vbStride;
|
normal.Normalize();
|
||||||
RenderTools::CalculateTangentFrame(vb.Normal, vb.Tangent, normal);
|
Float3 n;
|
||||||
|
Float4 t;
|
||||||
|
RenderTools::CalculateTangentFrame(n, t, normal);
|
||||||
|
normalStream.SetFloat3(i, n);
|
||||||
|
tangentStream.SetFloat4(i, t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (deformation.Type == MeshBufferType::Vertex0)
|
else if (deformation.Type == MeshBufferType::Vertex0)
|
||||||
{
|
{
|
||||||
// Copy particle positions to the mesh data
|
// Copy particle positions to the mesh data
|
||||||
ASSERT(vbStride == sizeof(VB0ElementType));
|
auto positionStream = accessor.Position();
|
||||||
for (uint32 i = 0; i < vbCount; i++)
|
if (positionStream.IsValid())
|
||||||
{
|
{
|
||||||
*(Float3*)vbData = *(Float3*)&particles.Get()[i];
|
for (uint32 i = 0; i < vbCount; i++)
|
||||||
vbData += vbStride;
|
{
|
||||||
|
positionStream.SetFloat3(i, *(const Float3*)&particles.Get()[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Write normals for the modified vertices by the cloth
|
// Write normals for the modified vertices by the cloth
|
||||||
ASSERT(vbStride == sizeof(VB1ElementType));
|
auto normalStream = accessor.Normal();
|
||||||
for (uint32 i = 0; i < vbCount; i++)
|
auto tangentStream = accessor.Tangent();
|
||||||
|
if (normalStream.IsValid() && tangentStream.IsValid())
|
||||||
{
|
{
|
||||||
Float3 normal = normals.Get()[i];
|
for (uint32 i = 0; i < vbCount; i++)
|
||||||
normal.Normalize();
|
{
|
||||||
VB1ElementType& vb = *(VB1ElementType*)vbData;
|
Float3 normal = normals.Get()[i];
|
||||||
vbData += vbStride;
|
normal.Normalize();
|
||||||
RenderTools::CalculateTangentFrame(vb.Normal, vb.Tangent, normal);
|
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/Serialization/FileWriteStream.h"
|
||||||
#include "Engine/Graphics/RenderTools.h"
|
#include "Engine/Graphics/RenderTools.h"
|
||||||
#include "Engine/Graphics/Textures/TextureData.h"
|
#include "Engine/Graphics/Textures/TextureData.h"
|
||||||
|
#include "Engine/Graphics/PixelFormatSampler.h"
|
||||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||||
#include "Engine/Utilities/AnsiPathTempFile.h"
|
#include "Engine/Utilities/AnsiPathTempFile.h"
|
||||||
#include "Engine/Platform/File.h"
|
#include "Engine/Platform/File.h"
|
||||||
@@ -199,7 +200,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Convert into RGBA8
|
// Convert into RGBA8
|
||||||
const auto sampler = GetSampler(texture->Format);
|
const auto sampler = PixelFormatSampler::Get(texture->Format);
|
||||||
if (sampler == nullptr)
|
if (sampler == nullptr)
|
||||||
{
|
{
|
||||||
LOG(Warning, "Texture data format {0} is not supported.", (int32)textureData.Format);
|
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++)
|
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)
|
if (sRGB)
|
||||||
color = Color::SrgbToLinear(color);
|
color = Color::SrgbToLinear(color);
|
||||||
*(ptr + x + y * texture->Width) = color.ToFloat4();
|
*(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++)
|
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)
|
if (sRGB)
|
||||||
color = Color::SrgbToLinear(color);
|
color = Color::SrgbToLinear(color);
|
||||||
*(ptr + x + y * texture->Width) = Color32(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);
|
dst.Items.Resize(arraySize, false);
|
||||||
auto formatSize = PixelFormatExtensions::SizeInBytes(textureData->Format);
|
auto formatSize = PixelFormatExtensions::SizeInBytes(textureData->Format);
|
||||||
auto components = PixelFormatExtensions::ComputeComponentsCount(textureData->Format);
|
auto components = PixelFormatExtensions::ComputeComponentsCount(textureData->Format);
|
||||||
auto sampler = TextureTool::GetSampler(textureData->Format);
|
auto sampler = PixelFormatSampler::Get(textureData->Format);
|
||||||
if (!sampler)
|
if (!sampler)
|
||||||
{
|
{
|
||||||
LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast<int32>(textureData->Format));
|
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
|
#endif
|
||||||
{
|
{
|
||||||
int32 bytesPerPixel = PixelFormatExtensions::SizeInBytes(dstFormat);
|
int32 bytesPerPixel = PixelFormatExtensions::SizeInBytes(dstFormat);
|
||||||
auto dstSampler = TextureTool::GetSampler(dstFormat);
|
auto dstSampler = PixelFormatSampler::Get(dstFormat);
|
||||||
if (!dstSampler)
|
if (!dstSampler)
|
||||||
{
|
{
|
||||||
LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast<int32>(dstFormat));
|
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++)
|
for (int32 x = 0; x < mipWidth; x++)
|
||||||
{
|
{
|
||||||
// Sample source texture
|
// 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
|
// 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 components = PixelFormatExtensions::ComputeComponentsCount(format);
|
||||||
auto srcMipWidth = srcMip.RowPitch / formatSize;
|
auto srcMipWidth = srcMip.RowPitch / formatSize;
|
||||||
auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch;
|
auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch;
|
||||||
auto sampler = GetSampler(format);
|
auto sampler = PixelFormatSampler::Get(format);
|
||||||
|
|
||||||
// Allocate memory
|
// Allocate memory
|
||||||
dstMip.RowPitch = dstMipWidth * formatSize;
|
dstMip.RowPitch = dstMipWidth * formatSize;
|
||||||
@@ -869,8 +870,8 @@ bool TextureTool::ResizeStb(PixelFormat format, TextureMipData& dstMip, const Te
|
|||||||
for (int32 x = 0; x < dstMipWidth; x++)
|
for (int32 x = 0; x < dstMipWidth; x++)
|
||||||
{
|
{
|
||||||
const Float2 uv((float)x / dstMipWidth, (float)y / dstMipHeight);
|
const Float2 uv((float)x / dstMipWidth, (float)y / dstMipHeight);
|
||||||
Color color = SamplePoint(sampler, uv, srcMip.Data.Get(), srcSize, srcMip.RowPitch);
|
Color color = sampler->SamplePoint(srcMip.Data.Get(), uv, srcSize, srcMip.RowPitch);
|
||||||
Store(sampler, x, y, dstMip.Data.Get(), dstMip.RowPitch, color);
|
sampler->Store(dstMip.Data.Get(), x, y, dstMip.RowPitch, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -18,18 +18,37 @@
|
|||||||
#include "Engine/Content/Content.h"
|
#include "Engine/Content/Content.h"
|
||||||
#include "Engine/Core/Types/Variant.h"
|
#include "Engine/Core/Types/Variant.h"
|
||||||
#include "Engine/Graphics/RenderTools.h"
|
#include "Engine/Graphics/RenderTools.h"
|
||||||
|
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||||
#include "Engine/Localization/Localization.h"
|
#include "Engine/Localization/Localization.h"
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
#include "Editor/Editor.h"
|
#include "Editor/Editor.h"
|
||||||
#endif
|
#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)
|
TextRender::TextRender(const SpawnParams& params)
|
||||||
: Actor(params)
|
: Actor(params)
|
||||||
, _size(32)
|
, _size(32)
|
||||||
, _ib(0, sizeof(uint16))
|
, _ib(0, sizeof(uint16))
|
||||||
, _vb0(0, sizeof(VB0ElementType), String::Empty, VB0ElementType::GetLayout())
|
, _vb(0, sizeof(TextRenderVertex), String::Empty, TextRenderVertex::GetLayout())
|
||||||
, _vb1(0, sizeof(VB1ElementType), String::Empty, VB1ElementType::GetLayout())
|
|
||||||
, _vb2(0, sizeof(VB2ElementType), String::Empty, VB2ElementType::GetLayout())
|
|
||||||
{
|
{
|
||||||
_color = Color::White;
|
_color = Color::White;
|
||||||
_localBox = BoundingBox(Vector3::Zero);
|
_localBox = BoundingBox(Vector3::Zero);
|
||||||
@@ -102,9 +121,7 @@ void TextRender::UpdateLayout()
|
|||||||
{
|
{
|
||||||
// Clear
|
// Clear
|
||||||
_ib.Clear();
|
_ib.Clear();
|
||||||
_vb0.Clear();
|
_vb.Clear();
|
||||||
_vb1.Clear();
|
|
||||||
_vb2.Clear();
|
|
||||||
_localBox = BoundingBox(Vector3::Zero);
|
_localBox = BoundingBox(Vector3::Zero);
|
||||||
BoundingBox::Transform(_localBox, _transform, _box);
|
BoundingBox::Transform(_localBox, _transform, _box);
|
||||||
BoundingSphere::FromBox(_box, _sphere);
|
BoundingSphere::FromBox(_box, _sphere);
|
||||||
@@ -163,9 +180,7 @@ void TextRender::UpdateLayout()
|
|||||||
|
|
||||||
// Prepare buffers capacity
|
// Prepare buffers capacity
|
||||||
_ib.Data.EnsureCapacity(text.Length() * 6 * sizeof(uint16));
|
_ib.Data.EnsureCapacity(text.Length() * 6 * sizeof(uint16));
|
||||||
_vb0.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB0ElementType));
|
_vb.Data.EnsureCapacity(text.Length() * 4 * sizeof(TextRenderVertex));
|
||||||
_vb1.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB1ElementType));
|
|
||||||
_vb2.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB2ElementType));
|
|
||||||
_buffersDirty = true;
|
_buffersDirty = true;
|
||||||
|
|
||||||
// Init draw chunks data
|
// Init draw chunks data
|
||||||
@@ -268,20 +283,15 @@ void TextRender::UpdateLayout()
|
|||||||
byte sign = 0;
|
byte sign = 0;
|
||||||
|
|
||||||
// Write vertices
|
// Write vertices
|
||||||
VB0ElementType vb0;
|
TextRenderVertex v;
|
||||||
VB1ElementType vb1;
|
|
||||||
VB2ElementType vb2;
|
|
||||||
#define WRITE_VB(pos, uv) \
|
#define WRITE_VB(pos, uv) \
|
||||||
vb0.Position = Float3(-pos, 0.0f); \
|
v.Position = Float3(-pos, 0.0f); \
|
||||||
box.Merge(vb0.Position); \
|
box.Merge(v.Position); \
|
||||||
_vb0.Write(vb0); \
|
v.TexCoord = Half2(uv); \
|
||||||
vb1.TexCoord = Half2(uv); \
|
v.Normal = Float1010102(normal * 0.5f + 0.5f, 0); \
|
||||||
vb1.Normal = Float1010102(normal * 0.5f + 0.5f, 0); \
|
v.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); \
|
||||||
vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); \
|
v.Color = color; \
|
||||||
vb1.LightmapUVs = Half2::Zero; \
|
_vb.Write(v)
|
||||||
_vb1.Write(vb1); \
|
|
||||||
vb2.Color = color; \
|
|
||||||
_vb2.Write(vb2)
|
|
||||||
//
|
//
|
||||||
WRITE_VB(charRect.GetBottomRight(), rightBottomUV);
|
WRITE_VB(charRect.GetBottomRight(), rightBottomUV);
|
||||||
WRITE_VB(charRect.GetBottomLeft(), Float2(upperLeftUV.X, rightBottomUV.Y));
|
WRITE_VB(charRect.GetBottomLeft(), Float2(upperLeftUV.X, rightBottomUV.Y));
|
||||||
@@ -317,7 +327,7 @@ void TextRender::UpdateLayout()
|
|||||||
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
||||||
// Setup collision proxy for detailed collision detection for triangles
|
// Setup collision proxy for detailed collision detection for triangles
|
||||||
const int32 totalIndicesCount = _ib.Data.Count() / sizeof(uint16);
|
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
|
#endif
|
||||||
|
|
||||||
// Update text bounds (from build vertex positions)
|
// Update text bounds (from build vertex positions)
|
||||||
@@ -351,16 +361,14 @@ void TextRender::Draw(RenderContext& renderContext)
|
|||||||
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
|
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
|
||||||
|
|
||||||
const DrawPass drawModes = DrawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(ShadowsMode);
|
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
|
// Flush buffers
|
||||||
if (_buffersDirty)
|
if (_buffersDirty)
|
||||||
{
|
{
|
||||||
_buffersDirty = false;
|
_buffersDirty = false;
|
||||||
_ib.Flush();
|
_ib.Flush();
|
||||||
_vb0.Flush();
|
_vb.Flush();
|
||||||
_vb1.Flush();
|
|
||||||
_vb2.Flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup draw call
|
// Setup draw call
|
||||||
@@ -373,9 +381,7 @@ void TextRender::Draw(RenderContext& renderContext)
|
|||||||
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
|
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
|
||||||
drawCall.PerInstanceRandom = GetPerInstanceRandom();
|
drawCall.PerInstanceRandom = GetPerInstanceRandom();
|
||||||
drawCall.Geometry.IndexBuffer = _ib.GetBuffer();
|
drawCall.Geometry.IndexBuffer = _ib.GetBuffer();
|
||||||
drawCall.Geometry.VertexBuffers[0] = _vb0.GetBuffer();
|
drawCall.Geometry.VertexBuffers[0] = _vb.GetBuffer();
|
||||||
drawCall.Geometry.VertexBuffers[1] = _vb1.GetBuffer();
|
|
||||||
drawCall.Geometry.VertexBuffers[2] = _vb2.GetBuffer();
|
|
||||||
drawCall.InstanceCount = 1;
|
drawCall.InstanceCount = 1;
|
||||||
|
|
||||||
// Submit draw calls
|
// Submit draw calls
|
||||||
|
|||||||
@@ -44,9 +44,7 @@ private:
|
|||||||
BoundingBox _localBox;
|
BoundingBox _localBox;
|
||||||
GeometryDrawStateData _drawState;
|
GeometryDrawStateData _drawState;
|
||||||
DynamicIndexBuffer _ib;
|
DynamicIndexBuffer _ib;
|
||||||
DynamicVertexBuffer _vb0;
|
DynamicVertexBuffer _vb;
|
||||||
DynamicVertexBuffer _vb1;
|
|
||||||
DynamicVertexBuffer _vb2;
|
|
||||||
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
||||||
CollisionProxy _collisionProxy;
|
CollisionProxy _collisionProxy;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -24,11 +24,18 @@ namespace FlaxEngine.Utilities
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The vertex buffer.
|
/// The vertex buffer.
|
||||||
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use new VertexAccessor.")]
|
||||||
public Mesh.Vertex[] VertexBuffer;
|
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 MeshData[][] _meshDatas;
|
||||||
private bool _inProgress;
|
private bool _inProgress;
|
||||||
private bool _cancel;
|
private bool _cancel;
|
||||||
@@ -46,9 +53,9 @@ namespace FlaxEngine.Utilities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Requests the mesh data.
|
/// Requests the mesh data.
|
||||||
/// </summary>
|
/// </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>
|
/// <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)
|
if (model == null)
|
||||||
throw new ArgumentNullException();
|
throw new ArgumentNullException();
|
||||||
@@ -110,23 +117,29 @@ namespace FlaxEngine.Utilities
|
|||||||
if (_model.WaitForLoaded())
|
if (_model.WaitForLoaded())
|
||||||
throw new Exception("WaitForLoaded failed");
|
throw new Exception("WaitForLoaded failed");
|
||||||
|
|
||||||
var lods = _model.LODs;
|
var lodsCount = _model.LODsCount;
|
||||||
_meshDatas = new MeshData[lods.Length][];
|
_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];
|
_model.GetMeshes(out var meshes, lodIndex);
|
||||||
var meshes = lod.Meshes;
|
|
||||||
_meshDatas[lodIndex] = new MeshData[meshes.Length];
|
_meshDatas[lodIndex] = new MeshData[meshes.Length];
|
||||||
|
|
||||||
for (int meshIndex = 0; meshIndex < meshes.Length && !_cancel; meshIndex++)
|
for (int meshIndex = 0; meshIndex < meshes.Length && !_cancel; meshIndex++)
|
||||||
{
|
{
|
||||||
var mesh = meshes[meshIndex];
|
var mesh = meshes[meshIndex];
|
||||||
_meshDatas[lodIndex][meshIndex] = new MeshData
|
var meshData = new MeshData
|
||||||
{
|
{
|
||||||
IndexBuffer = mesh.DownloadIndexBuffer(),
|
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;
|
success = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user