/* 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,
};
}
}
}