using UnityEngine; using UnityEditor; using System.Collections.Generic; namespace FIMSpace.Generating { public class MeshPaintHelper { public Transform parent; public Mesh mesh; public List colors; public List verts; public Vector3[] worldPosVerts; public Dictionary clusterMap; public bool ClustersWasGenerated { get; private set; } float padding = 1f; #region Caching meshes static Dictionary cache = null; static double cacheTime = -1; public static void PrepareCache() { double currentTime = Time.unscaledTime; #if UNITY_EDITOR if (Application.isPlaying == false) currentTime = EditorApplication.timeSinceStartup; #endif if (cacheTime == currentTime) return; cacheTime = currentTime; cache = new Dictionary(); } /// Using caching feature public static MeshPaintHelper GetMeshHelper(Transform parent, Mesh m) { if (cache != null) if (cache.ContainsKey(m)) return cache[m]; MeshPaintHelper mesh = new MeshPaintHelper(parent, m); if (cache != null) cache.Add(m, mesh); return mesh; } #endregion public MeshPaintHelper(Transform parent, Mesh mesh) { ClustersWasGenerated = false; this.parent = parent; this.mesh = mesh; colors = new List(); mesh.GetColors(colors); verts = new List(); mesh.GetVertices(verts); worldPosVerts = new Vector3[(int)verts.Count]; if (parent != null) { for (int v = 0; v < verts.Count; v++) worldPosVerts[v] = parent.TransformPoint(verts[v]); } else { for (int v = 0; v < verts.Count; v++) worldPosVerts[v] = (verts[v]); } } public bool IsValid { get { if (mesh == null) return false; if (colors == null) return false; if (verts == null) return false; if (verts.Count == 0) return false; if (verts.Count != colors.Count) return false; return true; } } //public struct VertexHelper //{ // public Vector3 worldPos; // public Vector3 vertPos; // public Vector3 freeDir; // public float collisionDistance; // public int nearest0; // public int nearest1; // public int nearest2; // public int nearest3; // public int nearest4; // public VertexHelper(Vector3 vPos, Vector3 wPos) // { // vertPos = vPos; // worldPos = wPos; // freeDir = Vector3.zero; // collisionDistance = 0f; // nearest0 = -1; // nearest1 = -1; // nearest2 = -1; // nearest3 = -1; // nearest4 = -1; // } //} public class VertexCluster { public Vector3Int clusterID; bool boundsSet; public Bounds clusterBounds; public Bounds neightbourCoverBounds; public List neightCoverVertsIn; public List vertsIn; public VertexCluster(Vector3Int id) { clusterID = id; boundsSet = false; clusterBounds = new Bounds(Vector3.zero, Vector3.zero); vertsIn = new List(); neightCoverVertsIn = new List(); boundsSet = false; } public void AddVertex(int i, Vector3 wPos) { vertsIn.Add(i); if (!boundsSet) { clusterBounds = new Bounds(wPos, Vector3.zero); boundsSet = true; } clusterBounds.Encapsulate(wPos); } } VertexCluster GetCluster(Vector3Int key) { VertexCluster get; if (clusterMap.TryGetValue(key, out get)) return get; get = new VertexCluster(key); clusterMap.Add(key, get); return get; } // Precomputing vertex structure to help out edges painting for each vertex public void GeneratePaintingGrid(float clusterSize = 0f) { Vector3 lossyScale = Vector3.one; if (parent != null) lossyScale = parent.lossyScale; if (clusterSize < 0.01f) { Vector3 wBounds = Vector3.Scale(lossyScale, mesh.bounds.size); padding = ((wBounds.x + wBounds.y + wBounds.z) / 3f) * 0.75f; } else { padding = clusterSize; } clusterMap = new Dictionary(); for (int v = 0; v < verts.Count; v++) { Vector3 wPos = worldPosVerts[v]; Vector3Int key = V3toV3Int(RoundValueTo(wPos, padding)); VertexCluster cluster = GetCluster(key); cluster.AddVertex(v, wPos); } float sqrtPadding = padding * padding * 0.5f; foreach (KeyValuePair item in clusterMap) { item.Value.neightbourCoverBounds = item.Value.clusterBounds; VertexCluster c; if (clusterMap.TryGetValue(item.Key + new Vector3Int(1, 0, 0), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(-1, 0, 0), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(0, 0, 1), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(0, 0, -1), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(0, 1, 0), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(0, -1, 0), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(1, 0, 1), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(-1, 0, 1), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(1, 0, -1), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); if (clusterMap.TryGetValue(item.Key + new Vector3Int(-1, 0, -1), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); //if (clusterMap.TryGetValue(item.Key + new Vector3Int(2, 0, 0), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); //if (clusterMap.TryGetValue(item.Key + new Vector3Int(-2, 0, 0), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); //if (clusterMap.TryGetValue(item.Key + new Vector3Int(0, 0, 2), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); //if (clusterMap.TryGetValue(item.Key + new Vector3Int(0, 0, -2), out c)) EncapsulateNeightbours(item.Value, c, sqrtPadding); Bounds fixBounds = item.Value.neightbourCoverBounds; if (fixBounds.size.x == 0f) fixBounds.size = new Vector3(padding * 0.05f, fixBounds.size.y, fixBounds.size.z); if (fixBounds.size.y == 0f) fixBounds.size = new Vector3(fixBounds.size.x, padding * 0.05f, fixBounds.size.z); if (fixBounds.size.z == 0f) fixBounds.size = new Vector3(fixBounds.size.x, fixBounds.size.y, padding * 0.05f); item.Value.neightbourCoverBounds = fixBounds; } // Debug Draw //float h = 0f; //foreach (var item in clusterMap) //{ // h += 0.135f; // if (h > 1f) h -= 1f; // Color debCol = Color.HSVToRGB(h, 0.6f, 0.6f); // FDebug.DrawBounds3D(item.Value.neightbourCoverBounds, Color.red * 1.1f, 1f); // //for (int i = 0; i < item.Value.vertsIn.Count - 1; i++) UnityEngine.Debug.DrawLine(worldPosVerts[item.Value.vertsIn[i]], worldPosVerts[item.Value.vertsIn[i + 1]], debCol, 1.01f); //} ClustersWasGenerated = true; } void EncapsulateNeightbours(VertexCluster cluster, VertexCluster other, float sqrtPadding) { for (int i = 0; i < other.vertsIn.Count; i++) { Vector3 pos = worldPosVerts[other.vertsIn[i]]; float dist = cluster.clusterBounds.SqrDistance(pos); if (dist <= sqrtPadding) { cluster.neightbourCoverBounds.Encapsulate(pos); cluster.neightCoverVertsIn.Add(other.vertsIn[i]); } } } public float GetEdgeFactor(int i, Vector3 vertWPos, bool upper, float falloff) { Vector3Int key = V3toV3Int(RoundValueTo(vertWPos, padding)); VertexCluster helperCluster; clusterMap.TryGetValue(key + (upper ? new Vector3Int(0, 1, 0) : new Vector3Int(0, -1, 0)), out helperCluster); var cluster = GetCluster(key); //if ( helperCluster == null) if (vertWPos.y > cluster.clusterBounds.max.y - cluster.clusterBounds.extents.y * 0.01f) return 1f; int highest = i; for (int c = 0; c < cluster.vertsIn.Count; c++) { int othId = cluster.vertsIn[c]; Vector3 othWpos = worldPosVerts[othId]; float dist = Vector2.Distance(new Vector2(othWpos.x, othWpos.z), new Vector2(vertWPos.x, vertWPos.z)); if (dist > padding * 0.2f) continue; if (upper) { if (othWpos.y > worldPosVerts[highest].y) highest = othId; } else if (othWpos.y < worldPosVerts[highest].y) highest = othId; } if (helperCluster != null) { for (int c = 0; c < helperCluster.vertsIn.Count; c++) { int othId = helperCluster.vertsIn[c]; Vector3 othWpos = worldPosVerts[othId]; float dist = Vector2.Distance(new Vector2(othWpos.x, othWpos.z), new Vector2(vertWPos.x, vertWPos.z)); if (dist > padding * 0.2f) continue; if (upper) { if (othWpos.y > worldPosVerts[highest].y) highest = othId; } else if (othWpos.y < worldPosVerts[highest].y) highest = othId; } } // Debug backup //if (highest != i) UnityEngine.Debug.DrawLine(vertWPos, worldPosVerts[highest], Color.green, 1.01f); float edgeDistance = 0f; if (highest != i) edgeDistance = Mathf.Abs(vertWPos.y - worldPosVerts[highest].y); //if (highest != i) edgeDistance = Vector3.Distance(vertWPos, worldPosVerts[highest]); if (falloff < 0.0001f) { if (edgeDistance < padding * 0.001f) return 1f; } return Mathf.InverseLerp(falloff, 0f, edgeDistance); // Far from edge = 0 blend, near edge = 1 } Vector3Int WPosToKey(Vector3 wpos) { return V3toV3Int(RoundValueTo(wpos, padding)); } public bool IsSideVertex(Vector3 vertWPos, float checkOffset) { if (!WorldPosContainedNotPrecise(vertWPos + new Vector3(checkOffset, 0, 0))) return true; if (!WorldPosContainedNotPrecise(vertWPos + new Vector3(-checkOffset, 0, 0))) return true; if (!WorldPosContainedNotPrecise(vertWPos + new Vector3(0, 0, checkOffset))) return true; if (!WorldPosContainedNotPrecise(vertWPos + new Vector3(0, 0, -checkOffset))) return true; if (!WorldPosContainedNotPrecise(vertWPos + new Vector3(checkOffset, 0, checkOffset))) return true; if (!WorldPosContainedNotPrecise(vertWPos + new Vector3(-checkOffset, 0, checkOffset))) return true; if (!WorldPosContainedNotPrecise(vertWPos + new Vector3(-checkOffset, 0, -checkOffset))) return true; if (!WorldPosContainedNotPrecise(vertWPos + new Vector3(checkOffset, 0, -checkOffset))) return true; return false; } bool WorldPosContainedNotPrecise(Vector3 wPos) { VertexCluster outClusterCheck; if (!clusterMap.TryGetValue(WPosToKey(wPos), out outClusterCheck)) return false; else if (outClusterCheck.neightbourCoverBounds.Contains(wPos)) { //float range = padding * padding * 1f; //for (int v = 0; v < outClusterCheck.neightCoverVertsIn.Count; v++) //{ // float dist = Vector3.SqrMagnitude(wPos - worldPosVerts[outClusterCheck.neightCoverVertsIn[v]]); // if ( dist < range) return true; //} return true; } return false; } private Vector3 RoundValueTo(Vector3 toRound, float to) { return new Vector3(RoundValueTo(toRound.x, to), RoundValueTo(toRound.y, to), RoundValueTo(toRound.z, to)); } private float RoundValueTo(float toRound, float to) { return Mathf.Round(toRound / to); } private Vector3Int V3toV3Int(Vector3 v) { return new Vector3Int(Mathf.RoundToInt(v.x), Mathf.RoundToInt(v.y), Mathf.RoundToInt(v.z)); } } }