/// <summary> /// Generates the mesh for a given openstreetmap element /// </summary> /// <param name="OSMid">The id of the OSM element</param> /// <param name="MinPointOnMap">Minimum point on the target map (to calculate the offset)</param> /// <param name="bounds">The boundaries of the used OSM map</param> /// <param name="properties">The details of the target scene generation.</param> /// <param name="connPostGreSql">The connection string to the database. Please refer to .Net Data Provider for MSSQL for format specifications</param> /// <param name="shape">The type of the generated mesh. Refer to <see cref="GaPSLabs.Geometry.MeshGenerator.OSMShape"/></param> /// <param name="RemoveRedundantPointsOnTheSameLine">If true, it will remove redundant points on straight parts of the shape according to<paramref name="RedundantPointErrorThreshold"/>.</param> /// <param name="RedundantPointErrorThreshold">The error toleration when removing redundant points. The default is 0.001f</param> /// <param name="SegmentLines">If true, it will segment the road geometry so that the texture will look uniform over the whole mesh.</param> /// <returns>Returns a <see cref="GaPSLabs.Geometry.GameObject"/> of the generated mesh.</returns> /// <remarks> /// <para>Removing the redundant points happen before the line segmentation. Therefore the points that were generated when setting <paramref name="SegmentLines"/> to true will not be removed.</para> /// <para>The <paramref name="RedundantPointErrorThreshold"/> specifies the difference in slope of the line segments that can be ignored.</para> /// </remarks> public static GameObject OSMtoGameObject(string OSMid, Vector3 MinPointOnMap, Bounds bounds, MapProperties properties, string connPostGreSql, OSMShape shape = OSMShape.Way, bool RemoveRedundantPointsOnTheSameLine = true, float RedundantPointErrorThreshold = 0.001f, bool SegmentLines = true) { if (rand == null) { rand = new Random((int)DateTime.Now.Ticks); } if (properties == null) { properties = Properties; } var minmaxX = properties.minMaxX; var minmaxY = properties.minMaxY; int direction = -1; float LineWidth = properties.RoadLineThickness; float BuildingWidth = properties.BuildingLineThickness; float height = properties.BuildingHeight; if (height < 4) { height = 4; } var FirstWay = OSMid; if (shape == OSMShape.Way) { if (FirstWay.Equals("41018641")) { System.Console.WriteLine("Missing road found in WCF"); } var w = new Way(FirstWay); List <OsmNode> WayNodes; List <Tag> WayTags; using (Npgsql.NpgsqlConnection con = new Npgsql.NpgsqlConnection(connPostGreSql)) { con.Open(); WayNodes = w.GetNodesPostgreSQL(FirstWay, con); WayTags = w.GetTagsPostgreSQL(FirstWay, con); con.Close(); } if (WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "landuse" || i.KeyValueSQL[0].ToLower() == "building" || i.KeyValueSQL[0].ToLower() == "highway").Count() != 0) { var tempPoints = new Vector3[WayNodes.Count]; int counter = 0; foreach (var node in WayNodes) { var result = CoordinateConvertor.SimpleInterpolation((float)node.PositionSQL.Lat, (float)node.PositionSQL.Lon, bounds, minmaxX, minmaxY); // Testing the correct direction tempPoints[counter] = new Vector3(direction * (float)result[0], 0, (float)result[1]) - MinPointOnMap; counter++; } if (RemoveRedundantPointsOnTheSameLine) { tempPoints = tempPoints.RemoveRedundantPointsInTheSameLines(); } WayNodes = null; var building = WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "building"); var highwayType = WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "highway"); var onewayType = WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "oneway").FirstOrDefault(); var lanesType = WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "lanes").FirstOrDefault(); var areaType = WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "area").FirstOrDefault(); WayTags = null; if (building.Count() != 0) { #region Buildings // Check if it has overlapping start and ending points. // NOTE: Replaced with the code to remove all the duplicates, not only the endpoints. // Checking for duplicates: tempPoints = tempPoints.ToArray().RemoveDuplicates(); var Skip = false; if (tempPoints.Length <= 2) { // Buildings that are too small to show such as 76844368 // http://www.openstreetmap.org/browse/way/76844368 // "A weird building were found and ignored. FirstWay \nRelated url: http://www.openstreetmap.org/browse/way/{0}" Skip = true; // continue; } if (!Skip) { var p2d = tempPoints.ToCPoint2D(); // TODO bug in the cpolygon, probably duplicates var shp = new PolygonCuttingEar.CPolygonShape(p2d); shp.CutEar(); p2d = null; GC.Collect(); // TODO: //var randHeight = CoordinateConvertor.linear((float)rand.NextDouble(), 0, 1, -3f, height); // var randMaterial = (randHeight > height / 2f) ? "BuildingTall" : randHeight < height / 2f ? "Building2" : "Building"; var randHeight = properties.BuildingHeightVariation.GetNextCircular(); var randMaterial = (randHeight > (properties.BuildingHeightVariation.Average + properties.BuildingHeightVariation.Max) / 2f) ? "BuildingTall" : ("Building" + rand.Next(1, 5)); var resultedGameObject = shp.GenerateShapeUVedWithWalls_Balanced( CoordinateConvertor.OSMType.Polygon, FirstWay, "Building", "Building", randMaterial, /*height +*/ randHeight, /* height + 7*/ properties.BuildingHeightVariation.Max, true); return(resultedGameObject); } #endregion } else { if (highwayType.Count() != 0) { #region Roads var hwtype = highwayType.First(); //Console.WriteLine("Generating roads..id=" + FirstWay); switch (hwtype.KeyValueSQL[1]) { case "cycleway": { var resultedGameObject = CoordinateConvertor.MeshGenerationFilledCorners(SegmentLines ? tempPoints.ToSegmentedPoints(2f) : tempPoints, properties.CyclewayWidth, CoordinateConvertor.OSMType.Line, FirstWay, hwtype.KeyValueSQL[1], "Line", properties.CycleWayMaterial.Name, -0.01f); // ObjFormat.MeshToFile(resultedGameObject,System.IO.Path.Combine( exportPath , resultedGameObject.Name.Replace("|", "-") + ".obj")); return(resultedGameObject); } case "footway": case "path": case "pedestrian": { if (areaType != null) { tempPoints = tempPoints.ToArray().RemoveDuplicates(); var Skip = false; if (tempPoints.Length <= 2) { // Buildings that are too small to show such as 76844368 // http://www.openstreetmap.org/browse/way/76844368 // "A weird building were found and ignored. FirstWay \nRelated url: http://www.openstreetmap.org/browse/way/{0}" Skip = true; // continue; } if (!Skip) { var p2d = tempPoints.ToCPoint2D(); // TODO bug in the cpolygon, probably duplicates var shp = new PolygonCuttingEar.CPolygonShape(p2d); shp.CutEar(); p2d = null; GC.Collect(); var resultedGameObject = shp.GenerateShapeUVedPlanar_Balanced( CoordinateConvertor.OSMType.Polygon, FirstWay, "FootwayArea", "Area", properties.AreaMaterial.Name, 0, true); return(resultedGameObject); } break; } else { var resultedGameObject = CoordinateConvertor.MeshGenerationFilledCorners(SegmentLines ? tempPoints.ToSegmentedPoints(4f) : tempPoints, properties.FootwayWidth, CoordinateConvertor.OSMType.Line, FirstWay, hwtype.KeyValueSQL[1], "Line", properties.FootWayMaterial.Name, -0.01f); // ObjFormat.MeshToFile(resultedGameObject,System.IO.Path.Combine( exportPath , resultedGameObject.Name.Replace("|", "-") + ".obj")); return(resultedGameObject); } } case "steps": { var resultedGameObject = CoordinateConvertor.MeshGenerationFilledCorners(SegmentLines ? tempPoints.ToSegmentedPoints(4f) : tempPoints, properties.CyclewayWidth, CoordinateConvertor.OSMType.Line, FirstWay, hwtype.KeyValueSQL[1], "Line", properties.StepsMaterial.Name, -0.01f); return(resultedGameObject); } // Miguel R.C. commented these lines. // Reason: Why breaking in the case "motorway"? That results in some roads missing when generating scenarios. // With these lines commented, the motorways will be generated in the default case. //case "motorway": // { // break; // } default: { // Calculating the corrent road width based on the available information // Parameters affecting the width are: // 1- road direction: 'oneway' tag // 2- number of lanes: 'lanes' tag. The defaults are: http://wiki.openstreetmap.org/wiki/Lane#Assumptions // // |If two-way then # of lanes is 2, and if one-way then # of lanes is 1| // highway=residential // highway=tertiary // highway=secondary // highway=primary // // |If two-way then # of lanes is 1, and if one-way then # of lanes is 1| // highway=service // highway=track // highway=path // The actual number of lanes for the following must always be tagged in the lanes value. // highway=motorway // highway=trunk //TODO: USE lanes value. var calculatedRoadWidth = properties.RoadWidth; bool useLaneInfo = false; if (lanesType != null) { var laneNumbers = lanesType.KeyValueSQL[1].Split(new char[] { '+' }, StringSplitOptions.RemoveEmptyEntries); float lanes = 0; foreach (var lane in laneNumbers) { float templane; if (float.TryParse(lane, out templane)) { lanes += templane; } } if (lanes != 0) { useLaneInfo = true; calculatedRoadWidth = properties.RoadWidth * lanes; } } if (!useLaneInfo) { if (onewayType == null) // It is a 2-way by default { if (hwtype.KeyValueSQL[1] == Tags.Highways.residential || hwtype.KeyValueSQL[1] == Tags.Highways.tertiary || hwtype.KeyValueSQL[1] == Tags.Highways.secondary || hwtype.KeyValueSQL[1] == Tags.Highways.primary) { calculatedRoadWidth = properties.RoadWidth * 2; } else if (hwtype.KeyValueSQL[1] == Tags.Highways.service || hwtype.KeyValueSQL[1] == Tags.Highways.track || hwtype.KeyValueSQL[1] == Tags.Highways.path) { calculatedRoadWidth = properties.RoadWidth; } } else if (onewayType.KeyValueSQL[1].ToLower() == "0" | onewayType.KeyValueSQL[1].ToLower() == "no") // It is still a 2-way { if (hwtype.KeyValueSQL[1] == Tags.Highways.residential || hwtype.KeyValueSQL[1] == Tags.Highways.tertiary || hwtype.KeyValueSQL[1] == Tags.Highways.secondary || hwtype.KeyValueSQL[1] == Tags.Highways.primary) { calculatedRoadWidth = properties.RoadWidth * 2; } else if (hwtype.KeyValueSQL[1] == Tags.Highways.service || hwtype.KeyValueSQL[1] == Tags.Highways.track || hwtype.KeyValueSQL[1] == Tags.Highways.path) { calculatedRoadWidth = properties.RoadWidth; } } else // It is a one-way { calculatedRoadWidth = properties.RoadWidth; } } var matName = properties.RoadMaterial.Name; if (hwtype.KeyValueSQL[1] == Tags.Highways.residential) { matName = properties.ResidentialMaterial.Name; } else if (hwtype.KeyValueSQL[1] == Tags.Highways.service) { matName = properties.ServiceMaterial.Name; } var resultedGameObject = CoordinateConvertor.MeshGenerationFilledCorners(SegmentLines ? tempPoints.ToSegmentedPoints(0.5f) : tempPoints, calculatedRoadWidth, CoordinateConvertor.OSMType.Line, FirstWay, hwtype.KeyValueSQL[1], "Line", matName, 0f); return(resultedGameObject); } } #endregion } } } } else if (shape == OSMShape.Relation) // It is probably a multi-polygon building defined in a relation. { List <OsmNode[]> OuterNodes; //List<OsmNode> OuterWay; List <OsmNode[]> InnerNodes; List <Tag> tags; Relation r = new Relation(FirstWay); Way w = new Way(); using (Npgsql.NpgsqlConnection con = new Npgsql.NpgsqlConnection(connPostGreSql)) { con.Open(); tags = r.GetTagsPostgreSQL(con); var isMultiPolygon = tags.Exists(t => t.KeyValueSQL[0] == "type" && t.KeyValueSQL[1] == "multipolygon"); if (isMultiPolygon) { var Members = r.GetMembersPostgreSQLByType(r.id, 1, con); OuterNodes = Members .Where(m => m.Role.ToLower() == Member.RoleOuter) .OrderBy(o => o.order) .Select(m => w.GetNodesPostgreSQL(m.ReferenceId, connPostGreSql).ToArray()).ToList(); InnerNodes = Members .Where(m => m.Role.ToLower() == Member.RoleInner) .OrderBy(o => o.order) .Select(m => w.GetNodesPostgreSQL(m.ReferenceId, connPostGreSql).ToArray()).ToList(); //OuterWay = new List<OsmNode>(); //foreach (var nodelist in OuterNodes) // OuterWay.AddRange(nodelist); List <Vector3[]> tempPointsOuterPoints = new List <Vector3[]>(); int counter = 0; foreach (var node in OuterNodes) { Vector3[] currrentOuter = new Vector3[node.Length]; for (int i = 0; i < node.Length; i++) { var result = CoordinateConvertor.SimpleInterpolation((float)node[i].PositionSQL.Lat, (float)node[i].PositionSQL.Lon, bounds, minmaxX, minmaxY); // Testing the correct direction currrentOuter[i] = new Vector3(direction * (float)result[0], 0, (float)result[1]) - MinPointOnMap; } tempPointsOuterPoints.Add(currrentOuter.RemoveDuplicates()); } List <Vector3[]> holes = new List <Vector3[]>(); for (int i = 0; i < InnerNodes.Count; i++) { holes.Add(new Vector3[InnerNodes[i].Length]); for (int j = 0; j < InnerNodes[i].Length; j++) { var result = CoordinateConvertor.SimpleInterpolation((float)InnerNodes[i][j].PositionSQL.Lat, (float)InnerNodes[i][j].PositionSQL.Lon, bounds, minmaxX, minmaxY); // Testing the correct direction holes[i][j] = new Vector3(direction * (float)result[0], 0, (float)result[1]) - MinPointOnMap; } holes[i] = holes[i].RemoveDuplicates(); } if (RemoveRedundantPointsOnTheSameLine) { for (int i = 0; i < tempPointsOuterPoints.Count; i++) { tempPointsOuterPoints[i] = tempPointsOuterPoints[i].RemoveRedundantPointsInTheSameLines(); } for (int i = 0; i < holes.Count; i++) { holes[i] = holes[i].RemoveRedundantPointsInTheSameLines(); } } // Check the holes against all outers List <IEnumerable <Poly2Tri.PolygonPoint> > outerpolypoints = new List <IEnumerable <Poly2Tri.PolygonPoint> >(); for (int i = 0; i < tempPointsOuterPoints.Count; i++) { outerpolypoints.Add(tempPointsOuterPoints[i].Select(s => new Poly2Tri.PolygonPoint(s.x, s.z))); } //= tempPointsOuterPoints var Polies = outerpolypoints.Select(opp => new Poly2Tri.Polygon(opp)).ToArray(); //Poly2Tri.Polygon poly = new Poly2Tri.Polygon(outerpolypoints); // TODO: poly.IsPointInside() check the holes against all pieces var innerspolypoints = new List <Poly2Tri.PolygonPoint[]>(); for (int i = 0; i < holes.Count; i++) { var currentInner = holes[i].Select(s => new Poly2Tri.PolygonPoint(s.x, s.z)).ToArray(); innerspolypoints.Add(currentInner); for (int j = 0; j < Polies.Length; j++) { if (Polies[j].IsPointInside(new Poly2Tri.TriangulationPoint(currentInner[0].X, currentInner[0].Y))) { Polies[j].AddHole(new Poly2Tri.Polygon(currentInner)); } } } for (int i = 0; i < Polies.Length; i++) { try { Poly2Tri.P2T.Triangulate(Polies[i]); } catch (Exception e) { //throw e; } } //var randHeight = CoordinateConvertor.linear((float)rand.NextDouble(), 0, 1, -3f, height); //var randMaterial = (randHeight > height / 2f) ? "BuildingTall" : randHeight < height / 2f ? "Building2" : "Building"; var randHeight = properties.BuildingHeightVariation.GetNextCircular(); var randMaterial = (randHeight > (properties.BuildingHeightVariation.Average + properties.BuildingHeightVariation.Max) / 2f) ? "BuildingTall" : ("Building" + rand.Next(1, 5)); List <GameObject> resultedGameObjects = new List <GameObject>(); for (int i = 0; i < Polies.Length; i++) { var currentGameObject = Polies[i].GenerateShapeUVedWithWalls_Balanced( CoordinateConvertor.OSMType.Relation, FirstWay, "Building", "Building", randMaterial, /*height +*/ randHeight, /* height + 7*/ properties.BuildingHeightVariation.Max, true); resultedGameObjects.Add(currentGameObject); } return(resultedGameObjects.CombineGameObjectsOfTheSameMaterial()); } con.Close(); } } return(null); }
public static GameObject OSMtoGameObject2(string OSMid, Vector3 MinPointOnMap, Bounds bounds, MapProperties properties, string connPostGreSql, OSMShape shape = OSMShape.Way) { if (rand == null) { rand = new Random((int)DateTime.Now.Ticks); } if (properties == null) { properties = Properties; } var minmaxX = properties.minMaxX; var minmaxY = properties.minMaxY; int direction = -1; float LineWidth = properties.RoadLineThickness; float BuildingWidth = properties.BuildingLineThickness; float height = properties.BuildingHeight; if (height < 4) { height = 4; } var FirstWay = OSMid; if (shape == OSMShape.Way) { var w = new Way(FirstWay); List <OsmNode> WayNodes; List <Tag> WayTags; using (Npgsql.NpgsqlConnection con = new Npgsql.NpgsqlConnection(connPostGreSql)) { con.Open(); WayNodes = w.GetNodesPostgreSQL(FirstWay, con); WayTags = w.GetTagsPostgreSQL(FirstWay, con); con.Close(); } if (WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "landuse" || i.KeyValueSQL[0].ToLower() == "building" || i.KeyValueSQL[0].ToLower() == "highway").Count() != 0) { var tempPoints = new Vector3[WayNodes.Count]; int counter = 0; foreach (var node in WayNodes) { var result = CoordinateConvertor.SimpleInterpolation((float)node.PositionSQL.Lat, (float)node.PositionSQL.Lon, bounds, minmaxX, minmaxY); // Testing the correct direction tempPoints[counter] = new Vector3(direction * (float)result[0], 0, (float)result[1]) - MinPointOnMap; counter++; } WayNodes = null; var building = WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "building"); var highwayType = WayTags.Where(i => i.KeyValueSQL[0].ToLower() == "highway"); WayTags = null; if (building.Count() != 0) { #region Buildings // Check if it has overlapping start and ending points. // NOTE: Replaced with the code to remove all the duplicates, not only the endpoints. // Checking for duplicates: tempPoints = tempPoints.ToArray().RemoveDuplicates(); var Skip = false; if (tempPoints.Length <= 2) { // Buildings that are too small to show such as 76844368 // http://www.openstreetmap.org/browse/way/76844368 // "A weird building were found and ignored. FirstWay \nRelated url: http://www.openstreetmap.org/browse/way/{0}" Skip = true; // continue; } if (!Skip) { var p2d = tempPoints.ToCPoint2D(); // TODO bug in the cpolygon, probably duplicates var shp = new PolygonCuttingEar.CPolygonShape(p2d); shp.CutEar(); p2d = null; GC.Collect(); // TODO: var randHeight = CoordinateConvertor.linear((float)rand.NextDouble(), 0, 1, -3f, height); var randMaterial = (randHeight > height / 2f) ? "BuildingTall" : randHeight < height / 2f ? "Building2" : "Building"; var resultedGameObject = shp.GenerateShapeUVedWithWalls_Balanced( CoordinateConvertor.OSMType.Polygon, FirstWay, "Building", "Building", randMaterial, height + randHeight, height + 7, true); return(resultedGameObject); } #endregion } else { if (highwayType.Count() != 0) { #region Roads var hwtype = highwayType.First(); //Console.WriteLine("Generating roads..id=" + FirstWay); switch (hwtype.KeyValueSQL[1]) { case "cycleway": { var resultedGameObject = CoordinateConvertor.MeshGenerationFilledCorners(tempPoints.ToSegmentedPoints(2f), properties.CyclewayWidth, CoordinateConvertor.OSMType.Line, FirstWay, hwtype.KeyValueSQL[1], "Line", properties.CycleWayMaterial.Name, -0.01f); // ObjFormat.MeshToFile(resultedGameObject,System.IO.Path.Combine( exportPath , resultedGameObject.Name.Replace("|", "-") + ".obj")); return(resultedGameObject); } case "footway": case "path": case "pedestrian": { var resultedGameObject = CoordinateConvertor.MeshGenerationFilledCorners(tempPoints.ToSegmentedPoints(4f), properties.FootwayWidth, CoordinateConvertor.OSMType.Line, FirstWay, hwtype.KeyValueSQL[1], "Line", properties.FootWayMaterial.Name, -0.01f); // ObjFormat.MeshToFile(resultedGameObject,System.IO.Path.Combine( exportPath , resultedGameObject.Name.Replace("|", "-") + ".obj")); return(resultedGameObject); } case "steps": { var resultedGameObject = CoordinateConvertor.MeshGenerationFilledCorners(tempPoints.ToSegmentedPoints(4f), properties.CyclewayWidth, CoordinateConvertor.OSMType.Line, FirstWay, hwtype.KeyValueSQL[1], "Line", properties.StepsMaterial.Name, -0.01f); return(resultedGameObject); } // Miguel R.C. commented these lines. // Reason: Why breaking in the case "motorway"? That results in some roads missing when generating scenarios. // With these lines commented, the motorways will be generated in the default case. //case "motorway": // { // break; // } default: { var resultedGameObject = CoordinateConvertor.MeshGenerationFilledCorners(tempPoints.ToSegmentedPoints(0.5f), properties.RoadWidth, CoordinateConvertor.OSMType.Line, FirstWay, hwtype.KeyValueSQL[1], "Line", properties.RoadMaterial.Name, 0f); return(resultedGameObject); } } #endregion } } } } else if (shape == OSMShape.Relation) // It is probably a multi-polygon building defined in a relation. { List <OsmNode[]> OuterNodes; List <OsmNode> OuterWay; List <OsmNode[]> InnerNodes; List <Tag> tags; Relation r = new Relation(FirstWay); Way w = new Way(); using (Npgsql.NpgsqlConnection con = new Npgsql.NpgsqlConnection(connPostGreSql)) { con.Open(); tags = r.GetTagsPostgreSQL(con); var isMultiPolygon = tags.Exists(t => t.KeyValueSQL[0] == "type" && t.KeyValueSQL[1] == "multipolygon"); if (isMultiPolygon) { var Members = r.GetMembersPostgreSQLByType(r.id, 1, con); OuterNodes = Members .Where(m => m.Role.ToLower() == Member.RoleOuter) .OrderBy(o => o.order) .Select(m => w.GetNodesPostgreSQL(m.ReferenceId, connPostGreSql).ToArray()).ToList(); InnerNodes = Members .Where(m => m.Role.ToLower() == Member.RoleInner) .OrderBy(o => o.order) .Select(m => w.GetNodesPostgreSQL(m.ReferenceId, connPostGreSql).ToArray()).ToList(); OuterWay = new List <OsmNode>(); foreach (var nodelist in OuterNodes) { OuterWay.AddRange(nodelist); } var tempPointsOuterPoints = new Vector3[OuterWay.Count]; int counter = 0; foreach (var node in OuterWay) { var result = CoordinateConvertor.SimpleInterpolation((float)node.PositionSQL.Lat, (float)node.PositionSQL.Lon, bounds, minmaxX, minmaxY); // Testing the correct direction tempPointsOuterPoints[counter] = new Vector3(direction * (float)result[0], 0, (float)result[1]) - MinPointOnMap; counter++; } List <Vector3[]> holes = new List <Vector3[]>(); for (int i = 0; i < InnerNodes.Count; i++) { holes.Add(new Vector3[InnerNodes[i].Length]); for (int j = 0; j < InnerNodes[i].Length; j++) { var result = CoordinateConvertor.SimpleInterpolation((float)InnerNodes[i][j].PositionSQL.Lat, (float)InnerNodes[i][j].PositionSQL.Lon, bounds, minmaxX, minmaxY); // Testing the correct direction holes[i][j] = new Vector3(direction * (float)result[0], 0, (float)result[1]) - MinPointOnMap; } } var outerpolypoints = tempPointsOuterPoints.Select(s => new Poly2Tri.PolygonPoint(s.x, s.z)); Poly2Tri.Polygon poly = new Poly2Tri.Polygon(outerpolypoints); // TODO: poly.IsPointInside() check the holes against all pieces var innerspolypoints = new List <Poly2Tri.PolygonPoint[]>(); for (int i = 0; i < holes.Count; i++) { var currentInner = holes[i].Select(s => new Poly2Tri.PolygonPoint(s.x, s.z)).ToArray(); innerspolypoints.Add(currentInner); poly.AddHole(new Poly2Tri.Polygon(currentInner)); } Poly2Tri.P2T.Triangulate(poly); var randHeight = CoordinateConvertor.linear((float)rand.NextDouble(), 0, 1, -3f, height); var randMaterial = (randHeight > height / 2f) ? "BuildingTall" : randHeight < height / 2f ? "Building2" : "Building"; var resultedGameObject = poly.GenerateShapeUVedWithWalls_Balanced( CoordinateConvertor.OSMType.Relation, FirstWay, "Building", "Building", randMaterial, height + randHeight, height + 7, true); return(resultedGameObject); } con.Close(); } } return(null); }