using System; using System.Collections.Generic; using TriangleNet.Geometry; using TriangleNet.Meshing; using UnityEngine; using UnityEngine.Rendering; #if VEGETATION_STUDIO using AwesomeTechnologies; using AwesomeTechnologies.VegetationStudio; #endif #if VEGETATION_STUDIO_PRO using AwesomeTechnologies.VegetationSystem.Biomes; #endif #if UNITY_EDITOR using UnityEditor; #endif [RequireComponent(typeof(MeshFilter))] public class LakePolygon : MonoBehaviour { public int toolbarInt = 0; public LakePolygonProfile currentProfile; public LakePolygonProfile oldProfile; public List points = new List(); public List splinePoints = new List(); public AnimationCurve terrainCarve = new AnimationCurve(new Keyframe[] { new Keyframe(0, 0), new Keyframe(10, -2) }); public float distSmooth = 5; public float terrainSmoothMultiplier = 5; public bool overrideLakeRender = false; public float uvScale = 1; public bool receiveShadows = false; public ShadowCastingMode shadowCastingMode = ShadowCastingMode.Off; public AnimationCurve terrainPaintCarve = new AnimationCurve(new Keyframe[] { new Keyframe(0, 0), new Keyframe(1, 1) }); public int currentSplatMap = 1; public float distanceClearFoliage = 1; public float distanceClearFoliageTrees = 1; public bool mixTwoSplatMaps = false; public int secondSplatMap = 1; public bool addCliffSplatMap = false; public int cliffSplatMap = 1; public float cliffAngle = 25; public float cliffBlend = 1; public int cliffSplatMapOutside = 1; public float cliffAngleOutside = 45; public float cliffBlendOutside = 1; public bool noiseCarve = false; public float noiseMultiplierInside = 1f; public float noiseMultiplierOutside = 0.25f; public float noiseSizeX = 0.2f; public float noiseSizeZ = 0.2f; public bool noisePaint = false; public float noiseMultiplierInsidePaint = 1f; public float noiseMultiplierOutsidePaint = 0.5f; public float noiseSizeXPaint = 0.2f; public float noiseSizeZPaint = 0.2f; public float maximumTriangleSize = 50; public float traingleDensity = 0.2f; public float height = 0; public bool lockHeight = true; public float yOffset = 0; public float trianglesGenerated = 0; public float vertsGenerated = 0; public Mesh currentMesh; public MeshFilter meshfilter; public bool showVertexColors; public bool showFlowMap; public bool overrideFlowMap = false; public float automaticFlowMapScale = 0.2f; public bool noiseflowMap = false; public float noiseMultiplierflowMap = 1f; public float noiseSizeXflowMap = 0.2f; public float noiseSizeZflowMap = 0.2f; public bool drawOnMesh = false; public bool drawOnMeshFlowMap = false; public Color drawColor = Color.black; public bool drawColorR = true; public bool drawColorG = true; public bool drawColorB = true; public bool drawColorA = true; public bool drawOnMultiple = false; public float opacity = 0.1f; public float drawSize = 1f; public Material oldMaterial; public Color[] colors; public List colorsFlowMap = new List(); public float floatSpeed = 10; public float flowSpeed = 1f; public float flowDirection = 0f; public float closeDistanceSimulation = 5f; public int angleSimulation = 5; public float checkDistanceSimulation = 50; public bool removeFirstPointSimulation = true; public bool normalFromRaycast = false; public bool snapToTerrain = false; public LayerMask snapMask = 1; #if VEGETATION_STUDIO_PRO public float biomMaskResolution = 0.5f; public float vegetationBlendDistance = 1; public float vegetationMaskSize = 3; public BiomeMaskArea biomeMaskArea; public bool refreshMask = false; #endif #if VEGETATION_STUDIO public float vegetationMaskResolution = 0.5f; public float vegetationMaskPerimeter = 5; public VegetationMaskArea vegetationMaskArea; #endif public LakePolygonCarveData lakePolygonCarveData; public LakePolygonCarveData lakePolygonPaintData; public LakePolygonCarveData lakePolygonClearData; public List meshGOs = new List(); /// /// Add point at end of spline /// /// New point position public void AddPoint(Vector3 position) { points.Add(position); } /// /// Add point in the middle of the spline /// /// Point id public void AddPointAfter(int i) { Vector3 position = points[i]; if (i < points.Count - 1 && points.Count > i + 1) { Vector3 positionSecond = points[i + 1]; if (Vector3.Distance((Vector3)positionSecond, (Vector3)position) > 0) position = (position + positionSecond) * 0.5f; else position.x += 1; } else if (points.Count > 1 && i == points.Count - 1) { Vector3 positionSecond = points[i - 1]; if (Vector3.Distance((Vector3)positionSecond, (Vector3)position) > 0) position = position + (position - positionSecond); else position.x += 1; } else { position.x += 1; } points.Insert(i + 1, position); } /// /// Changes point position, if new position doesn't have width old width will be taken /// /// Point id /// New position public void ChangePointPosition(int i, Vector3 position) { points[i] = position; } /// /// Removes point in spline /// /// public void RemovePoint(int i) { if (i < points.Count) { points.RemoveAt(i); } } /// /// Removes points from point id forward /// /// Point id public void RemovePoints(int fromID = -1) { int pointsCount = points.Count - 1; for (int i = pointsCount; i > fromID; i--) { RemovePoint(i); } } void CenterPivot() { Vector3 position = transform.position; Vector3 center = Vector3.zero; for (int i = 0; i < points.Count; i++) { center += points[i]; } center /= points.Count; for (int i = 0; i < points.Count; i++) { Vector3 vec = points[i]; vec.x -= center.x; vec.y -= center.y; vec.z -= center.z; points[i] = vec; } transform.position += center; } public void GeneratePolygon(bool quick = false) { MeshRenderer meshRenderer = gameObject.GetComponent(); if (meshRenderer != null) { meshRenderer.receiveShadows = receiveShadows; meshRenderer.shadowCastingMode = shadowCastingMode; } if (lockHeight) { for (int i = 1; i < points.Count; i++) { Vector3 vec = points[i]; vec.y = points[0].y; points[i] = vec; } } if (points.Count < 3) return; CenterPivot(); splinePoints.Clear(); for (int i = 0; i < points.Count; i++) { //if ((i == 0 || i == points.Count - 2 || i == points.Count - 1) && !true) //{ // continue; //} CalculateCatmullRomSpline(i); } List verticesList = new List(); List verts = new List(); List indices = new List(); verticesList.AddRange(splinePoints.ToArray()); //Triangulator traingulator = new Triangulator(verts2d.ToArray()); // indices.AddRange(traingulator.Triangulate()); Polygon polygon = new Polygon(); List vertexs = new List(); for (int i = 0; i < verticesList.Count; i++) { Vertex vert = new Vertex(verticesList[i].x, verticesList[i].z); vert.z = verticesList[i].y; vertexs.Add(vert); } polygon.Add(new Contour(vertexs)); var options = new ConstraintOptions() { ConformingDelaunay = true }; var quality = new QualityOptions() { MinimumAngle = 90, MaximumArea = maximumTriangleSize }; TriangleNet.Mesh mesh = (TriangleNet.Mesh)polygon.Triangulate(options, quality); polygon.Triangulate(options, quality); indices.Clear(); foreach (var triangle in mesh.triangles) { Vertex vertex = mesh.vertices[triangle.vertices[2].id]; Vector3 v0 = new Vector3((float)vertex.x, (float)vertex.z, (float)vertex.y); vertex = mesh.vertices[triangle.vertices[1].id]; Vector3 v1 = new Vector3((float)vertex.x, (float)vertex.z, (float)vertex.y); vertex = mesh.vertices[triangle.vertices[0].id]; Vector3 v2 = new Vector3((float)vertex.x, (float)vertex.z, (float)vertex.y); indices.Add(verts.Count); indices.Add(verts.Count + 1); indices.Add(verts.Count + 2); verts.Add(v0); verts.Add(v1); verts.Add(v2); } RaycastHit hit; Vector3[] vertices = verts.ToArray(); int vertCount = vertices.Length; Vector3[] normals = new Vector3[vertCount]; Vector2[] uvs = new Vector2[vertCount]; colors = new Color[vertCount]; // for (int i = 0; i < vertCount; i++) { if (Physics.Raycast(vertices[i] + transform.position + Vector3.up * 10, Vector3.down, out hit, 1000, snapMask.value)) { if (snapToTerrain) { vertices[i] = hit.point - transform.position + new Vector3(0, 0.1f, 0); } } vertices[i].y += yOffset; if (normalFromRaycast) normals[i] = hit.normal; else normals[i] = Vector3.up; uvs[i] = new Vector2(vertices[i].x, vertices[i].z) * 0.01f * uvScale; colors[i] = Color.black; } //if (colorsFlowMap.Count != vertCount) // colorsFlowMap.Clear(); if (overrideFlowMap || quick) { while (colorsFlowMap.Count < vertCount) { colorsFlowMap.Add(new Vector2(0, 1)); } while (colorsFlowMap.Count > vertCount) { colorsFlowMap.RemoveAt(colorsFlowMap.Count - 1); } } else { List lines = new List(); List vert2 = new List(); for (int i = 0; i < splinePoints.Count; i++) { lines.Add(new Vector2(splinePoints[i].x, splinePoints[i].z)); } for (int i = 0; i < vertices.Length; i++) { vert2.Add(new Vector2(vertices[i].x, vertices[i].z)); } colorsFlowMap.Clear(); Vector2 flow = Vector2.zero; for (int i = 0; i < vertCount; i++) { float minDist = float.MaxValue; Vector2 minPoint = vert2[i]; for (int k = 0; k < splinePoints.Count; k++) { int idOne = k; int idTwo = (k + 1) % lines.Count; Vector2 point; float dist = DistancePointLine(vert2[i], lines[idOne], lines[idTwo], out point); if (minDist > dist) { minDist = dist; minPoint = point; } } flow = minPoint - vert2[i]; flow = -flow.normalized * (automaticFlowMapScale + (noiseflowMap ? Mathf.PerlinNoise(vert2[i].x * noiseSizeXflowMap, vert2[i].y * noiseSizeZflowMap) * noiseMultiplierflowMap - noiseMultiplierflowMap * 0.5f : 0)); colorsFlowMap.Add(flow); } } //Debug.Log("poly"); currentMesh = new Mesh(); vertsGenerated = vertCount; if (vertCount > 65000) { currentMesh.indexFormat = IndexFormat.UInt32; } currentMesh.vertices = vertices; currentMesh.subMeshCount = 1; currentMesh.SetTriangles(indices, 0); currentMesh.uv = uvs; currentMesh.uv4 = colorsFlowMap.ToArray(); currentMesh.normals = normals; currentMesh.colors = colors; currentMesh.RecalculateTangents(); currentMesh.RecalculateBounds(); currentMesh.RecalculateTangents(); currentMesh.RecalculateBounds(); trianglesGenerated = indices.Count / 3; meshfilter = GetComponent(); meshfilter.sharedMesh = currentMesh; MeshCollider meshCollider = GetComponent(); if (meshCollider != null) { meshCollider.sharedMesh = currentMesh; } } public static LakePolygon CreatePolygon(Material material, List positions = null) { GameObject gameobject = new GameObject("Lake Polygon"); gameobject.layer = LayerMask.NameToLayer("Water"); LakePolygon polygon = gameobject.AddComponent(); MeshRenderer meshRenderer = gameobject.AddComponent(); meshRenderer.receiveShadows = false; meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; if (material != null) meshRenderer.sharedMaterial = material; if (positions != null) for (int i = 0; i < positions.Count; i++) { polygon.AddPoint(positions[i]); } return polygon; } void CalculateCatmullRomSpline(int pos) { Vector3 p0 = points[ClampListPos(pos - 1)]; Vector3 p1 = points[pos]; Vector3 p2 = points[ClampListPos(pos + 1)]; Vector3 p3 = points[ClampListPos(pos + 2)]; int loops = Mathf.FloorToInt(1f / traingleDensity); for (int i = 1; i <= loops; i++) { float t = i * traingleDensity; splinePoints.Add(GetCatmullRomPosition(t, p0, p1, p2, p3)); } } public int ClampListPos(int pos) { if (pos < 0) { pos = points.Count - 1; } if (pos > points.Count) { pos = 1; } else if (pos > points.Count - 1) { pos = 0; } return pos; } Vector3 GetCatmullRomPosition(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3) { //The coefficients of the cubic polynomial (except the 0.5f * which I added later for performance) Vector3 a = 2f * p1; Vector3 b = p2 - p0; Vector3 c = 2f * p0 - 5f * p1 + 4f * p2 - p3; Vector3 d = -p0 + 3f * p1 - 3f * p2 + p3; //The cubic polynomial: a + b * t + c * t^2 + d * t^3 Vector3 pos = 0.5f * (a + (b * t) + (c * t * t) + (d * t * t * t)); return pos; } public float DistancePointLine(Vector2 point, Vector2 lineStart, Vector2 lineEnd, out Vector2 pointProject) { pointProject = ProjectPointLine(point, lineStart, lineEnd); return Vector2.Distance(pointProject, point); } public Vector2 ProjectPointLine(Vector2 point, Vector2 lineStart, Vector2 lineEnd) { Vector2 rhs = point - lineStart; Vector2 vector2 = lineEnd - lineStart; float magnitude = vector2.magnitude; Vector2 lhs = vector2; if (magnitude > 1E-06f) { lhs = (Vector2)(lhs / magnitude); } float num2 = Mathf.Clamp(Vector2.Dot(lhs, rhs), 0f, magnitude); return (lineStart + ((Vector2)(lhs * num2))); } #region terrainFunctions public void TerrainCarve(bool terrainShow = false) { Terrain[] terrains = Terrain.activeTerrains; Physics.autoSyncTransforms = false; if (meshGOs != null && meshGOs.Count > 0) { foreach (var item in meshGOs) { DestroyImmediate(item); } meshGOs.Clear(); } foreach (var terrain in terrains) { TerrainData terrainData = terrain.terrainData; float[,] heightmapData; float polygonHeight = transform.position.y; float posY = terrain.transform.position.y; float sizeX = terrain.terrainData.size.x; float sizeY = terrain.terrainData.size.y; float sizeZ = terrain.terrainData.size.z; if (lakePolygonCarveData == null || distSmooth != lakePolygonCarveData.distSmooth) { #if UNITY_EDITOR Undo.RegisterCompleteObjectUndo(terrainData, "Lake curve"); #endif float minX = float.MaxValue; float maxX = float.MinValue; float minZ = float.MaxValue; float maxZ = float.MinValue; for (int i = 0; i < splinePoints.Count; i++) { Vector3 point = transform.TransformPoint(splinePoints[i]); if (minX > point.x) minX = point.x; if (maxX < point.x) maxX = point.x; if (minZ > point.z) minZ = point.z; if (maxZ < point.z) maxZ = point.z; } //Debug.DrawLine(new Vector3(minX, 0, minZ), new Vector3(minX, 0, minZ) + Vector3.up * 100, Color.green, 3); // Debug.DrawLine(new Vector3(maxX, 0, maxZ), new Vector3(maxX, 0, maxZ) + Vector3.up * 100, Color.blue, 3); float terrainTowidth = (1 / sizeZ * (terrainData.heightmapResolution - 1)); float terrainToheight = (1 / sizeX * (terrainData.heightmapResolution - 1)); minX -= terrain.transform.position.x + distSmooth; maxX -= terrain.transform.position.x - distSmooth; minZ -= terrain.transform.position.z + distSmooth; maxZ -= terrain.transform.position.z - distSmooth; minX = minX * terrainToheight; maxX = maxX * terrainToheight; minZ = minZ * terrainTowidth; maxZ = maxZ * terrainTowidth; minX = (int)Mathf.Clamp(minX, 0, (terrainData.heightmapResolution)); maxX = (int)Mathf.Clamp(maxX, 0, (terrainData.heightmapResolution)); minZ = (int)Mathf.Clamp(minZ, 0, (terrainData.heightmapResolution)); maxZ = (int)Mathf.Clamp(maxZ, 0, (terrainData.heightmapResolution)); heightmapData = terrainData.GetHeights((int)minX, (int)minZ, (int)(maxX - minX), (int)(maxZ - minZ)); Vector4[,] distances = new Vector4[heightmapData.GetLength(0), heightmapData.GetLength(1)]; MeshCollider meshCollider = gameObject.AddComponent(); Transform transformTerrain = terrain.transform; Vector3 position = Vector3.zero; position.y = polygonHeight; for (int x = 0; x < heightmapData.GetLength(0); x++) { for (int z = 0; z < heightmapData.GetLength(1); z++) { position.x = (z + minX) / (float)terrainToheight + transformTerrain.position.x;//, polygonHeight position.z = (x + minZ) / (float)terrainTowidth + transformTerrain.position.z; Ray ray = new Ray(position + Vector3.up * 1000, Vector3.down); RaycastHit hit; if (meshCollider.Raycast(ray, out hit, 10000)) { // Debug.DrawLine(hit.point, hit.point + Vector3.up * 30, Color.green, 3); float minDist = float.MaxValue; for (int i = 0; i < splinePoints.Count; i++) { int idOne = i; int idTwo = (i + 1) % splinePoints.Count; float dist = DistancePointLine(hit.point, transform.TransformPoint(splinePoints[idOne]), transform.TransformPoint(splinePoints[idTwo])); if (minDist > dist) minDist = dist; } distances[x, z] = new Vector3(hit.point.x, minDist, hit.point.z); } else { float minDist = float.MaxValue; for (int i = 0; i < splinePoints.Count; i++) { int idOne = i; int idTwo = (i + 1) % splinePoints.Count; float dist = DistancePointLine(position, transform.TransformPoint(splinePoints[idOne]), transform.TransformPoint(splinePoints[idTwo])); if (minDist > dist) minDist = dist; } distances[x, z] = new Vector3(position.x, -minDist, position.z); } } } //Debug.DrawRay(new Vector3((minX) / (float)terrainToheight, polygonHeight, (minZ) / (float)terrainTowidth), Vector3.up * 30, Color.black, 3); //Debug.DrawRay(new Vector3((maxX) / (float)terrainToheight, polygonHeight, (maxZ) / (float)terrainTowidth), Vector3.up * 30, Color.black, 3); DestroyImmediate(meshCollider); lakePolygonCarveData = new LakePolygonCarveData(); lakePolygonCarveData.minX = minX; lakePolygonCarveData.maxX = maxX; lakePolygonCarveData.minZ = minZ; lakePolygonCarveData.maxZ = maxZ; lakePolygonCarveData.distances = distances; } heightmapData = terrainData.GetHeights((int)lakePolygonCarveData.minX, (int)lakePolygonCarveData.minZ, (int)(lakePolygonCarveData.maxX - lakePolygonCarveData.minX), (int)(lakePolygonCarveData.maxZ - lakePolygonCarveData.minZ)); float noise = 0; List> positionArray = new List>(); for (int x = 0; x < heightmapData.GetLength(0); x++) { List positionArrayRow = new List(); for (int z = 0; z < heightmapData.GetLength(1); z++) { Vector3 distance = lakePolygonCarveData.distances[x, z]; if (distance.y > 0) { if (noiseCarve) noise = Mathf.PerlinNoise(x * noiseSizeX, z * noiseSizeZ) * noiseMultiplierInside - noiseMultiplierInside * 0.5f; else noise = 0; float minDist = distance.y; heightmapData[x, z] = (noise + polygonHeight + terrainCarve.Evaluate(minDist) - posY) / (float)sizeY; positionArrayRow.Add(new Vector4(distance.x, heightmapData[x, z] * sizeY + posY, distance.z, 1)); } else if (-distance.y <= distSmooth) { if (noiseCarve) noise = Mathf.PerlinNoise(x * noiseSizeX, z * noiseSizeZ) * noiseMultiplierOutside - noiseMultiplierOutside * 0.5f; else noise = 0; float y = heightmapData[x, z] * sizeY + posY; float height = polygonHeight + terrainCarve.Evaluate(distance.y); float smoothValue = -distance.y / distSmooth; smoothValue = Mathf.Pow(smoothValue, terrainSmoothMultiplier); height = noise + Mathf.Lerp(height, y, smoothValue) - posY; heightmapData[x, z] = height / sizeY; positionArrayRow.Add(new Vector4(distance.x, heightmapData[x, z] * sizeY + posY, distance.z, Mathf.Pow(1 + distance.y / distSmooth, 0.5f))); } else { //float dist = lakePolygon.distSmooth * 0.8f; positionArrayRow.Add(new Vector4(distance.x, (heightmapData[x, z]) * sizeY + posY, distance.z, 0));// distance.z,1- Mathf.Clamp(-(dist + distance.y),0, dist) / dist)); } } positionArray.Add(positionArrayRow); } if (terrainShow) { Mesh meshTerrain = new Mesh(); meshTerrain.indexFormat = IndexFormat.UInt32; List vertices = new List(); List triangles = new List(); List colors = new List(); foreach (var positionRow in positionArray) { foreach (var vert in positionRow) { vertices.Add(vert); colors.Add(new Color(vert.w, vert.w, vert.w, vert.w)); } } for (int i = 0; i < positionArray.Count - 1; i++) { List rowPosition = positionArray[i]; for (int j = 0; j < rowPosition.Count - 1; j++) { triangles.Add(j + i * rowPosition.Count); triangles.Add(j + (i + 1) * rowPosition.Count); triangles.Add((j + 1) + i * rowPosition.Count); triangles.Add((j + 1) + i * rowPosition.Count); triangles.Add(j + (i + 1) * rowPosition.Count); triangles.Add((j + 1) + (i + 1) * rowPosition.Count); } } meshTerrain.SetVertices(vertices); meshTerrain.SetTriangles(triangles, 0); meshTerrain.SetColors(colors); meshTerrain.RecalculateNormals(); meshTerrain.RecalculateTangents(); meshTerrain.RecalculateBounds(); GameObject meshGO = new GameObject("TerrainMesh"); meshGO.transform.parent = transform; //meshGO.hideFlags = HideFlags.HideAndDontSave; meshGO.AddComponent(); MeshRenderer meshRenderer = meshGO.AddComponent(); meshRenderer.sharedMaterial = new Material(Shader.Find("Debug Terrain Carve")); meshRenderer.sharedMaterial.color = new Color(0, 0.5f, 0); meshGO.transform.position = Vector3.zero; meshGO.GetComponent().sharedMesh = meshTerrain; if (overrideLakeRender) meshGO.GetComponent().sharedMaterial.renderQueue = 5000; else meshGO.GetComponent().sharedMaterial.renderQueue = 2980; meshGOs.Add(meshGO); } else { if (meshGOs != null && meshGOs.Count > 0) { foreach (var item in meshGOs) { DestroyImmediate(item); } meshGOs.Clear(); } terrainData.SetHeights((int)lakePolygonCarveData.minX, (int)lakePolygonCarveData.minZ, heightmapData); terrain.Flush(); lakePolygonCarveData = null; } } Physics.autoSyncTransforms = true; } public void TerrainPaint(bool terrainShow = false) { Terrain[] terrains = Terrain.activeTerrains; Physics.autoSyncTransforms = false; if (meshGOs != null && meshGOs.Count > 0) { foreach (var item in meshGOs) { DestroyImmediate(item); } meshGOs.Clear(); } float distSmooth = this.distSmooth; float minKey = float.MaxValue; foreach (var key in terrainPaintCarve.keys) { if (key.time < minKey) minKey = key.time; } if (minKey < 0) distSmooth = -minKey; foreach (var terrain in terrains) { TerrainData terrainData = terrain.terrainData; float[,,] alphamapData; float polygonHeight = transform.position.y; //float posY = terrain.transform.position.y; float sizeX = terrain.terrainData.size.x; //float sizeY = terrain.terrainData.size.y; float sizeZ = terrain.terrainData.size.z; if (lakePolygonPaintData == null || distSmooth != lakePolygonPaintData.distSmooth) { #if UNITY_EDITOR Undo.RegisterCompleteObjectUndo(terrainData, "Paint lake"); Undo.RegisterCompleteObjectUndo(terrain, "Terrain draw texture"); Undo.RegisterCompleteObjectUndo(terrainData.alphamapTextures, "alpha"); #endif float minX = float.MaxValue; float maxX = float.MinValue; float minZ = float.MaxValue; float maxZ = float.MinValue; for (int i = 0; i < splinePoints.Count; i++) { Vector3 point = transform.TransformPoint(splinePoints[i]); if (minX > point.x) minX = point.x; if (maxX < point.x) maxX = point.x; if (minZ > point.z) minZ = point.z; if (maxZ < point.z) maxZ = point.z; } //Debug.DrawLine(new Vector3(minX, 0, minZ), new Vector3(minX, 0, minZ) + Vector3.up * 100, Color.green, 3); // Debug.DrawLine(new Vector3(maxX, 0, maxZ), new Vector3(maxX, 0, maxZ) + Vector3.up * 100, Color.blue, 3); float terrainTowidth = (1 / sizeZ * (terrainData.alphamapWidth - 1)); float terrainToheight = (1 / sizeX * (terrainData.alphamapHeight - 1)); Debug.Log(terrainTowidth + " " + terrainToheight); minX -= terrain.transform.position.x + distSmooth; maxX -= terrain.transform.position.x - distSmooth; minZ -= terrain.transform.position.z + distSmooth; maxZ -= terrain.transform.position.z - distSmooth; minX = minX * terrainToheight; maxX = maxX * terrainToheight; minZ = minZ * terrainTowidth; maxZ = maxZ * terrainTowidth; minX = (int)Mathf.Clamp(minX, 0, (terrainData.alphamapWidth)); maxX = (int)Mathf.Clamp(maxX, 0, (terrainData.alphamapWidth)); minZ = (int)Mathf.Clamp(minZ, 0, (terrainData.alphamapHeight)); maxZ = (int)Mathf.Clamp(maxZ, 0, (terrainData.alphamapHeight)); alphamapData = terrainData.GetAlphamaps((int)minX, (int)minZ, (int)(maxX - minX), (int)(maxZ - minZ)); Vector4[,] distances = new Vector4[alphamapData.GetLength(0), alphamapData.GetLength(1)]; MeshCollider meshCollider = gameObject.AddComponent(); Transform transformTerrain = terrain.transform; Vector3 position = Vector3.zero; position.y = polygonHeight; for (int x = 0; x < alphamapData.GetLength(0); x++) { for (int z = 0; z < alphamapData.GetLength(1); z++) { position.x = (z + minX) / (float)terrainToheight + transformTerrain.position.x;//, polygonHeight position.z = (x + minZ) / (float)terrainTowidth + transformTerrain.position.z; Ray ray = new Ray(position + Vector3.up * 1000, Vector3.down); RaycastHit hit; if (meshCollider.Raycast(ray, out hit, 10000)) { // Debug.DrawLine(hit.point, hit.point + Vector3.up * 30, Color.green, 3); float minDist = float.MaxValue; for (int i = 0; i < splinePoints.Count; i++) { int idOne = i; int idTwo = (i + 1) % splinePoints.Count; float dist = DistancePointLine(hit.point, transform.TransformPoint(splinePoints[idOne]), transform.TransformPoint(splinePoints[idTwo])); if (minDist > dist) minDist = dist; } float angle = 0; if (addCliffSplatMap) { ray = new Ray(position + Vector3.up * 1000, Vector3.down); TerrainCollider collider = terrain.GetComponent(); if (collider.Raycast(ray, out hit, 10000)) { angle = Vector3.Angle(hit.normal, Vector3.up); } } distances[x, z] = new Vector4(hit.point.x, minDist, hit.point.z, angle); } else { float minDist = float.MaxValue; for (int i = 0; i < splinePoints.Count; i++) { int idOne = i; int idTwo = (i + 1) % splinePoints.Count; float dist = DistancePointLine(position, transform.TransformPoint(splinePoints[idOne]), transform.TransformPoint(splinePoints[idTwo])); if (minDist > dist) minDist = dist; } float angle = 0; if (addCliffSplatMap) { ray = new Ray(position + Vector3.up * 1000, Vector3.down); TerrainCollider collider = terrain.GetComponent(); if (collider.Raycast(ray, out hit, 10000)) { angle = Vector3.Angle(hit.normal, Vector3.up); } } distances[x, z] = new Vector4(position.x, -minDist, position.z, angle); } } } DestroyImmediate(meshCollider); lakePolygonPaintData = new LakePolygonCarveData(); lakePolygonPaintData.minX = minX; lakePolygonPaintData.maxX = maxX; lakePolygonPaintData.minZ = minZ; lakePolygonPaintData.maxZ = maxZ; lakePolygonPaintData.distances = distances; } alphamapData = terrainData.GetAlphamaps((int)lakePolygonPaintData.minX, (int)lakePolygonPaintData.minZ, (int)(lakePolygonPaintData.maxX - lakePolygonPaintData.minX), (int)(lakePolygonPaintData.maxZ - lakePolygonPaintData.minZ)); float noise = 0; List> positionArray = new List>(); for (int x = 0; x < alphamapData.GetLength(0); x++) { List positionArrayRow = new List(); for (int z = 0; z < alphamapData.GetLength(1); z++) { Vector4 distance = lakePolygonPaintData.distances[x, z]; if ((-distance.y <= distSmooth) || (distance.y > 0)) { if (!mixTwoSplatMaps) { if (noisePaint) { if (distance.y > 0) noise = Mathf.PerlinNoise(x * noiseSizeXPaint, z * noiseSizeZPaint) * noiseMultiplierInsidePaint - noiseMultiplierInsidePaint * 0.5f; else noise = Mathf.PerlinNoise(x * noiseSizeXPaint, z * noiseSizeZPaint) * noiseMultiplierOutsidePaint - noiseMultiplierOutsidePaint * 0.5f; } else noise = 0; float oldValue = alphamapData[x, z, currentSplatMap]; alphamapData[x, z, currentSplatMap] = Mathf.Clamp01(Mathf.Lerp(alphamapData[x, z, currentSplatMap], 1, terrainPaintCarve.Evaluate(distance.y) + noise * terrainPaintCarve.Evaluate(distance.y))); for (int l = 0; l < terrainData.terrainLayers.Length; l++) { if (l != currentSplatMap) { alphamapData[x, z, l] = oldValue == 1 ? 0 : Mathf.Clamp01(alphamapData[x, z, l] * ((1 - alphamapData[x, z, currentSplatMap]) / (1 - oldValue))); } } } else { if (distance.y > 0) noise = Mathf.PerlinNoise(x * noiseSizeXPaint, z * noiseSizeZPaint) * noiseMultiplierInsidePaint - noiseMultiplierInsidePaint * 0.5f; else noise = Mathf.PerlinNoise(x * noiseSizeXPaint, z * noiseSizeZPaint) * noiseMultiplierOutsidePaint - noiseMultiplierOutsidePaint * 0.5f; float oldValue = alphamapData[x, z, currentSplatMap]; alphamapData[x, z, currentSplatMap] = Mathf.Clamp01(Mathf.Lerp(alphamapData[x, z, currentSplatMap], 1, terrainPaintCarve.Evaluate(distance.y))); for (int l = 0; l < terrainData.terrainLayers.Length; l++) { if (l != currentSplatMap) { alphamapData[x, z, l] = oldValue == 1 ? 0 : Mathf.Clamp01(alphamapData[x, z, l] * ((1 - alphamapData[x, z, currentSplatMap]) / (1 - oldValue))); } } if (noise > 0) { oldValue = alphamapData[x, z, secondSplatMap]; alphamapData[x, z, secondSplatMap] = Mathf.Clamp01(Mathf.Lerp(alphamapData[x, z, secondSplatMap], 1, noise)); for (int l = 0; l < terrainData.terrainLayers.Length; l++) { if (l != secondSplatMap) { alphamapData[x, z, l] = oldValue == 1 ? 0 : Mathf.Clamp01(alphamapData[x, z, l] * ((1 - alphamapData[x, z, secondSplatMap]) / (1 - oldValue))); } } } } if (addCliffSplatMap) { float oldValue = alphamapData[x, z, cliffSplatMap]; // Debug.Log(lakePolygon.cliffAngle + " " + distance.w); if (distance.y > 0) { if (distance.w > cliffAngle) { alphamapData[x, z, cliffSplatMap] = cliffBlend;// Mathf.Clamp01(Mathf.Lerp(alphamapData[x, z, lakePolygon.cliffSplatMap], 1, lakePolygon.terrainPaintCarve.Evaluate(distance.y))); for (int l = 0; l < terrainData.terrainLayers.Length; l++) { if (l != cliffSplatMap) { alphamapData[x, z, l] = oldValue == 1 ? 0 : Mathf.Clamp01(alphamapData[x, z, l] * ((1 - alphamapData[x, z, cliffSplatMap]) / (1 - oldValue))); } } } } else { if (distance.w > cliffAngleOutside) { alphamapData[x, z, cliffSplatMapOutside] = cliffBlendOutside;// Mathf.Clamp01(Mathf.Lerp(alphamapData[x, z, lakePolygon.cliffSplatMap], 1, lakePolygon.terrainPaintCarve.Evaluate(distance.y))); for (int l = 0; l < terrainData.terrainLayers.Length; l++) { if (l != cliffSplatMapOutside) { alphamapData[x, z, l] = oldValue == 1 ? 0 : Mathf.Clamp01(alphamapData[x, z, l] * ((1 - alphamapData[x, z, cliffSplatMapOutside]) / (1 - oldValue))); } } } } } } } } if (meshGOs != null && meshGOs.Count > 0) { foreach (var item in meshGOs) { DestroyImmediate(item); } meshGOs.Clear(); } terrainData.SetAlphamaps((int)lakePolygonPaintData.minX, (int)lakePolygonPaintData.minZ, alphamapData); terrain.Flush(); lakePolygonPaintData = null; } Physics.autoSyncTransforms = true; } public void TerrainClearTrees(bool details = true) { Terrain[] terrains = Terrain.activeTerrains; Physics.autoSyncTransforms = false; if (meshGOs != null && meshGOs.Count > 0) { foreach (var item in meshGOs) { DestroyImmediate(item); } meshGOs.Clear(); } foreach (var terrain in terrains) { TerrainData terrainData = terrain.terrainData; Transform transformTerrain = terrain.transform; float polygonHeight = transform.position.y; float posY = terrain.transform.position.y; float sizeX = terrain.terrainData.size.x; float sizeY = terrain.terrainData.size.y; float sizeZ = terrain.terrainData.size.z; int[,] detailLayer; #if UNITY_EDITOR Undo.RegisterCompleteObjectUndo(terrainData, "Paint lake"); Undo.RegisterCompleteObjectUndo(terrain, "Terrain draw texture"); #endif float minX = float.MaxValue; float maxX = float.MinValue; float minZ = float.MaxValue; float maxZ = float.MinValue; for (int i = 0; i < splinePoints.Count; i++) { Vector3 point = transform.TransformPoint(splinePoints[i]); if (minX > point.x) minX = point.x; if (maxX < point.x) maxX = point.x; if (minZ > point.z) minZ = point.z; if (maxZ < point.z) maxZ = point.z; } //Debug.DrawLine(new Vector3(minX, 0, minZ), new Vector3(minX, 0, minZ) + Vector3.up * 100, Color.green, 3); // Debug.DrawLine(new Vector3(maxX, 0, maxZ), new Vector3(maxX, 0, maxZ) + Vector3.up * 100, Color.blue, 3); float terrainTowidth = (1 / sizeZ * (terrainData.detailWidth - 1)); float terrainToheight = (1 / sizeX * (terrainData.detailHeight - 1)); minX -= terrain.transform.position.x + distanceClearFoliage; maxX -= terrain.transform.position.x - distanceClearFoliage; minZ -= terrain.transform.position.z + distanceClearFoliage; maxZ -= terrain.transform.position.z - distanceClearFoliage; minX = minX * terrainToheight; maxX = maxX * terrainToheight; minZ = minZ * terrainTowidth; maxZ = maxZ * terrainTowidth; minX = (int)Mathf.Clamp(minX, 0, (terrainData.detailWidth)); maxX = (int)Mathf.Clamp(maxX, 0, (terrainData.detailWidth)); minZ = (int)Mathf.Clamp(minZ, 0, (terrainData.detailHeight)); maxZ = (int)Mathf.Clamp(maxZ, 0, (terrainData.detailHeight)); detailLayer = terrainData.GetDetailLayer((int)minX, (int)minZ, (int)(maxX - minX), (int)(maxZ - minZ), 0); Vector4[,] distances = new Vector4[detailLayer.GetLength(0), detailLayer.GetLength(1)]; MeshCollider meshCollider = gameObject.AddComponent(); Vector3 position = Vector3.zero; position.y = polygonHeight; for (int x = 0; x < detailLayer.GetLength(0); x++) { for (int z = 0; z < detailLayer.GetLength(1); z++) { position.x = (z + minX) / (float)terrainToheight + transformTerrain.position.x;//, polygonHeight position.z = (x + minZ) / (float)terrainTowidth + transformTerrain.position.z; Ray ray = new Ray(position + Vector3.up * 1000, Vector3.down); RaycastHit hit; if (meshCollider.Raycast(ray, out hit, 10000)) { // Debug.DrawLine(hit.point, hit.point + Vector3.up * 30, Color.green, 3); float minDist = float.MaxValue; for (int i = 0; i < splinePoints.Count; i++) { int idOne = i; int idTwo = (i + 1) % splinePoints.Count; float dist = DistancePointLine(hit.point, transform.TransformPoint(splinePoints[idOne]), transform.TransformPoint(splinePoints[idTwo])); if (minDist > dist) minDist = dist; } float angle = 0; distances[x, z] = new Vector4(hit.point.x, minDist, hit.point.z, angle); } else { float minDist = float.MaxValue; for (int i = 0; i < splinePoints.Count; i++) { int idOne = i; int idTwo = (i + 1) % splinePoints.Count; float dist = DistancePointLine(position, transform.TransformPoint(splinePoints[idOne]), transform.TransformPoint(splinePoints[idTwo])); if (minDist > dist) minDist = dist; } float angle = 0; distances[x, z] = new Vector4(position.x, -minDist, position.z, angle); } } } if (!details) { List newTrees = new List(); TreeInstance[] oldTrees = terrainData.treeInstances; position.y = polygonHeight; foreach (var tree in oldTrees) { //Debug.DrawRay(new Vector3(, 0, tree.position.z * sizeZ) + terrain.transform.position, Vector3.up * 5, Color.red, 3); position.x = tree.position.x * sizeX + transformTerrain.position.x;//, polygonHeight position.z = tree.position.z * sizeZ + transformTerrain.position.z; Ray ray = new Ray(position + Vector3.up * 1000, Vector3.down); RaycastHit hit; if (!meshCollider.Raycast(ray, out hit, 10000)) { float minDist = float.MaxValue; for (int i = 0; i < splinePoints.Count; i++) { int idOne = i; int idTwo = (i + 1) % splinePoints.Count; float dist = DistancePointLine(position, transform.TransformPoint(splinePoints[idOne]), transform.TransformPoint(splinePoints[idTwo])); if (minDist > dist) minDist = dist; } if (minDist > distanceClearFoliageTrees) { newTrees.Add(tree); } } } terrainData.treeInstances = newTrees.ToArray(); DestroyImmediate(meshCollider); } lakePolygonClearData = new LakePolygonCarveData(); lakePolygonClearData.minX = minX; lakePolygonClearData.maxX = maxX; lakePolygonClearData.minZ = minZ; lakePolygonClearData.maxZ = maxZ; lakePolygonClearData.distances = distances; // terrainData.treeInstances = newTrees.ToArray(); if (details) { for (int l = 0; l < terrainData.detailPrototypes.Length; l++) { detailLayer = terrainData.GetDetailLayer((int)lakePolygonClearData.minX, (int)lakePolygonClearData.minZ, (int)(lakePolygonClearData.maxX - lakePolygonClearData.minX), (int)(lakePolygonClearData.maxZ - lakePolygonClearData.minZ), l); List> positionArray = new List>(); for (int x = 0; x < detailLayer.GetLength(0); x++) { List positionArrayRow = new List(); for (int z = 0; z < detailLayer.GetLength(1); z++) { Vector4 distance = lakePolygonClearData.distances[x, z]; if ((-distance.y <= distanceClearFoliage) || (distance.y > 0)) { float oldValue = detailLayer[x, z]; detailLayer[x, z] = 0; } } } terrainData.SetDetailLayer((int)lakePolygonClearData.minX, (int)lakePolygonClearData.minZ, l, detailLayer); } } if (meshGOs != null && meshGOs.Count > 0) { foreach (var item in meshGOs) { DestroyImmediate(item); } meshGOs.Clear(); } terrain.Flush(); lakePolygonClearData = null; } Physics.autoSyncTransforms = true; } public void Simulation() { #if UNITY_EDITOR Undo.RegisterCompleteObjectUndo(this, "Simulate lake"); Undo.RegisterCompleteObjectUndo(transform, "Simulate lake"); if (meshfilter != null) Undo.RegisterCompleteObjectUndo(meshfilter, "Simulate lake"); #endif List vectorPoints = new List(); vectorPoints.Add(transform.TransformPoint(points[0])); int iterations = 1; for (int i = 0; i < iterations; i++) { List newPoints = new List(); foreach (var vec in vectorPoints) { for (int angle = 0; angle <= 360; angle += angleSimulation) { RaycastHit hit; Ray ray = new Ray(vec, (new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0, Mathf.Sin(angle * Mathf.Deg2Rad)).normalized)); if (Physics.Raycast(ray, out hit, checkDistanceSimulation)) { //Debug.Log(hit.distance); //Debug.DrawRay(hit.point, Vector3.up * angle * 0.1f, Color.red, 1 + angle / (float)100); // Debug.DrawLine(hit.point, vec, Color.green, 1 + angle / (float)100); bool tooClose = false; Vector3 point = hit.point; foreach (var item in vectorPoints) { if (Vector3.Distance(point, item) < closeDistanceSimulation) { tooClose = true; break; } } foreach (var item in newPoints) { if (Vector3.Distance(point, item) < closeDistanceSimulation) { tooClose = true; break; } } if (!tooClose) { newPoints.Add(point + ray.direction * 0.3f); } } else { bool tooClose = false; Vector3 point = ray.origin + ray.direction * 50; foreach (var item in vectorPoints) { if (Vector3.Distance(point, item) < closeDistanceSimulation) { tooClose = true; break; } } foreach (var item in newPoints) { if (Vector3.Distance(point, item) < closeDistanceSimulation) { tooClose = true; break; } } if (!tooClose) { newPoints.Add(point); } } } } if (i == 0) vectorPoints.AddRange(newPoints); else { for (int k = 0; k < newPoints.Count; k++) { float min = float.MaxValue; int idMin = -1; Vector3 point = newPoints[k]; for (int p = 0; p < vectorPoints.Count; p++) { Vector3 posOne = vectorPoints[p]; Vector3 posTwo = vectorPoints[(p + 1) % vectorPoints.Count]; bool intersects = false; for (int f = 0; f < vectorPoints.Count; f++) { if (p != f) { Vector3 posCheckOne = vectorPoints[f]; Vector3 posCheckTwo = vectorPoints[(f + 1) % vectorPoints.Count]; if (AreLinesIntersecting(posOne, point, posCheckOne, posCheckTwo) || AreLinesIntersecting(point, posTwo, posCheckOne, posCheckTwo)) { intersects = true; break; } } } if (!intersects) { float dist = Vector3.Distance(point, posTwo); if (min > dist) { min = dist; idMin = (p + 1) % vectorPoints.Count; } //vectorPoints.Insert(p + 1, point); //break; } } if (idMin > -1) vectorPoints.Insert(idMin, point); } } if (i == 0 && removeFirstPointSimulation) vectorPoints.RemoveAt(0); } //List finalListPoints = new List(); //for (int i = 0; i < 3; i++) //{ // finalListPoints.Add(vectorPoints[i]); //} //for (int i = 3; i < vectorPoints.Count; i++) //{ // Vector3 point = vectorPoints[i]; // bool foundPosition = false; // for (int p = 0; p < finalListPoints.Count; p++) // { // Vector3 posOne = finalListPoints[p]; // Vector3 posTwo = finalListPoints[(p + 1) % finalListPoints.Count]; // bool intersects = false; // for (int f = 0; f < finalListPoints.Count; f++) // { // if (p != f) // { // Vector3 posCheckOne = finalListPoints[f]; // Vector3 posCheckTwo = finalListPoints[(f + 1) % finalListPoints.Count]; // if (AreLinesIntersecting(posOne, point, posCheckOne, posCheckTwo) || AreLinesIntersecting(point, posTwo, posCheckOne, posCheckTwo)) // { // intersects = true; // break; // } // } // } // if (!intersects) // { // finalListPoints.Insert(p + 1, point); // break; // } // } //} //for (int i = 0; i < vectorPoints.Count; i++) //{ // Debug.DrawRay(vectorPoints[i], Vector3.up * (i + 1) * 0.5f, Color.black); //} //for (int i = 0; i < vectorPoints.Count; i++) //{ // Debug.DrawLine(vectorPoints[i], vectorPoints[(i + 1) % vectorPoints.Count], Color.red, 3); //} points.Clear(); foreach (var vec in vectorPoints) { points.Add(transform.InverseTransformPoint(vec)); } GeneratePolygon(); } public static bool AreLinesIntersecting(Vector3 l1_p1, Vector3 l1_p2, Vector3 l2_p1, Vector3 l2_p2, bool shouldIncludeEndPoints = true) { //To avoid floating point precision issues we can add a small value float epsilon = 0.00001f; bool isIntersecting = false; float denominator = (l2_p2.z - l2_p1.z) * (l1_p2.x - l1_p1.x) - (l2_p2.x - l2_p1.x) * (l1_p2.z - l1_p1.z); //Make sure the denominator is > 0, if not the lines are parallel if (denominator != 0f) { float u_a = ((l2_p2.x - l2_p1.x) * (l1_p1.z - l2_p1.z) - (l2_p2.z - l2_p1.z) * (l1_p1.x - l2_p1.x)) / denominator; float u_b = ((l1_p2.x - l1_p1.x) * (l1_p1.z - l2_p1.z) - (l1_p2.z - l1_p1.z) * (l1_p1.x - l2_p1.x)) / denominator; //Are the line segments intersecting if the end points are the same if (shouldIncludeEndPoints) { //Is intersecting if u_a and u_b are between 0 and 1 or exactly 0 or 1 if (u_a >= 0f + epsilon && u_a <= 1f - epsilon && u_b >= 0f + epsilon && u_b <= 1f - epsilon) { isIntersecting = true; } } else { //Is intersecting if u_a and u_b are between 0 and 1 if (u_a > 0f + epsilon && u_a < 1f - epsilon && u_b > 0f + epsilon && u_b < 1f - epsilon) { isIntersecting = true; } } } return isIntersecting; } #endregion public static float DistancePointLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd) { return Vector3.Distance(ProjectPointLine(point, lineStart, lineEnd), point); } public static Vector3 ProjectPointLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd) { Vector3 rhs = point - lineStart; Vector3 vector2 = lineEnd - lineStart; float magnitude = vector2.magnitude; Vector3 lhs = vector2; if (magnitude > 1E-06f) { lhs = (Vector3)(lhs / magnitude); } float num2 = Mathf.Clamp(Vector3.Dot(lhs, rhs), 0f, magnitude); return (lineStart + ((Vector3)(lhs * num2))); } } public class LakePolygonCarveData { public float distSmooth = 0; public float minX = float.MaxValue; public float maxX = float.MinValue; public float minZ = float.MaxValue; public float maxZ = float.MinValue; public Vector4[,] distances; }