public static void SetDefaults(FragmentsProperties fp) { fp.minThickness = Constants.DefaultMinThickness; fp.maxThickness = Constants.DefaultMaxThickness; fp.sitesPerTriangle = Constants.DefaultSites; fp.maxArea = Constants.DefaultMaxArea; fp.density = Constants.DefaultDensity; }
public static void Copy(FragmentsProperties dst, FragmentsProperties src) { dst.minThickness = src.minThickness; dst.maxThickness = src.maxThickness; dst.sitesPerTriangle = src.sitesPerTriangle; dst.maxArea = src.maxArea; dst.density = src.density; dst.desnities = new Dictionary <string, float>(src.desnities); }
// Start is called before the first frame update void Awake() { // Load values from menu or assume defaults if (PlayerPrefs.HasKey("HouseNumber") == false) { PlayerPrefs.SetInt("HouseNumber", 9); housesNum = 9; } else { housesNum = PlayerPrefs.GetInt("HouseNumber"); } if (PlayerPrefs.HasKey("GrowTrees") == false) { PlayerPrefs.SetInt("GrowTrees", 1); treeGrowing = true; } else { treeGrowing = System.Convert.ToBoolean(PlayerPrefs.GetInt("GrowTrees")); } if (PlayerPrefs.HasKey("IvyGrowingSpeed") == false) { PlayerPrefs.SetInt("IvyGrowingSpeed", (int)IvyGrowingSpeed.medium); ivyGrowingSpeed = IvyGrowingSpeed.medium; } else { ivyGrowingSpeed = (IvyGrowingSpeed)PlayerPrefs.GetInt("IvyGrowingSpeed"); } if (!PlayerPrefs.HasKey("SitesPerTriangle")) { PlayerPrefs.SetInt("SitesPerTriangle", 3); sitesPerTriangle = 3; } else { sitesPerTriangle = PlayerPrefs.GetInt("SitesPerTriangle"); } if (!PlayerPrefs.HasKey("DestructionStart")) { PlayerPrefs.SetInt("DestructionStart", 0); destructionStart = DestructionTime.soon; } else { destructionStart = (DestructionTime)PlayerPrefs.GetInt("DestructionStart"); } // Change number of houses in the scene housesNum = Mathf.Clamp(housesNum, 0, houses.Count); for (int i = houses.Count - 1; i >= housesNum; --i) { DestroyImmediate(houses[houses.Count - 1]); houses.RemoveAt(houses.Count - 1); } foreach (GameObject house in houses) { //house.transform.Find("Walls+Roof").gameObject.GetComponent<FragmentsProperties>().sitesPerTriangle = sitesPerTriangle; foreach (Transform child in house.transform) { if (child.name.StartsWith("House")) { Transform grandchild = child.Find("Walls+Roof"); if (grandchild != null) { FragmentsProperties fp = grandchild.gameObject.GetComponent <FragmentsProperties>(); if (fp != null) { fp.sitesPerTriangle = sitesPerTriangle; } } } } } // Change how fast the ivy will grow in the scene foreach (IvyBehavior iv in FindObjectsOfType <IvyBehavior>()) { switch (ivyGrowingSpeed) { case IvyGrowingSpeed.none: iv.refreshInterval = 1000.0f; iv.probability = 0.0f; break; case IvyGrowingSpeed.slow: iv.refreshInterval = 0.7f; iv.probability = 0.5f; break; case IvyGrowingSpeed.medium: iv.refreshInterval = 0.5f; iv.probability = 0.5f; break; case IvyGrowingSpeed.fast: iv.refreshInterval = 0.3f; iv.probability = 0.5f; break; case IvyGrowingSpeed.veryFast: iv.refreshInterval = 0.1f; iv.probability = 0.7f; break; } } // Change whether tree will grow in the scene foreach (TreeGrowing t in FindObjectsOfType <TreeGrowing>()) { t.isGrowing = treeGrowing; } switch (destructionStart) { case DestructionTime.soon: Constants.DestroyerMiscMinStart = 5.0f; Constants.DestroyerMiscMaxStart = 6.0f; Constants.DestroyerBuildMinStart = 10.0f; Constants.DestroyerBuildMaxStart = 15.0f; break; case DestructionTime.late: Constants.DestroyerMiscMinStart = 10.0f; Constants.DestroyerMiscMaxStart = 15.0f; Constants.DestroyerBuildMinStart = 40.0f; Constants.DestroyerBuildMaxStart = 50.0f; break; } }
/// <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); }
public static IEnumerator Fragment(GameObject toFragmentObject, Stats returnedStats, bool areaInsteadOfSites = false) { returnedStats.totalTime = 0L; Debug.Log("Fragmentation started!"); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); // Gather fragmentation properties for the current game object. The program checks the // current game object and the topmost game object of the hierarchy for such properties. // If none are found, assume default properties. FragmentsProperties fragmentsProperties = toFragmentObject.GetComponent <FragmentsProperties>(); if (fragmentsProperties == null) { GameObject root = toFragmentObject.transform.root.gameObject; FragmentsProperties rootFragmentsProperties = root.GetComponent <FragmentsProperties>(); fragmentsProperties = (rootFragmentsProperties == null) ? toFragmentObject.AddComponent <FragmentsProperties>() : rootFragmentsProperties; } // Create new game object as parent of future generated fragments. This game object will // replace the current game object in the hierarchy and will retain its fragmentation properties. GameObject parentObject = new GameObject(toFragmentObject.name + Constants.FragmentStr); CopyTransforms(parentObject.transform, toFragmentObject.transform); parentObject.transform.SetParent(toFragmentObject.transform.parent, false); FragmentsProperties.Copy(parentObject.AddComponent <FragmentsProperties>(), fragmentsProperties); // Iterate through the mesh's triangles and divide each triangle in separate Voronoi diagrams. // The generated fragments will retain the original mesh's materials, normals and uv coordinates. int totalFragments = 0; Mesh mesh = toFragmentObject.GetComponent <MeshFilter>().mesh; for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; ++subMeshIndex) { int[] triangles = mesh.GetTriangles(subMeshIndex); // TODO paralelize for (int ti = 0; ti < triangles.Length; ti += 3) { Vertex[] v = new Vertex[3]; for (int i = 0; i < 3; ++i) { v[i].position = mesh.vertices[triangles[ti + i]]; // Assume zero values if missing normal or uv coordinates v[i].normal = (mesh.normals.Length <= triangles[ti + i]) ? Vector3.zero : mesh.normals[triangles[ti + i]]; v[i].uv = (mesh.uv.Length <= triangles[ti + i]) ? Vector2.zero : mesh.uv[triangles[ti + i]]; } List <GameObject> fragments = GenerateVoronoiFragments(v[0], v[1], v[2], fragmentsProperties, areaInsteadOfSites); foreach (GameObject fragment in fragments) { if (fragment.GetComponent <MeshFilter>() != null) { // Create new game object with fragment's mesh fragment.name = Constants.FrozenFragmentStr + totalFragments; fragment.transform.SetParent(parentObject.transform, false); fragment.tag = Constants.FrozenFragmentStr; MeshRenderer goMeshRenderer = toFragmentObject.GetComponent <MeshRenderer>(); MeshRenderer fragmentMeshRenderer = fragment.AddComponent <MeshRenderer>(); int materialIdx = subMeshIndex % goMeshRenderer.materials.Length; fragmentMeshRenderer.material = goMeshRenderer.materials[materialIdx]; // Hide current fragment's mesh until the destruction of the initial game object // to prevent z-fighting effects. fragmentMeshRenderer.enabled = false; MeshCollider mc = fragment.AddComponent <MeshCollider>(); mc.convex = true; Rigidbody rb = fragment.AddComponent <Rigidbody>(); rb.detectCollisions = false; rb.isKinematic = true; rb.interpolation = RigidbodyInterpolation.Interpolate; rb.mass = fragmentsProperties.density * FragmentVolume(fragment); rb.angularDrag = Constants.FragmentRBodyAngDrag; rb.drag = Constants.FragmentRBodyDrag; fragment.AddComponent <JointBreakListener>(); ++totalFragments; } else { // Missing fragment Debug.Log($"Clipped site: {fragment.transform.localPosition}"); } } // Ensure the current function operation does not take a lot of time // in a single frame if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs) { Debug.Log("Fragmenter Yield in voronoi generation"); returnedStats.totalTime += stopWatch.ElapsedMilliseconds; yield return(null); stopWatch.Restart(); } } // Ensure the current function operation does not take a lot of time // in a single frame if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs) { Debug.Log("Fragmenter Yield submesh parsing"); returnedStats.totalTime += stopWatch.ElapsedMilliseconds; yield return(null); stopWatch.Restart(); } } Debug.Log($"Fragmentation Ended at: {returnedStats.totalTime / 1000.0f}"); // Create an undirected graph with all fragments. Each fragment represents a node in the // graph. The edges represent the connectivity between the fragments. Each node will have // a direct edge to each of its touching neighbours. var graph = new Graph <GameObject, HingeJoint>(); foreach (Transform child in parentObject.transform) { if (!child.CompareTag(Constants.FrozenFragmentStr)) { continue; } GameObject go = child.gameObject; List <GameObject> possibleNeighbours = new List <GameObject>(); // To find the touching neighbours, do a rough and fast bounding box intersection first. foreach (Transform child2 in parentObject.transform) { GameObject go2 = child2.gameObject; if (go != go2) { Bounds b1 = go.GetComponent <MeshCollider>().bounds; Bounds b2 = go2.GetComponent <MeshCollider>().bounds; if (b1 == null) { Debug.Log($"{go} does not have a Mesh Collider"); } if (b2 == null) { Debug.Log($"{go2} does not have a Mesh Collider"); } if (b1 != null && b2 != null && b1.Intersects(b2)) { possibleNeighbours.Add(go2); } } } // Do a more fine and more computational heavy intersection. To do so, the mesh collider's // size will be slightly increased. The actual mesh collider's size cannot be modified, // thus a new game object will be created with a bigger mesh and a mesh collider. GameObject biggerGo = new GameObject($"Big_{child.name}"); biggerGo.transform.SetParent(go.transform); CopyTransforms(biggerGo.transform, go.transform); biggerGo.AddComponent <MeshFilter>().mesh = ScaleMesh(go.GetComponent <MeshFilter>().mesh, Constants.MeshUpscaling); biggerGo.AddComponent <MeshCollider>().convex = true; if (!graph.ContainsNode(go)) { graph.AddNode(go); } foreach (GameObject neighbour in possibleNeighbours) { Collider colBigg = biggerGo.GetComponent <Collider>(); Collider colNeigh = neighbour.GetComponent <Collider>(); if (Physics.ComputePenetration( colBigg, go.transform.position, go.transform.rotation, colNeigh, neighbour.transform.position, neighbour.transform.rotation, out _, out _)) { if (graph.ContainsEdge(go, neighbour) || graph.ContainsEdge(neighbour, go)) { continue; } if (!graph.ContainsNode(neighbour)) { graph.AddNode(neighbour); } void CreateJoint(GameObject go1, GameObject go2) { Vector3 center1 = FragmentCenter(go1); Vector3 center2 = FragmentCenter(go2); HingeJoint hj = go1.AddComponent <HingeJoint>(); hj.connectedBody = go2.GetComponent <Rigidbody>(); hj.enableCollision = false; hj.breakForce = Constants.FragmentHingeBreakForce; hj.anchor = center2; hj.axis = center2 - center1; JointLimits hinjeLimits = hj.limits; hinjeLimits.min = Constants.FragmentHingeMinAngleDeg; hinjeLimits.bounciness = 0.0f; hinjeLimits.bounceMinVelocity = 0.0f; hinjeLimits.max = Constants.FragmentHingeMaxAngleDeg; hj.limits = hinjeLimits; hj.useLimits = true; graph.AddEdge(go1, go2, hj); } CreateJoint(go, neighbour); CreateJoint(neighbour, go); } } //empty.transform.SetParent(null); Object.Destroy(biggerGo); // Ensure the current function operation does not take a lot of time // in a single frame. if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs) { Debug.Log("Fragmenter Yield in graph creating"); returnedStats.totalTime += stopWatch.ElapsedMilliseconds; yield return(null); stopWatch.Restart(); } } //Debug.Log($"Min Area: {minSurface}, Max Area: {maxSurface}"); FragmentsGraph fragmentsGraph = parentObject.AddComponent <FragmentsGraph>(); fragmentsGraph.graph = graph; // Chose anchor fragments. An anchor fragment is a fragment which is considered crucial in // a mesh structural stability. These have to be manually specified. The program decides // if a fragment is an anchor if it intersects a 3D object with "Anchor" tag and layer. AnchorList anchorList = toFragmentObject.GetComponent <AnchorList>(); if (anchorList == null) { anchorList = toFragmentObject.transform.parent.gameObject.GetComponent <AnchorList>(); } if (anchorList != null) { // Fast and rough intersection List <GameObject> possibleAnchored = new List <GameObject>(); foreach (Transform fragment in parentObject.transform) { foreach (GameObject anchor in anchorList.gameObjects) { Bounds b1 = fragment.GetComponent <Collider>().bounds; Bounds b2 = anchor.GetComponent <BoxCollider>().bounds; if (b1.Intersects(b2)) { possibleAnchored.Add(fragment.gameObject); } } } // Slow and accurate intersection HashSet <GameObject> anchoredFragments = new HashSet <GameObject>(); foreach (GameObject fragment in possibleAnchored) { Collider col1 = fragment.GetComponent <Collider>(); foreach (GameObject anchor in anchorList.gameObjects) { Collider col2 = anchor.GetComponent <Collider>(); if (Physics.ComputePenetration( col1, fragment.transform.position, fragment.transform.rotation, col2, anchor.transform.position, anchor.transform.rotation, out _, out _)) { anchoredFragments.Add(fragment); //Debug.Log($"Anchored {fragment.name}"); } } } fragmentsGraph.anchoredFragments = anchoredFragments; fragmentsGraph.initialAnchoredCount = anchoredFragments.Count; } foreach (Transform child in parentObject.transform) { child.gameObject.GetComponent <MeshRenderer>().enabled = true; Rigidbody rb = child.gameObject.GetComponent <Rigidbody>(); rb.isKinematic = fragmentsGraph.anchoredFragments != null && fragmentsGraph.anchoredFragments.Contains(rb.gameObject); rb.detectCollisions = true; } returnedStats.isDone = true; returnedStats.fragments = totalFragments; returnedStats.fragmentedGameObject = parentObject; fragmentsGraph.initialFragmentsCount = fragmentsGraph.currentFragmentsCount = totalFragments; Object.Destroy(toFragmentObject); }