You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

392 lines
12 KiB
C#

3 years ago
using System;
using UnityEditor;
namespace UnityEngine.Polybrush
{
/// <summary>
/// Script that will be attached at all time on any polybrush object
/// It will be used to store a copy of the mesh inside a polymesh object (that is serializable).
/// It will also handle the advs switch mode (on/off)
/// </summary>
[ExecuteInEditMode]
class PolybrushMesh : MonoBehaviour
{
internal enum Mode
{
Mesh,
AdditionalVertexStream
}
internal static class Styles
{
internal const string k_VertexMismatchStringFormat = "Warning! The GameObject \"{0}\" cannot apply it's 'Additional Vertex Streams' mesh, because it's base mesh has changed and has a different vertex count.";
}
internal enum ObjectType
{
Mesh,
SkinnedMesh
}
/// <summary>
/// References components needed by Polybrush: MeshFilter, MeshRenderer and SkinnedMeshRenderer.
/// Will cache references if they are found to avoid additional GetComponent() calls.
/// </summary>
internal struct MeshComponentsCache
{
GameObject m_Owner;
MeshFilter m_MeshFilter;
MeshRenderer m_MeshRenderer;
SkinnedMeshRenderer m_SkinMeshRenderer;
internal bool IsValid()
{
return m_Owner != null;
}
internal MeshFilter MeshFilter
{
get { return m_MeshFilter; }
}
internal MeshRenderer MeshRenderer
{
get { return m_MeshRenderer; }
}
internal SkinnedMeshRenderer SkinnedMeshRenderer
{
get { return m_SkinMeshRenderer; }
}
internal MeshComponentsCache(GameObject root)
{
m_Owner = root;
m_MeshFilter = root.GetComponent<MeshFilter>();
m_MeshRenderer = root.GetComponent<MeshRenderer>();
m_SkinMeshRenderer = root.GetComponent<SkinnedMeshRenderer>();
}
}
//seriazlied polymesh stored on the component
[SerializeField]
PolyMesh m_PolyMesh;
//mesh ref from the skinmesh asset, if any
[SerializeField]
Mesh m_SkinMeshRef;
[SerializeField]
Mesh m_OriginalMeshObject;
[SerializeField]
Mode m_Mode;
MeshComponentsCache m_ComponentsCache;
/// <summary>
/// Accessor to Unity rendering related components attached to the same GameObject as this component.
/// </summary>
internal MeshComponentsCache componentsCache
{
get { return m_ComponentsCache; }
}
/// <summary>
/// Returns true if there are applied modification on this mesh.
/// </summary>
internal bool hasAppliedChanges
{
get
{
if (m_ComponentsCache.MeshFilter)
return m_PolyMesh.ToUnityMesh() != m_ComponentsCache.MeshFilter.sharedMesh;
if (m_ComponentsCache.SkinnedMeshRenderer)
return m_PolyMesh.ToUnityMesh() != m_ComponentsCache.SkinnedMeshRenderer.sharedMesh;
return false;
}
}
internal bool hasAppliedAdditionalVertexStreams
{
get
{
if (m_ComponentsCache.MeshRenderer != null && m_ComponentsCache.MeshRenderer.additionalVertexStreams != null)
return m_ComponentsCache.MeshRenderer.additionalVertexStreams == m_PolyMesh.ToUnityMesh();
return false;
}
}
//access to the polymesh directly
internal PolyMesh polyMesh
{
get
{
return m_PolyMesh;
}
}
//get: will convert the data stored inside polymesh to a mesh, return it
internal Mesh storedMesh
{
get
{
if (m_PolyMesh != null)
{
return m_PolyMesh.ToUnityMesh();
}
else
{
return null;
}
}
}
internal ObjectType type
{
get
{
if (m_ComponentsCache.SkinnedMeshRenderer)
return ObjectType.SkinnedMesh;
return ObjectType.Mesh;
}
}
[Obsolete()]
internal static bool s_UseADVS { private get; set; }
internal Mode mode
{
get { return m_Mode; }
set { UpdateMode(value); }
}
internal Mesh skinMeshRef
{
get
{
return m_SkinMeshRef;
}
set
{
m_SkinMeshRef = value;
}
}
internal Mesh sourceMesh
{
get { return m_OriginalMeshObject; }
}
bool m_Initialized = false;
/// <summary>
/// Returns true if internal data has been initialized.
/// If returns false, use <see cref="Initialize"/>.
/// </summary>
internal bool isInitialized
{
get { return m_Initialized; }
}
/// <summary>
/// Initializes non-serialized internal cache in the component.
/// Should be called after instantiation.
/// Use <see cref="isInitialized"/> to check if it has already been initialized.
/// </summary>
internal void Initialize()
{
if (isInitialized)
return;
if (!m_ComponentsCache.IsValid())
m_ComponentsCache = new MeshComponentsCache(gameObject);
if (m_PolyMesh == null)
m_PolyMesh = new PolyMesh();
Mesh mesh = null;
if (m_ComponentsCache.MeshFilter != null)
{
mesh = m_ComponentsCache.MeshFilter.sharedMesh;
if (m_OriginalMeshObject == null)
m_OriginalMeshObject = mesh;
}
else if (m_ComponentsCache.SkinnedMeshRenderer != null)
mesh = m_ComponentsCache.SkinnedMeshRenderer.sharedMesh;
if (!polyMesh.IsValid() && mesh)
SetMesh(mesh);
else if (polyMesh.IsValid())
SetMesh(polyMesh.ToUnityMesh());
m_Initialized = true;
}
/// <summary>
/// Update Polymesh data with the given Unity Mesh information.
/// </summary>
/// <param name="unityMesh">Unity mesh.</param>
internal void SetMesh(Mesh unityMesh)
{
if (unityMesh == null)
return;
m_PolyMesh.InitializeWithUnityMesh(unityMesh);
SynchronizeWithMeshRenderer();
}
internal void SetAdditionalVertexStreams(Mesh vertexStreams)
{
m_PolyMesh.ApplyAttributesFromUnityMesh(vertexStreams, MeshChannelUtility.ToMask(vertexStreams));
SynchronizeWithMeshRenderer();
}
/// <summary>
/// Update GameObject's renderers with PolybrushMesh data.
/// </summary>
internal void SynchronizeWithMeshRenderer()
{
if (m_PolyMesh == null)
return;
m_PolyMesh.UpdateMeshFromData();
if (m_ComponentsCache.SkinnedMeshRenderer != null && skinMeshRef != null)
UpdateSkinMesh();
if (mode == Mode.Mesh)
{
if (m_ComponentsCache.MeshFilter != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Undo.RecordObject(m_ComponentsCache.MeshFilter, "Assign new mesh to MeshFilter");
#endif
m_ComponentsCache.MeshFilter.sharedMesh = m_PolyMesh.ToUnityMesh();
}
SetAdditionalVertexStreamsOnRenderer(null);
}
else
{
if (!CanApplyAdditionalVertexStreams())
{
if (hasAppliedAdditionalVertexStreams)
RemoveAdditionalVertexStreams();
Debug.LogWarning(string.Format(Styles.k_VertexMismatchStringFormat, gameObject.name), this);
return;
}
SetAdditionalVertexStreamsOnRenderer(m_PolyMesh.ToUnityMesh());
}
}
/// <summary>
/// Checks if all conditions are met to apply Additional Vertex Streams.
/// </summary>
/// <returns></returns>
internal bool CanApplyAdditionalVertexStreams()
{
// If "Use additional vertex streams is enabled in Preferences."
if (mode == Mode.AdditionalVertexStream)
{
if (m_ComponentsCache.MeshFilter != null && m_ComponentsCache.MeshFilter.sharedMesh != null)
{
if (m_ComponentsCache.MeshFilter.sharedMesh.vertexCount != polyMesh.vertexCount)
return false;
}
}
return true;
}
/// <summary>
/// Takes the mesh from the skinmesh in the asset (saved by EditableObject during creation) and apply skinmesh information to the regular stored mesh.
/// Then set it to the skinmeshrenderer.
/// </summary>
void UpdateSkinMesh()
{
Mesh mesh = skinMeshRef;
Mesh generatedMesh = m_PolyMesh.ToUnityMesh();
generatedMesh.boneWeights = mesh.boneWeights;
generatedMesh.bindposes = mesh.bindposes;
#if UNITY_EDITOR
if (!Application.isPlaying)
Undo.RecordObject(m_ComponentsCache.SkinnedMeshRenderer, "Assign new mesh to SkinnedMeshRenderer");
#endif
m_ComponentsCache.SkinnedMeshRenderer.sharedMesh = generatedMesh;
}
/// <summary>
/// Set the Additional Vertex Streams on the current MeshRenderer.
/// </summary>
/// <param name="mesh"></param>
void SetAdditionalVertexStreamsOnRenderer(Mesh mesh)
{
if (m_ComponentsCache.MeshRenderer != null)
m_ComponentsCache.MeshRenderer.additionalVertexStreams = mesh;
}
internal void RemoveAdditionalVertexStreams()
{
SetAdditionalVertexStreamsOnRenderer(null);
}
void UpdateMode(Mode newMode)
{
// SkinnedMesh can only work in baked mode.
// It doesn't support Additional Vertex Streams.
if (type == ObjectType.SkinnedMesh && m_Mode != Mode.Mesh)
{
m_Mode = Mode.Mesh;
return;
}
m_Mode = newMode;
if (mode == Mode.AdditionalVertexStream)
{
if (m_ComponentsCache.MeshFilter != null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Undo.RecordObject(m_ComponentsCache.MeshFilter, "Assign new mesh to MeshFilter");
#endif
m_ComponentsCache.MeshFilter.sharedMesh = m_OriginalMeshObject;
}
SetMesh(m_PolyMesh.ToUnityMesh());
}
else if (mode == Mode.Mesh)
{
SetMesh(m_PolyMesh.ToUnityMesh());
}
}
void OnEnable()
{
m_Initialized = false;
if (!isInitialized)
Initialize();
}
void OnDestroy()
{
// Time.frameCount is zero when loading scenes in the Editor. It's the only way I could figure to
// differentiate between OnDestroy invoked from user delete & editor scene loading.
if (Application.isEditor &&
!Application.isPlaying &&
Time.frameCount > 0)
{
// Re-assign source mesh only if it has changed.
// Likely to happen with ProBuilder.
if (type == ObjectType.Mesh && componentsCache.MeshFilter !=null && sourceMesh == componentsCache.MeshFilter.sharedMesh)
return;
SetMesh(sourceMesh);
}
}
}
}