Skip to content

Instantly share code, notes, and snippets.

@kraj0t
Created May 9, 2022 21:26
Show Gist options
  • Save kraj0t/4b0739f8ada920980693537a13a103bf to your computer and use it in GitHub Desktop.
Save kraj0t/4b0739f8ada920980693537a13a103bf to your computer and use it in GitHub Desktop.
[Unity] MeshDataCache - collect mesh data and modify it before applying the changes to the Mesh. Compatible with VertexHelper
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
namespace kraj0t.Rendering
{
/// <summary>
/// This class is a convenient way of collecting mesh data and avoiding having to constantly query and dirty a Unity mesh, which is BAD for performance.
///
/// It allows you to edit to and from Unity.Mesh and VertexHelper instances.
///
/// The current implementation is limited to the same structure as VertexHelper to ensure compatibility. However, the said class presents a number of
/// limitations that do not allow for easy access to the data. See <see cref="VertexHelperExtensions"/> for more info.
///
/// Current known limitations and future improvements:
/// - All limitations of VertexHelper are currently inherited. See <see cref="VertexHelperExtensions"/> for more info.
/// - No support for submeshes
/// - Up to 4 uv channels.
/// - No bone information.
/// - For greater performance, this class could use the methods in the Mesh class that expect NativeArray or buffers.
/// </summary>
public class MeshDataCache
{
private const MeshUpdateFlags DISABLE_MESH_UPDATE_FLAGS = MeshUpdateFlags.DontNotifyMeshUsers | MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontResetBoneBounds | MeshUpdateFlags.DontValidateIndices;
private static MeshDataCache _reusableInstance;
// TODO support multiple meshes. Some of these lists should be Lists of Lists.
public List<Vector3> vertices;
public List<Color32> colors;
public List<Vector4> uv0;
public List<Vector4> uv1;
public List<Vector4> uv2;
public List<Vector4> uv3;
public List<Vector3> normals;
public List<Vector4> tangents;
public List<int> indices;
public bool HasColors => colors.Count != 0;
public bool HasUV0 => uv0.Count != 0;
public bool HasUV1 => uv1.Count != 0;
public bool HasUV2 => uv2.Count != 0;
public bool HasUV3 => uv3.Count != 0;
public bool HasNormals => normals.Count != 0;
public bool HasTangents => tangents.Count != 0;
/// <summary>Returns an instance of MeshDataCache that can be shared across different parts of the code.
/// Consider using this instance instead of creating a new one.</summary>
public static MeshDataCache ReusableInstance => _reusableInstance ??= new MeshDataCache();
public void Clear()
{
vertices.Clear();
colors.Clear();
uv0.Clear();
uv1.Clear();
uv2.Clear();
uv3.Clear();
normals.Clear();
tangents.Clear();
indices.Clear();
}
/// <summary>Populate the cache from the given mesh.</summary>
public void Read(Mesh mesh)
{
InitializeListsIfNeeded();
mesh.GetVertices(vertices);
mesh.GetColors(colors);
mesh.GetUVs(0, uv0);
mesh.GetUVs(1, uv1);
mesh.GetUVs(2, uv2);
mesh.GetUVs(3, uv3);
mesh.GetNormals(normals);
mesh.GetTangents(tangents);
mesh.GetIndices(indices, 0);
}
/// <summary>Populate the cache from the given VertexHelper.
/// The lists will be copied by reference from the VertexHelper. This means that, after calling this, modifying the cache's lists will affect the
/// VertexHelper directly, without needing to set the helper's lists all the time. This also means that when the VertexHelper is disposed this
/// cache will become unusable too.</summary>
public void Read(VertexHelper helper)
{
vertices = helper.GetPositions();
colors = helper.GetColors();
uv0 = helper.GetUV0();
uv1 = helper.GetUV1();
uv2 = helper.GetUV2();
uv3 = helper.GetUV3();
normals = helper.GetNormals();
tangents = helper.GetTangents();
indices = helper.GetIndices();
}
/// <summary>Feed the cache's data to the given mesh.</summary>
public void Write(Mesh mesh, bool recalculateBounds = true, MeshTopology topology = MeshTopology.Triangles)
{
mesh.SetVertices(vertices);
mesh.SetIndices(indices, topology, 0, false);
if (HasColors) mesh.SetColors(colors);
if (HasUV0) mesh.SetUVs(0, uv0);
if (HasUV1) mesh.SetUVs(1, uv1);
if (HasUV2) mesh.SetUVs(2, uv2);
if (HasUV3) mesh.SetUVs(3, uv3);
if (HasNormals) mesh.SetNormals(normals);
if (HasTangents) mesh.SetTangents(tangents);
if (recalculateBounds)
{
mesh.RecalculateBounds(DISABLE_MESH_UPDATE_FLAGS);
}
mesh.MarkModified();
}
/// <summary><para>Convenience method to quickly add a new vertex to the cache.</para>
///
/// <para>Remember that you can always target the cache's lists directly if you know what you're doing</para>
///
/// <para>IMPORTANT: you need to manually re-triangulate after calling this method!</para></summary>
/// <returns>The index of the new vertex, which is the last index in the Vertices list.</returns>
public int AddVertex(Vector3 position, Color32 color, Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3, Vector3 normal, Vector4 tangent)
{
InitializeListsIfNeeded();
var index = vertices.Count;
vertices.Add(position);
if (HasColors) colors.Add(color);
if (HasUV0) this.uv0.Add(uv0);
if (HasUV1) this.uv1.Add(uv1);
if (HasUV2) this.uv2.Add(uv2);
if (HasUV3) this.uv3.Add(uv3);
if (HasNormals) normals.Add(normal);
if (HasTangents) tangents.Add(tangent);
return index;
}
/// <summary><para>Convenience method to quickly add new a vertex to the cache by interpolating it from two other vertices in the cache.</para>
///
/// <para>Remember that you can always target the cache's lists directly if you know what you're doing</para>
///
/// <para>IMPORTANT: you need to manually re-triangulate after calling this method!</para></summary>
/// <returns>The index of the new vertex, which is the last index in the Vertices list.</returns>
public int AddVertex(Vector3 position, int neighbor0, int neighbor1, float t)
{
var index = vertices.Count;
vertices.Add(position);
if (HasColors) colors.Add(Color32.LerpUnclamped(colors[neighbor0], colors[neighbor1], t));
if (HasUV0) uv0.Add(Vector2.LerpUnclamped(uv0[neighbor0], uv0[neighbor1], t));
if (HasUV1) uv1.Add(Vector2.LerpUnclamped(uv1[neighbor0], uv1[neighbor1], t));
if (HasUV2) uv2.Add(Vector2.LerpUnclamped(uv2[neighbor0], uv2[neighbor1], t));
if (HasUV3) uv3.Add(Vector2.LerpUnclamped(uv3[neighbor0], uv3[neighbor1], t));
if (HasNormals) normals.Add(Vector3.SlerpUnclamped(normals[neighbor0], normals[neighbor1], t));
if (HasTangents) tangents.Add(Vector4.LerpUnclamped(tangents[neighbor0], tangents[neighbor1], t));
return index;
}
private void InitializeListsIfNeeded()
{
vertices ??= new List<Vector3>();
colors ??= new List<Color32>();
uv0 ??= new List<Vector4>();
uv1 ??= new List<Vector4>();
uv2 ??= new List<Vector4>();
uv3 ??= new List<Vector4>();
normals ??= new List<Vector3>();
tangents ??= new List<Vector4>();
indices ??= new List<int>();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment