void Generate() { var area = Area; Vector2 position = transform.position; var calc = new VoronoiCalculator(); var clip = new VoronoiClipper(); var sites = new Vector2[maxNumberOfPieces]; for (int i = 0; i < sites.Length; i++) { var dist = Mathf.Abs(NormalizedRandom(0.0f, 8f)); var angle = 2.0f * Mathf.PI * Random.value; sites[i] = position + new Vector2( dist * Mathf.Cos(angle), dist * Mathf.Sin(angle)); } var diagram = calc.CalculateDiagram(sites); var clipped = new List <Vector2>(); for (int i = 0; i < sites.Length; i++) { clip.ClipSite(diagram, Polygon, i, ref clipped); if (clipped.Count > 0) { var newGo = Instantiate(gameObject, transform.parent); newGo.transform.localPosition = transform.localPosition; newGo.transform.localRotation = transform.localRotation; var cdGenerator = newGo.GetComponent <CityDistrictsGenerator>(); cdGenerator.shouldGenerate = false; cdGenerator.Thickness = Thickness; cdGenerator.Polygon.Clear(); cdGenerator.Polygon.AddRange(clipped); var grArea = newGo.GetComponent <GroundArea>(); grArea.props.name = "Area " + i; // Coloca una facción aleatoria a cada area creada desde un array de facciones (ScriptableObjects) grArea.props.faction = factionList[Random.Range(0, factionList.Length)]; var childArea = cdGenerator.Area; } } gameObject.SetActive(false); Destroy(gameObject); }
/// <summary> /// Given a triangle, subdivide it in multiple polygons using Voronoi diagrams. /// </summary> /// <param name="v0">First vertex of the triangle.</param> /// <param name="v1">Second vertex of the triangle.</param> /// <param name="v2">Third vertex of the triangle.</param> /// <param name="fragmentsProperties">Properties of the resulted fragments.</param> /// <returns>A list with all generated fragments' meshes. If the function failes to generate /// a fragment, it will the site of the fragment as a placeholder.</returns> private static List <GameObject> GenerateVoronoiFragments( Vertex v0, Vertex v1, Vertex v2, FragmentsProperties fragmentsProperties, bool areaInsteadOfSites = false) { if (fragmentsProperties == null) { throw new System.ArgumentNullException("fragmentsProperties"); } float area = 0.5f * Vector3.Cross(v0.position - v1.position, v0.position - v2.position).magnitude; //Debug.Log($"Area {area}"); //Debug.Log($"Max Area: {fragmentsProperties.maxArea}"); // The Voronoi API suffers from innacurate floating-point approximations and provides erroneous // results when dealing with small numbers. A current solution to this problem is to upscale // the whole mesh, do the calculations and then downscale the result to the original scale. Vector3 scale = new Vector3( Constants.VoronoiScale, Constants.VoronoiScale, Constants.VoronoiScale ); Vector3 reverseScale = new Vector3( 1.0f / Constants.VoronoiScale, 1.0f / Constants.VoronoiScale, 1.0f / Constants.VoronoiScale ); v0.position.Scale(scale); v1.position.Scale(scale); v2.position.Scale(scale); // The Voronoi API requires 2D points while the trianlge is in 3D space. In order to reduce // one dimension, all points must have the same z coordinate. Thus, rotate the triangle such // that its normal will become (0, 0, 1). Vector3 center = (v0.position + v1.position + v2.position) / 3.0f; Vector3 triangleNorm = Vector3.Cross(v1.position - v0.position, v2.position - v0.position).normalized; Quaternion rotation = Quaternion.FromToRotation(triangleNorm, Vector3.forward); Quaternion reverseRotation = Quaternion.FromToRotation(Vector3.forward, triangleNorm); Vector3 p0rot = (rotation * (v0.position - center) + center); Vector3 p1rot = (rotation * (v1.position - center) + center); Vector3 p2rot = (rotation * (v2.position - center) + center); float z = p0rot.z; // Try to make all fragments have similar area int sitesNum = areaInsteadOfSites ? Math.Max(3, (int)(area / fragmentsProperties.maxArea)) : fragmentsProperties.sitesPerTriangle;//Math.Max(3, (int)(area / fragmentsProperties.maxArea)); //Debug.Log($"Sites num: {sitesNum}"); Vector2[] sites = new Vector2[sitesNum]; // Generate random points inside the triangle. These will be the sites passed to the Voronoi API. for (int i = 0; i < sites.Length; ++i) { Vector3 p = Vector3.zero; int triesLeft = 50; while (triesLeft > 0) { float r1 = Random.Range(0.1f, 0.9f); float r2 = Random.Range(0.1f, 0.9f); p = (float)(1.0f - Math.Sqrt(r1)) * p0rot + (float)(Math.Sqrt(r1) * (1.0f - r2)) * p1rot + (float)(r2 * Math.Sqrt(r1)) * p2rot; if (PointInTriangle(p0rot, p1rot, p2rot, p)) { break; } Debug.Log($"Bad point {p:F5}\n" + $" inside triangle, ({p0rot:F5}, {p1rot:F5}, {p2rot:F5})\n" + $"{triesLeft} tries left"); --triesLeft; } sites[i] = p; } // Calculate the Voronoi diagram containing the given sites VoronoiCalculator calc = new VoronoiCalculator(); VoronoiClipper clip = new VoronoiClipper(); VoronoiDiagram diagram = calc.CalculateDiagram(sites); Vector2[] triangleClipper = new Vector2[3] { new Vector2(p0rot.x, p0rot.y), new Vector2(p1rot.x, p1rot.y), new Vector2(p2rot.x, p2rot.y) }; List <Vector2> clipped = new List <Vector2>(); List <GameObject> fragments = new List <GameObject>(sites.Length); // Generate a mesh for each site's polygon for (int i = 0; i < sites.Length; ++i) { clipped.Clear(); clip.ClipSite(diagram, triangleClipper, i, ref clipped); if (clipped.Count > 0) { // Rotate the points back to their original rotation Vector3[] originalSpacePoints = new Vector3[clipped.Count]; for (int j = 0; j < clipped.Count; ++j) { Vector3 v = new Vector3(clipped[j].x, clipped[j].y, z); originalSpacePoints[j] = reverseRotation * (v - center) + center; } Vertex[] vertices = CalculateNormalsAndUVs(v0, v1, v2, originalSpacePoints); // Revert the upscaling and scale to original object scale for (int j = 0; j < clipped.Count; ++j) { vertices[j].position.Scale(reverseScale); } float thickness = Random.Range(fragmentsProperties.minThickness, fragmentsProperties.maxThickness); fragments.Add(PolygonToFrustum(vertices, thickness)); } } return(fragments); }