namespace Mapbox.Unity.MeshGeneration.Modifiers { using System.Collections.Generic; using UnityEngine; using Mapbox.Unity.MeshGeneration.Data; using System; [CreateAssetMenu(menuName = "Mapbox/Modifiers/Smooth Height for Buildings Modifier")] public class ChamferHeightModifier : MeshModifier { [SerializeField] [Tooltip("Flatten top polygons to prevent unwanted slanted roofs because of the bumpy terrain")] private bool _flatTops; [SerializeField] [Tooltip("Fixed height value for ForceHeight option")] private float _height; [SerializeField] [Tooltip("Fix all features to certain height, suggested to be used for pushing roads above terrain level to prevent z-fighting.")] private bool _forceHeight; [SerializeField] [Range(0.1f,2)] [Tooltip("Chamfer width value")] private float _offset = 0.2f; public override ModifierType Type { get { return ModifierType.Preprocess; } } public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null) { if (md.Vertices.Count == 0 || feature == null || feature.Points.Count < 1) return; var minHeight = 0f; float hf = _height; if (!_forceHeight) { GetHeightData(feature, tile.TileScale, ref minHeight, ref hf); } var max = md.Vertices[0].y; var min = md.Vertices[0].y; if (_flatTops) { FlattenTops(md, minHeight, ref hf, ref max, ref min); } else { for (int i = 0; i < md.Vertices.Count; i++) { md.Vertices[i] = new Vector3(md.Vertices[i].x, md.Vertices[i].y + minHeight + hf, md.Vertices[i].z); } } var originalVertexCount = md.Vertices.Count; Chamfer(feature, md, tile); Sides(feature, md, hf, originalVertexCount); } private void Sides(VectorFeatureUnity feature, MeshData meshData, float hf, int originalVertexCount) { float d = 0f; Vector3 v1; Vector3 v2 = Mapbox.Unity.Constants.Math.Vector3Zero; int ind = 0; var wallTri = new List(); var wallUv = new List(); meshData.Vertices.Add(new Vector3(meshData.Vertices[originalVertexCount - 1].x, meshData.Vertices[originalVertexCount - 1].y - hf, meshData.Vertices[originalVertexCount - 1].z)); meshData.Tangents.Add(meshData.Tangents[originalVertexCount - 1]); wallUv.Add(new Vector2(0, -hf)); meshData.Normals.Add(meshData.Normals[originalVertexCount - 1]); for (int i = 0; i < meshData.Edges.Count; i += 2) { v1 = meshData.Vertices[meshData.Edges[i]]; v2 = meshData.Vertices[meshData.Edges[i + 1]]; ind = meshData.Vertices.Count; meshData.Vertices.Add(v1); meshData.Vertices.Add(v2); meshData.Vertices.Add(new Vector3(v1.x, v1.y - hf, v1.z)); meshData.Vertices.Add(new Vector3(v2.x, v2.y - hf, v2.z)); meshData.Normals.Add(meshData.Normals[meshData.Edges[i]]); meshData.Normals.Add(meshData.Normals[meshData.Edges[i + 1]]); meshData.Normals.Add(meshData.Normals[meshData.Edges[i]]); meshData.Normals.Add(meshData.Normals[meshData.Edges[i + 1]]); meshData.Tangents.Add(v2 - v1.normalized); meshData.Tangents.Add(v2 - v1.normalized); meshData.Tangents.Add(v2 - v1.normalized); meshData.Tangents.Add(v2 - v1.normalized); d = (v2 - v1).magnitude; wallUv.Add(new Vector2(0, 0)); wallUv.Add(new Vector2(d, 0)); wallUv.Add(new Vector2(0, -hf)); wallUv.Add(new Vector2(d, -hf)); wallTri.Add(ind); wallTri.Add(ind + 1); wallTri.Add(ind + 2); wallTri.Add(ind + 1); wallTri.Add(ind + 3); wallTri.Add(ind + 2); } meshData.Triangles.Add(wallTri); meshData.UV[0].AddRange(wallUv); } private static void FlattenTops(MeshData meshData, float minHeight, ref float hf, ref float max, ref float min) { for (int i = 0; i < meshData.Vertices.Count; i++) { if (meshData.Vertices[i].y > max) max = meshData.Vertices[i].y; else if (meshData.Vertices[i].y < min) min = meshData.Vertices[i].y; } for (int i = 0; i < meshData.Vertices.Count; i++) { meshData.Vertices[i] = new Vector3(meshData.Vertices[i].x, max + minHeight + hf, meshData.Vertices[i].z); } hf += max - min; } private static void GetHeightData(VectorFeatureUnity feature, float scale, ref float minHeight, ref float hf) { if (feature.Properties.ContainsKey("height")) { hf = Convert.ToSingle(feature.Properties["height"]); hf *= scale; if (feature.Properties.ContainsKey("min_height")) { minHeight = Convert.ToSingle(feature.Properties["min_height"]) * scale; hf -= minHeight; } } if (feature.Properties.ContainsKey("ele")) { hf = Convert.ToSingle(feature.Properties["ele"]); hf *= scale; } } public void Chamfer(VectorFeatureUnity feature, MeshData md, UnityTile tile = null) { if (md.Vertices.Count == 0 || feature.Points.Count < 1) return; List newVertices = new List(); List newUV = new List(); md.Normals.Clear(); md.Edges.Clear(); md.Tangents.Clear(); for (int t = 0; t < md.Triangles[0].Count; t++) { md.Triangles[0][t] *= 3; } var next = 0; var current = 0; var prev = 0; Vector3 v1, v2, n1, n2, pij1, pij2, pjk1, pjk2; Vector3 poi, close1, close2; var start = 0; for (int i = 0; i < feature.Points.Count; i++) { var count = feature.Points[i].Count; var cst = newVertices.Count; for (int j = 0; j < count; j++) { if (j == count - 1) { newVertices.Add(newVertices[cst]); newVertices.Add(newVertices[cst + 1]); newVertices.Add(newVertices[cst + 2]); newUV.Add(newUV[cst]); newUV.Add(newUV[cst + 1]); newUV.Add(newUV[cst + 2]); md.Normals.Add(md.Normals[cst]); md.Normals.Add(md.Normals[cst + 1]); md.Normals.Add(md.Normals[cst + 2]); md.Tangents.Add(md.Tangents[cst]); md.Tangents.Add(md.Tangents[cst + 1]); md.Tangents.Add(md.Tangents[cst + 2]); continue; } current = start + j; if (j > 0) next = start + j - 1; else next = start + j - 1 + count - 1; //another -1 as last item equals first prev = start + j + 1; v1 = new Vector3( md.Vertices[current].x - md.Vertices[next].x, 0, md.Vertices[current].z - md.Vertices[next].z); v1.Normalize(); v1 *= -_offset; n1 = new Vector3(-v1.z, 0, v1.x); pij1 = new Vector3( (float)(md.Vertices[next].x + n1.x), 0, (float)(md.Vertices[next].z + n1.z)); pij2 = new Vector3( (float)(md.Vertices[current].x + n1.x), 0, (float)(md.Vertices[current].z + n1.z)); v2 = new Vector3( md.Vertices[prev].x - md.Vertices[current].x, 0, md.Vertices[prev].z - md.Vertices[current].z); v2.Normalize(); v2 *= -_offset; n2 = new Vector3(-v2.z, 0, v2.x); pjk1 = new Vector3( (float)(md.Vertices[current].x + n2.x), 0, (float)(md.Vertices[current].z + n2.z)); pjk2 = new Vector3( (float)(md.Vertices[prev].x + n2.x), 0, (float)(md.Vertices[prev].z + n2.z)); // See where the shifted lines ij and jk intersect. bool lines_intersect, segments_intersect; FindIntersection(pij1, pij2, pjk1, pjk2, out lines_intersect, out segments_intersect, out poi, out close1, out close2); var d = Vector3.Distance(poi, pij2); if (d > 10 * _offset) { poi = new Vector3((md.Vertices[current].x + (poi - (-v1 - v2)).normalized.x), 0, (md.Vertices[current].z + (poi - (-v1 - v2)).normalized.z)); } newVertices.Add(new Vector3(poi.x, poi.y + _offset + md.Vertices[current].y, poi.z)); newVertices.Add(md.Vertices[current] + v1); newVertices.Add(md.Vertices[current] - v2); md.Normals.Add(Constants.Math.Vector3Up); md.Normals.Add(-n1); md.Normals.Add(-n2); md.Tangents.Add(v1 - v2); md.Tangents.Add(v1 - v2); md.Tangents.Add(v1 - v2); newUV.Add(md.UV[0][current]); newUV.Add(md.UV[0][current]); newUV.Add(md.UV[0][current]); md.Triangles[0].Add(3 * current); md.Triangles[0].Add(3 * current + 1); md.Triangles[0].Add(3 * current + 2); md.Edges.Add(3 * current + 2); md.Edges.Add(3 * current + 1); md.Triangles[0].Add(3 * prev); md.Triangles[0].Add(3 * current + 2); md.Triangles[0].Add(3 * prev + 1); md.Triangles[0].Add(3 * current); md.Triangles[0].Add(3 * current + 2); md.Triangles[0].Add(3 * prev); md.Edges.Add(3 * prev + 1); md.Edges.Add(3 * current + 2); } start += count; } md.Vertices = newVertices; md.UV[0] = newUV; } private List GetEnlargedPolygon(List old_points, float offset) { List enlarged_points = new List(); int num_points = old_points.Count; for (int j = 0; j < num_points; j++) { // Find the new location for point j. // Find the points before and after j. int i = (j - 1); if (i < 0) i += num_points; int k = (j + 1) % num_points; // Move the points by the offset. Vector3 v1 = new Vector3( old_points[j].x - old_points[i].x, 0, old_points[j].z - old_points[i].z); v1.Normalize(); v1 *= offset; Vector3 n1 = new Vector3(-v1.z, 0, v1.x); Vector3 pij1 = new Vector3( (float)(old_points[i].x + n1.x), 0, (float)(old_points[i].z + n1.z)); Vector3 pij2 = new Vector3( (float)(old_points[j].x + n1.x), 0, (float)(old_points[j].z + n1.z)); Vector3 v2 = new Vector3( old_points[k].x - old_points[j].x, 0, old_points[k].z - old_points[j].z); v2.Normalize(); v2 *= offset; Vector3 n2 = new Vector3(-v2.z, 0, v2.x); Vector3 pjk1 = new Vector3( (float)(old_points[j].x + n2.x), 0, (float)(old_points[j].z + n2.z)); Vector3 pjk2 = new Vector3( (float)(old_points[k].x + n2.x), 0, (float)(old_points[k].z + n2.z)); // See where the shifted lines ij and jk intersect. bool lines_intersect, segments_intersect; Vector3 poi, close1, close2; FindIntersection(pij1, pij2, pjk1, pjk2, out lines_intersect, out segments_intersect, out poi, out close1, out close2); Debug.Assert(lines_intersect, "Edges " + i + "-->" + j + " and " + j + "-->" + k + " are parallel"); enlarged_points.Add(poi); } return enlarged_points; } // Find the point of intersection between // the lines p1 --> p2 and p3 --> p4. private void FindIntersection( Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, out bool lines_intersect, out bool segments_intersect, out Vector3 intersection, out Vector3 close_p1, out Vector3 close_p2) { // Get the segments' parameters. float dx12 = p2.x - p1.x; float dy12 = p2.z - p1.z; float dx34 = p4.x - p3.x; float dy34 = p4.z - p3.z; // Solve for t1 and t2 float denominator = (dy12 * dx34 - dx12 * dy34); float t1 = ((p1.x - p3.x) * dy34 + (p3.z - p1.z) * dx34) / denominator; if (float.IsInfinity(t1)) { // The lines are parallel (or close enough to it). lines_intersect = false; segments_intersect = false; intersection = new Vector3(float.NaN, 0, float.NaN); close_p1 = new Vector3(float.NaN, 0, float.NaN); close_p2 = new Vector3(float.NaN, 0, float.NaN); return; } lines_intersect = true; float t2 = ((p3.x - p1.x) * dy12 + (p1.z - p3.z) * dx12) / -denominator; // Find the point of intersection. intersection = new Vector3(p1.x + dx12 * t1, 0, p1.z + dy12 * t1); // The segments intersect if t1 and t2 are between 0 and 1. segments_intersect = ((t1 >= 0) && (t1 <= 1) && (t2 >= 0) && (t2 <= 1)); // Find the closest points on the segments. if (t1 < 0) { t1 = 0; } else if (t1 > 1) { t1 = 1; } if (t2 < 0) { t2 = 0; } else if (t2 > 1) { t2 = 1; } close_p1 = new Vector3(p1.x + dx12 * t1, 0, p1.z + dy12 * t1); close_p2 = new Vector3(p3.x + dx34 * t2, 0, p3.z + dy34 * t2); } } }