using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Polybrush;
using UnityEditor.SettingsManagement;
namespace UnityEditor.Polybrush
{
///
/// Brush mode for moving vertices in a direction.
///
internal class BrushModeSmooth : BrushModeSculpt
{
class EditableObjectData
{
public Vector3[] vertices;
public Dictionary neighborLookup;
public int[][] commonVertices;
public int commonVertexCount;
}
const float SMOOTH_STRENGTH_MODIFIER = .1f;
[UserSetting]
internal static Pref s_SmoothDirection = new Pref("Brush.Direction", PolyDirection.VertexNormal, SettingsScope.Project);
///
/// If true vertices on the edge of a mesh will not be affected by brush strokes. It is up to inheriting
/// classes to implement this preference (use `nonManifoldIndices` HashSet to check if a vertex index is
/// non-manifold).
///
[UserSetting]
internal static Pref s_IgnoreOpenEdges = new Pref("SmoothBrush.IgnoreOpenEdges", true, SettingsScope.Project);
[UserSetting]
internal static Pref s_UseFirstNormalVector = new Pref("SmoothBrush.UseFirstNormalVector", false, SettingsScope.Project);
Dictionary m_EditableObjectsData = new Dictionary();
internal override string UndoMessage { get { return "Smooth Vertices"; } }
protected override string ModeSettingsHeader { get { return "Smooth Settings"; } }
protected override string DocsLink { get { return PrefUtility.documentationSmoothBrushLink; } }
internal override void DrawGUI(BrushSettings settings)
{
base.DrawGUI(settings);
EditorGUI.BeginChangeCheck();
s_IgnoreOpenEdges.value = PolyGUILayout.Toggle(BrushModeSculpt.Styles.gcIgnoreOpenEdges, s_IgnoreOpenEdges);
if (s_SmoothDirection == PolyDirection.BrushNormal)
s_UseFirstNormalVector.value = PolyGUILayout.Toggle(BrushModeSculpt.Styles.gcBrushNormalIsSticky, s_UseFirstNormalVector);
s_SmoothDirection.value = (PolyDirection)PolyGUILayout.PopupFieldWithTitle(BrushModeSculpt.Styles.gcDirection,
(int)s_SmoothDirection.value, BrushModeSculpt.Styles.s_BrushDirectionList);
if (EditorGUI.EndChangeCheck())
PolybrushSettings.Save();
}
internal override void OnBrushEnter(EditableObject target, BrushSettings settings)
{
base.OnBrushEnter(target, settings);
if (!m_LikelyToSupportVertexSculpt)
return;
EditableObjectData data;
if(!m_EditableObjectsData.TryGetValue(target, out data))
{
data = new EditableObjectData();
m_EditableObjectsData.Add(target, data);
}
data.vertices = target.editMesh.vertices;
data.neighborLookup = PolyMeshUtility.GetAdjacentVertices(target.editMesh);
data.commonVertices = PolyMeshUtility.GetCommonVertices(target.editMesh);
data.commonVertexCount = data.commonVertices.Length;
}
// Called when the mouse exits hovering an editable object.
internal override void OnBrushExit(EditableObject target)
{
base.OnBrushExit(target);
if(m_EditableObjectsData.ContainsKey(target))
m_EditableObjectsData.Remove(target);
}
internal override void OnBrushApply(BrushTarget target, BrushSettings settings)
{
if (!m_LikelyToSupportVertexSculpt)
return;
int rayCount = target.raycastHits.Count;
Vector3 v, t, avg, dirVec = s_SmoothDirection.value.ToVector3();
Plane plane = new Plane(Vector3.up, Vector3.zero);
PolyMesh mesh = target.editableObject.editMesh;
Vector3[] normals = (s_SmoothDirection == PolyDirection.BrushNormal) ? mesh.normals : null;
int vertexCount = mesh.vertexCount;
List brushNormalOnBeginApply= BrushNormalsOnBeginApply(target.editableObject);
var data = m_EditableObjectsData[target.editableObject];
// don't use target.GetAllWeights because brush normal needs
// to know which ray to use for normal
for(int ri = 0; ri < rayCount; ri++)
{
PolyRaycastHit hit = target.raycastHits[ri];
if(hit.weights == null || hit.weights.Length < vertexCount)
continue;
for (int i = 0; i < data.commonVertexCount; i++)
{
int index = data.commonVertices[i][0];
if (hit.weights[index] < .0001f || (s_IgnoreOpenEdges && ContainsIndexInNonManifoldIndices(target.editableObject,index)))
continue;
v = data.vertices[index];
if (s_SmoothDirection == PolyDirection.VertexNormal)
{
avg = Math.Average(data.vertices, data.neighborLookup[index]);
}
else
{
avg = Math.WeightedAverage(data.vertices, data.neighborLookup[index], hit.weights);
if (s_SmoothDirection == PolyDirection.BrushNormal)
{
if (s_UseFirstNormalVector)
dirVec = brushNormalOnBeginApply[ri].normalized;
else
dirVec = Math.WeightedAverage(normals, data.neighborLookup[index], hit.weights).normalized;
}
plane.SetNormalAndPosition(dirVec, avg);
avg = v - dirVec * plane.GetDistanceToPoint(v);
}
t = Vector3.Lerp(v, avg, hit.weights[index]);
int[] indices = data.commonVertices[i];
Vector3 pos = v + (t-v) * settings.strength * SMOOTH_STRENGTH_MODIFIER;
for(int n = 0; n < indices.Length; n++)
data.vertices[indices[n]] = pos;
}
}
mesh.vertices = data.vertices;
base.OnBrushApply(target, settings);
UpdateWireframe(target, settings);
}
///
/// Draw gizmos taking into account handling of normal by smooth brush mode.
///
/// Current target Object
///// Current brush settings
internal override void DrawGizmos(BrushTarget target, BrushSettings settings)
{
UpdateBrushGizmosColor();
Vector3 normal = s_SmoothDirection.value.ToVector3();
int rayCount = target.raycastHits.Count;
PolyMesh mesh = target.editableObject.editMesh;
int vertexCount = mesh.vertexCount;
Vector3[] normals = (s_SmoothDirection == PolyDirection.BrushNormal) ? mesh.normals : null;
List brushNormalOnBeginApply = BrushNormalsOnBeginApply(target.editableObject);
var data = m_EditableObjectsData[target.editableObject];
// don't use target.GetAllWeights because brush normal needs
// to know which ray to use for normal
for (int ri = 0; ri < rayCount; ri++)
{
PolyRaycastHit hit = target.raycastHits[ri];
if (hit.weights == null || hit.weights.Length < vertexCount)
continue;
if (s_SmoothDirection == PolyDirection.BrushNormal)
{
if (s_UseFirstNormalVector && brushNormalOnBeginApply.Count > ri)
{
normal = brushNormalOnBeginApply[ri];
}
else
{
// get the highest weighted vertex to use its direction computation
float highestWeight = .0001f;
int highestIndex = -1;
for (int i = 0; i < data.commonVertexCount; i++)
{
int index = data.commonVertices[i][0];
if (hit.weights[index] < .0001f || (s_IgnoreOpenEdges && ContainsIndexInNonManifoldIndices(target.editableObject, index)))
continue;
if (hit.weights[index] > highestWeight)
highestIndex = index;
}
normal = highestIndex != -1 ?
Math.WeightedAverage(normals, data.neighborLookup[highestIndex], hit.weights).normalized :
hit.normal;
}
}
else if (s_SmoothDirection == PolyDirection.VertexNormal)
{
normal = hit.normal;
}
PolyHandles.DrawBrush(hit.position,
normal,
settings,
target.localToWorldMatrix,
innerColor,
outerColor);
}
}
}
}