/* 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; /// /// The scale of the roof texture's UV coordinates. /// public Vector2 roofUVScale = Vector2.one; /// /// The starting height for the building. /// public float startHeight = 0; /// /// Size of a tile texture in meters. /// public Vector2 tileSize = new Vector2(30, 30); /// /// Offset of the UV coordinates. /// 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 = float.MaxValue; float minZ = float.MaxValue; float maxX = float.MinValue; float maxZ = float.MinValue; float maxDistance = 0; int maxDistanceIndex = -1; int nextIndex = -1; for (int i = 0; i < vertices.Count; i++) { Vector3 v = vertices[i]; if (v.x < minX) minX = v.x; if (v.z < minZ) minZ = v.z; if (v.x > maxX) maxX = v.x; if (v.z > maxZ) maxZ = v.z; nextIndex = i + 1; if (nextIndex >= vertices.Count) nextIndex = 0; float distance = (v - vertices[nextIndex]).sqrMagnitude; if (distance > maxDistance) { maxDistance = distance; maxDistanceIndex = i; } } nextIndex = maxDistanceIndex + 1; if (nextIndex >= vertices.Count) nextIndex = 0; Vector3 v1 = vertices[maxDistanceIndex]; Vector3 v2 = vertices[nextIndex]; float angle = 360 - RealWorldTerrainMath.Angle2D(v1, v2); float centerX = (minX + maxX) / 2; float centerZ = (minZ + maxZ) / 2; Matrix4x4 matrix = Matrix4x4.TRS(new Vector3(centerX, 0, centerZ), Quaternion.Euler(0, -angle, 0), Vector3.one); Bounds bounds = new Bounds(); foreach (Vector3 v in vertices) { Vector3 v1t = matrix.MultiplyPoint(v); bounds.Encapsulate(v1t); } Vector2 roofScale = roofUVScale; if (roofScale.x == 0) roofScale.x = 0.001f; if (roofScale.y == 0) roofScale.y = 0.001f; uv = new List(); foreach (Vector3 v in vertices) { Vector3 v1t = matrix.MultiplyPoint(v); Vector2 uv1 = new Vector2(v1t.x / roofUVScale.x, v1t.z / roofUVScale.y); uv.Add(uv1); } } 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 = RealWorldTerrainMath.Angle2D(targetVertices[southIndex], targetVertices[nextIndex]); float angle2 = RealWorldTerrainMath.Angle2D(targetVertices[southIndex], targetVertices[prevIndex]); return angle1 < angle2; } /// /// Generates the building mesh. /// 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, }; } } }