using System.Collections.Generic; using UMA; using UMA.CharacterSystem; using UnityEngine; using UnityEngine.SceneManagement; public class HairSmoosher : MonoBehaviour { // These are temporary until we can integrate this // into the workflow. private SlotDataAsset HairToSmoosh; private SlotDataAsset HairPlane; private SlotDataAsset HeadSlot; public bool invertX; public bool invertY; public bool invertZ; public bool invertDist; #if UNITY_EDITOR [InspectorButton("OnButtonClicked")] #endif public bool forceRebuild; public enum SmooshMode { ToCenter, Raycast, Physics }; public float SmooshDistance = 0.01f; public float OverSmoosh = 0.02f; public bool enableCaching = false; public string LastSmoosh; public SmooshMode Mode; [Range(0, 5000)] public int ShowVertex = -1; public Vector3 theShownVert = new Vector3(); public static Dictionary cachedSmooshes = new Dictionary(); /// /// Forces the character to rebuild and re-smoosh the hair. /// Ignore the zero references. It really is referenced, in the attribute above. /// private void OnButtonClicked() { DynamicCharacterAvatar dca = this.gameObject.GetComponent(); if (dca != null) { dca.GenerateNow(); } } public System.Diagnostics.Stopwatch StartTimer() { Debug.Log("Timer started at " + Time.realtimeSinceStartup + " Sec"); System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch(); st.Start(); return st; } public void StopTimer(System.Diagnostics.Stopwatch st, string Status) { st.Stop(); LastSmoosh = Status + " Completed " + st.ElapsedMilliseconds + "ms"; //Debug.Log(Status + " Completed " + st.ElapsedMilliseconds + "ms"); return; } public void BeforeBuild(UMAData umaData) { // find the Hair slot in the SlotDataList. // process all verts in the SlotDataList. // Set override vertexes. Debug.Log("Smoosher called"); SlotDataAsset hair = null; SlotDataAsset clip = HairPlane; int slotCount = umaData.GetSlotArraySize(); for (int i = 0; i < slotCount; i++) { var slot = umaData.GetSlot(i); if (slot.HasTag("SmooshTarget")) { HeadSlot = slot.asset; } if (HairToSmoosh != null && slot != null && slot.slotName == HairToSmoosh.slotName) { hair = slot.asset; } else if (slot.HasTag("smooshable")) { hair = slot.asset; } else if (slot.HasTag("smooshclip")) { clip = slot.asset; } } if (hair != null) { HairToSmoosh = hair; Debug.Log("Smooshing selected slot: " + hair.slotName); var st = StartTimer(); if (Mode == SmooshMode.Physics) { SmooshSlotPhysics(umaData, hair, clip, HeadSlot); } else { SmooshSlot(umaData, hair, clip, HeadSlot); } StopTimer(st, "Smooshing slot complete"); } } private bool PointInTriangle(Vector3 a, Vector3 b, Vector3 c, Vector3 p) { Vector3 d, e; double w1, w2; d = b - a; e = c - a; if (Mathf.Approximately(e.y, 0)) { e.y = 0.0001f; } w1 = ((e.x * (a.y - p.y)) + (e.y * (p.x - a.x))) / ((d.x * e.y) - (d.y * e.x)); w2 = (p.y - a.y - (w1 * d.y)) / e.y; return (w1 >= 0f) && (w2 >= 0.0) && ((w1 + w2) <= 1.0); } public Vector3 GetDestVert(Vector3 vertex, Vector3 center, float PlaneDist, SlotDataAsset SmooshTarget) { Vector3 result = new Vector3(); if (Mode == SmooshMode.ToCenter || HeadSlot == null) { return center; } result.Set(vertex.x, vertex.y, vertex.z); float distance = Vector3.Distance(center, vertex); SubMeshTriangles smt = SmooshTarget.meshData.submeshes[HeadSlot.subMeshIndex]; int[] tris = smt.getBaseTriangles(); Vector3[] verts = SmooshTarget.meshData.vertices; int tricount = tris.Length / 3; Plane p = new Plane(); Vector3 direction = center - vertex; direction.Normalize(); Ray r = new Ray(vertex, direction); for (int tri = 0; tri < tricount; tri++) { int baseTri = tri * 3; Vector3 v1 = verts[tris[baseTri]]; Vector3 v2 = verts[tris[baseTri + 1]]; Vector3 v3 = verts[tris[baseTri + 2]]; try { p.Set3Points(v1, v2, v3); } catch (System.Exception ex) { Debug.Log("Exception: " + ex.Message); } //Initialise the enter variable float enter = 0.0f; if (p.Raycast(r, out enter)) { //Get the point that is clicked Vector3 hitPoint = r.GetPoint(enter); if (PointInTriangle(v1, v2, v3, hitPoint)) { float vertdistance = (hitPoint - vertex).magnitude; if (vertdistance < distance) { distance = vertdistance; float newSmooshDistance = SmooshDistance; // Smooth smooshing if (distance > 0.0f) { newSmooshDistance = SmooshDistance + (SmooshDistance * (distance / OverSmoosh)); } Vector3 newLocation = hitPoint + (direction * (0 - newSmooshDistance)); result.Set(newLocation.x, newLocation.y, newLocation.z); } } } #if false Vector3 v = p.ClosestPointOnPlane(vertex); #endif } return result; } public Vector3 GetDestVertPhys(Vector3 vertex, Vector3 center, float PlaneDist, SlotDataAsset SmooshTarget, PhysicsScene ps, int vertindex) { Vector3 result = new Vector3(); if (Mode == SmooshMode.ToCenter || HeadSlot == null) { return center; } Vector3 AlternateCenter = new Vector3(center.x, center.y + 0.001f, center.z); result.Set(vertex.x, vertex.y, vertex.z); float distance = Vector3.Distance(center, vertex); Vector3 direction = (center - vertex).normalized; if (ps.Raycast(vertex, direction, out RaycastHit hit, 5.0f, -5, QueryTriggerInteraction.Ignore)) { // todo: offset by smoosh distance float vertdistance = (hit.point - vertex).magnitude; if (vertdistance < distance) { distance = vertdistance; float newSmooshDistance = SmooshDistance; // Smooth smooshing if (distance > 0.0f) { newSmooshDistance = SmooshDistance + (SmooshDistance * (distance / OverSmoosh)); } Vector3 newLocation = hit.point + (direction * (0 - newSmooshDistance)); result.Set(newLocation.x, newLocation.y, newLocation.z); } else { result.Set(hit.point.x, hit.point.y, hit.point.z); } } else { result.Set(center.x, center.y, center.z); } return result; } public void SmooshSlotPhysics(UMAData umaData, SlotDataAsset SmooshMe, SlotDataAsset SmooshPlane, SlotDataAsset SmooshTarget) { if (SmooshMe == null || SmooshPlane == null || SmooshTarget == null) { return; } Mesh m = new Mesh(); if (umaData.VertexOverrides.ContainsKey(SmooshTarget.slotName)) { m.SetVertices(umaData.VertexOverrides[SmooshTarget.slotName]); } else { m.SetVertices(SmooshTarget.meshData.vertices); } m.SetTriangles(SmooshTarget.meshData.submeshes[0].getBaseTriangles(), 0); CreateSceneParameters csp = new CreateSceneParameters(LocalPhysicsMode.Physics3D); Scene S = SceneManager.CreateScene("SmooshScene", csp); GameObject go = new GameObject(); try { var collider = go.AddComponent(); collider.sharedMesh = m; SceneManager.MoveGameObjectToScene(go, S); PhysicsScene physicsScene = S.GetPhysicsScene(); var a = SmooshPlane.meshData.vertices[0]; var b = SmooshPlane.meshData.vertices[1]; var c = SmooshPlane.meshData.vertices[2]; if (invertY) { a.y = -a.y; b.y = -b.y; c.y = -c.y; } if (invertX) { a.x = -a.x; b.x = -b.x; c.x = -c.x; } Plane p = new Plane(a, b, c); Vector3 center = (a + b + c) / 3; Vector3[] newVerts = new Vector3[SmooshMe.meshData.vertices.Length]; Vector3[] sourceVertexes = SmooshMe.meshData.vertices; if (umaData.VertexOverrides.ContainsKey(SmooshMe.slotName)) { sourceVertexes = umaData.VertexOverrides[SmooshMe.slotName]; } for (int i = 0; i < newVerts.Length; i++) { Vector3 currentVert = sourceVertexes[i]; float dist = p.GetDistanceToPoint(currentVert); if (invertDist) { dist *= -1; } if (dist > -OverSmoosh) { newVerts[i] = GetDestVertPhys(currentVert, center, dist, SmooshTarget, physicsScene, i); } else { newVerts[i] = currentVert; } } umaData.AddVertexOverride(SmooshMe, newVerts); } finally { // Cleanup SceneManager.UnloadSceneAsync(S); GameObject.Destroy(m); } } public void SmooshSlot(UMAData umaData, SlotDataAsset SmooshMe, SlotDataAsset SmooshPlane, SlotDataAsset SmooshTarget) { string key = SmooshMe.slotName + "*" + SmooshPlane.slotName; if (enableCaching) { if (cachedSmooshes.ContainsKey(key)) { Debug.Log("Used cached smoosh"); umaData.AddVertexOverride(SmooshMe, cachedSmooshes[key]); return; } } var a = SmooshPlane.meshData.vertices[0]; var b = SmooshPlane.meshData.vertices[1]; var c = SmooshPlane.meshData.vertices[2]; Vector3[] newVerts = new Vector3[SmooshMe.meshData.vertices.Length]; if (invertY) { a.y = -a.y; b.y = -b.y; c.y = -c.y; } if (invertX) { a.x = -a.x; b.x = -b.x; c.x = -c.x; } Plane p = new Plane(a, b, c); Vector3 center = (a + b + c) / 3; for (int i = 0; i < newVerts.Length; i++) { Vector3 currentVert = SmooshMe.meshData.vertices[i]; // if (invertX) currentVert.x = -currentVert.x; // if (invertY) currentVert.y = -currentVert.y; // if (invertZ) currentVert.z = -currentVert.z; float dist = p.GetDistanceToPoint(currentVert); if (invertDist) { dist *= -1; } if (dist > -OverSmoosh) { newVerts[i] = GetDestVert(currentVert, center, dist, SmooshTarget); } else { newVerts[i] = currentVert; } } umaData.AddVertexOverride(SmooshMe, newVerts); if (enableCaching) { if (cachedSmooshes.ContainsKey(key) == false) { cachedSmooshes.Add(key, newVerts); } else { cachedSmooshes[key] = newVerts; } } } }