/* INFINITY CODE */ /* https://infinity-code.com */ using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace InfinityCode.RealWorldTerrain { /// /// This class contains basic information about the building. /// [AddComponentMenu("")] [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class RealWorldTerrainBuilding : MonoBehaviour { /// /// The height of the walls. /// public float baseHeight; /// /// Array of base vertices. /// public Vector3[] baseVertices; /// /// Reference to RealWorldTerrainContainer instance. /// public RealWorldTerrainContainer container; /// /// ID of the building /// public string id; /// /// Indicates that roof normals is inverted. /// public bool invertRoof; /// /// Indicates that walls normals is inverted. /// public bool invertWall; /// /// Height of roof. /// public float roofHeight; /// /// Type of roof. /// public RealWorldTerrainRoofType roofType; /// /// Whether to generate the wall? /// public bool generateWall; /// /// Material of the roof. /// public Material roofMaterial; public float startHeight = 0; /// /// Size of a tile texture in meters. /// public Vector2 tileSize = new Vector2(30, 30); public Vector2 uvOffset = Vector2.zero; /// /// Material of the wall. /// public Material wallMaterial; private MeshFilter _meshFilter; /// /// Reference to MeshFilter of the building. /// public MeshFilter meshFilter { get { if (_meshFilter == null) _meshFilter = GetComponent(); return _meshFilter; } } /// /// Reference to MeshFilter of roof. /// [Obsolete("Use meshFilter instead.")] public MeshFilter roof { get { return meshFilter; } } /// /// Reference to MeshFilter of wall. /// [Obsolete("Use meshFilter instead.")] public MeshFilter wall { get { return meshFilter; } } private void CreateRoofDome(List vertices, List triangles) { Vector3 roofTopPoint = Vector3.zero; roofTopPoint = vertices.Aggregate(roofTopPoint, (current, point) => current + point) / vertices.Count; roofTopPoint.y = (baseHeight + roofHeight) * container.scale.y; int vIndex = vertices.Count; for (int i = 0; i < vertices.Count; i++) { int p1 = i; int p2 = i + 1; if (p2 >= vertices.Count) p2 -= vertices.Count; triangles.AddRange(new[] { p1, p2, vIndex }); } vertices.Add(roofTopPoint); } private void CreateRoofMesh(List vertices, out List uv, out List triangles) { List roofPoints = CreateRoofVertices(vertices); triangles = CreateRoofTriangles(vertices, roofPoints); if (invertRoof) triangles.Reverse(); float minX = vertices.Min(p => p.x); float minZ = vertices.Min(p => p.z); float maxX = vertices.Max(p => p.x); float maxZ = vertices.Max(p => p.z); float offX = maxX - minX; float offZ = maxZ - minZ; uv = vertices.Select(v => new Vector2((v.x - minX) / offX, (v.z - minZ) / offZ)).ToList(); } private List CreateRoofTriangles(List vertices, List roofPoints) { List triangles = new List(); if (roofType == RealWorldTerrainRoofType.flat) { int[] trs = RealWorldTerrainTriangulator.Triangulate(roofPoints); if (trs != null) triangles.AddRange(trs); } else if (roofType == RealWorldTerrainRoofType.dome) { CreateRoofDome(vertices, triangles); } return triangles; } private List CreateRoofVertices(List vertices) { Vector3[] targetVertices = new Vector3[baseVertices.Length]; Array.Copy(baseVertices, targetVertices, baseVertices.Length); if (container.prefs.buildingBottomMode == RealWorldTerrainBuildingBottomMode.followTerrain) { Vector3 tp = transform.position; RealWorldTerrainItem terrainItem = container.GetItemByWorldPosition(baseVertices[0] + tp); if (terrainItem != null) { TerrainData t = terrainItem.terrainData; Vector3 offset = tp - terrainItem.transform.position; for (int i = 0; i < targetVertices.Length; i++) { Vector3 v = targetVertices[i]; Vector3 localPos = offset + v; float y = t.GetInterpolatedHeight(localPos.x / t.size.x, localPos.z / t.size.z); v.y = terrainItem.transform.position.y + y - tp.y; targetVertices[i] = v; } } } List roofPoints = new List(); float topPoint = targetVertices.Max(v => v.y) + baseHeight * container.scale.y; foreach (Vector3 p in targetVertices) { Vector3 tv = new Vector3(p.x, topPoint, p.z); Vector2 rp = new Vector2(p.x, p.z); vertices.Add(tv); roofPoints.Add(rp); } return roofPoints; } private void CreateWallMesh(List vertices, List uv, out List triangles) { List wv = new List(); List wuv = new List(); bool reversed = CreateWallVertices(wv, wuv); if (invertWall) reversed = !reversed; triangles = CreateWallTriangles(wv, vertices.Count, reversed); vertices.AddRange(wv); uv.AddRange(wuv); } private List CreateWallTriangles(List vertices, int offset, bool reversed) { List triangles = new List(); for (int i = 0; i < vertices.Count / 4; i++) { int p1 = i * 4; int p2 = i * 4 + 2; int p3 = i * 4 + 3; int p4 = i * 4 + 1; if (p2 >= vertices.Count) p2 -= vertices.Count; if (p3 >= vertices.Count) p3 -= vertices.Count; p1 += offset; p2 += offset; p3 += offset; p4 += offset; if (reversed) { triangles.AddRange(new[] { p1, p4, p3, p1, p3, p2 }); } else { triangles.AddRange(new[] { p2, p3, p1, p3, p4, p1 }); } } return triangles; } private bool CreateWallVertices(List vertices, List uv) { Vector3[] targetVertices = new Vector3[baseVertices.Length]; Array.Copy(baseVertices, targetVertices, baseVertices.Length); if (container.prefs.buildingBottomMode == RealWorldTerrainBuildingBottomMode.followTerrain) { Vector3 tp = transform.position; RealWorldTerrainItem terrainItem = container.GetItemByWorldPosition(baseVertices[0] + tp); if (terrainItem != null) { TerrainData t = terrainItem.terrainData; Vector3 offset = tp - terrainItem.transform.position; for (int i = 0; i < targetVertices.Length; i++) { Vector3 v = targetVertices[i]; Vector3 localPos = offset + v; float y = t.GetInterpolatedHeight(localPos.x / t.size.x, localPos.z / t.size.z); v.y = terrainItem.transform.position.y + y - tp.y; targetVertices[i] = v; } } } float topPoint = targetVertices.Max(v => v.y) + baseHeight * container.scale.y; float startY = startHeight * container.scale.y; float offsetY = startY < 0 ? startY : 0; for (int i = 0; i < targetVertices.Length; i++) { Vector3 p1 = targetVertices[i]; Vector3 p2 = i < targetVertices.Length - 1 ? targetVertices[i + 1] : targetVertices[0]; if (p1.y < startY) p1.y = startY; if (p2.y < startY) p2.y = startY; p1.y += offsetY; p2.y += offsetY; vertices.Add(p1); vertices.Add(new Vector3(p1.x, topPoint, p1.z)); vertices.Add(p2); vertices.Add(new Vector3(p2.x, topPoint, p2.z)); } float totalDistance = 0; float bottomPoint = float.MaxValue; for (int i = 0; i < vertices.Count / 4; i++) { int i1 = Mathf.RoundToInt(Mathf.Repeat(i * 4, vertices.Count)); int i2 = Mathf.RoundToInt(Mathf.Repeat((i + 1) * 4, vertices.Count)); Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; v1.y = v2.y = 0; totalDistance += (v1 - v2).magnitude; if (bottomPoint > targetVertices[i].y) bottomPoint = targetVertices[i].y; } Vector3 lv1 = vertices[vertices.Count - 4]; Vector3 lv2 = vertices[0]; lv1.y = lv2.y = 0; totalDistance += (lv1 - lv2).magnitude; float currentDistance = 0; float nextU = 0; float uMul = totalDistance / tileSize.x; float vMax = topPoint / tileSize.y; float vMinMul = container.scale.y * tileSize.y; for (int i = 0; i < vertices.Count / 4; i++) { int i1 = Mathf.RoundToInt(Mathf.Repeat(i * 4, vertices.Count)); int i2 = Mathf.RoundToInt(Mathf.Repeat((i + 1) * 4, vertices.Count)); float curU = nextU; uv.Add(new Vector2(curU * uMul + uvOffset.x, (vertices[i * 4].y - bottomPoint) / vMinMul + uvOffset.y)); uv.Add(new Vector2(curU * uMul + uvOffset.x, vMax + uvOffset.y)); Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; v1.y = v2.y = 0; currentDistance += (v1 - v2).magnitude; nextU = currentDistance / totalDistance; uv.Add(new Vector2(nextU * uMul + uvOffset.x, (vertices[i * 4 + 2].y - bottomPoint) / vMinMul + uvOffset.y)); uv.Add(new Vector2(nextU * uMul + uvOffset.x, vMax + uvOffset.y)); } int southIndex = -1; float southZ = float.MaxValue; for (int i = 0; i < targetVertices.Length; i++) { if (targetVertices[i].z < southZ) { southZ = targetVertices[i].z; southIndex = i; } } int prevIndex = southIndex - 1; if (prevIndex < 0) prevIndex = targetVertices.Length - 1; int nextIndex = southIndex + 1; if (nextIndex >= targetVertices.Length) nextIndex = 0; float angle1 = RealWorldTerrainUtils.Angle2D(targetVertices[southIndex], targetVertices[nextIndex]); float angle2 = RealWorldTerrainUtils.Angle2D(targetVertices[southIndex], targetVertices[prevIndex]); return angle1 < angle2; } public void Generate() { Mesh mesh; if (meshFilter.sharedMesh != null) mesh = meshFilter.sharedMesh; else { mesh = new Mesh(); mesh.name = "Building " + id; mesh.subMeshCount = 2; meshFilter.sharedMesh = mesh; } List vertices = new List(); List uv; List roofTriangles; List wallTriangles = null; CreateRoofMesh(vertices, out uv, out roofTriangles); if (generateWall) CreateWallMesh(vertices, uv, out wallTriangles); mesh.SetVertices(vertices); mesh.SetUVs(0, uv); mesh.SetTriangles(roofTriangles, 0); if (generateWall) mesh.SetTriangles(wallTriangles, 1); mesh.RecalculateNormals(); mesh.RecalculateBounds(); GetComponent().materials = new[] { roofMaterial, wallMaterial, }; } } }