using UnityEngine; using System.Collections; using System.Collections.Generic; using Delaunay; using Delaunay.Geo; public static class SpriteExploder { public static List GenerateTriangularPieces(GameObject source, int extraPoints = 0, int subshatterSteps = 0, Material mat = null) { List pieces = new List(); if (mat == null) { mat = createFragmentMaterial(source); } //get transform information Vector3 origScale = source.transform.localScale; source.transform.localScale = Vector3.one; Quaternion origRotation = source.transform.localRotation; source.transform.localRotation = Quaternion.identity; //get rigidbody information Vector2 origVelocity = source.GetComponent().velocity; //get collider information PolygonCollider2D sourcePolyCollider = source.GetComponent(); BoxCollider2D sourceBoxCollider = source.GetComponent(); List points = new List(); List borderPoints = new List(); //add points from the present collider if (sourcePolyCollider != null) { points = getPoints(sourcePolyCollider); borderPoints = getPoints(sourcePolyCollider); } else if (sourceBoxCollider != null) { points = getPoints(sourceBoxCollider); borderPoints = getPoints(sourceBoxCollider); } //create a bounding rectangle based on the polygon points Rect rect = getRect(source); //if the target polygon is a triangle, generate a point in the middle to allow for fracture if (points.Count == 3) { points.Add((points[0] + points[1] + points[2]) / 3); } for (int i = 0; i < extraPoints; i++) { points.Add(new Vector2(Random.Range(rect.width / -2, rect.width / 2), Random.Range(rect.height / -2, rect.height / 2))); } Voronoi voronoi = new Delaunay.Voronoi(points, null, rect); List> clippedTriangles = new List>(); foreach (Triangle tri in voronoi.Triangles()) { clippedTriangles = ClipperHelper.clip(borderPoints, tri); foreach (List triangle in clippedTriangles) { pieces.Add(generateTriangularPiece(source, triangle, origVelocity, origScale, origRotation, mat)); } } List morePieces = new List(); if (subshatterSteps > 0) { subshatterSteps--; foreach (GameObject piece in pieces) { morePieces.AddRange(SpriteExploder.GenerateTriangularPieces(piece, extraPoints, subshatterSteps, mat)); GameObject.DestroyImmediate(piece); } } else { morePieces = pieces; } //reset transform information source.transform.localScale = origScale; source.transform.localRotation = origRotation; Resources.UnloadUnusedAssets(); return morePieces; } private static GameObject generateTriangularPiece(GameObject source, List tri, Vector2 origVelocity, Vector3 origScale, Quaternion origRotation, Material mat) { //Create Game Object and set transform settings properly GameObject piece = new GameObject(source.name + " piece"); piece.transform.position = source.transform.position; piece.transform.rotation = source.transform.rotation; piece.transform.localScale = source.transform.localScale; //Create and Add Mesh Components MeshFilter meshFilter = (MeshFilter)piece.AddComponent(typeof(MeshFilter)); piece.AddComponent(typeof(MeshRenderer)); Mesh uMesh = piece.GetComponent().sharedMesh; if (uMesh == null) { meshFilter.mesh = new Mesh(); uMesh = meshFilter.sharedMesh; } Vector3[] vertices = new Vector3[3]; int[] triangles = new int[3]; vertices[0] = new Vector3(tri[0].x, tri[0].y, 0); vertices[1] = new Vector3(tri[1].x, tri[1].y, 0); vertices[2] = new Vector3(tri[2].x, tri[2].y, 0); triangles[0] = 0; triangles[1] = 1; triangles[2] = 2; uMesh.vertices = vertices; uMesh.triangles = triangles; if (source.GetComponent() != null) { uMesh.uv = calcUV(vertices, source.GetComponent(), source.transform); } else { uMesh.uv = calcUV(vertices, source.GetComponent(), source.transform); } //set transform properties before fixing the pivot for easier rotation piece.transform.localScale = origScale; piece.transform.localRotation = origRotation; Vector3 diff = calcPivotCenterDiff(piece); centerMeshPivot(piece, diff); uMesh.RecalculateBounds(); //setFragmentMaterial(piece, source); piece.GetComponent().sharedMaterial = mat; //assign mesh meshFilter.mesh = uMesh; //Create and Add Polygon Collider PolygonCollider2D collider = piece.AddComponent(); collider.SetPath(0, new Vector2[]{uMesh.vertices[0],uMesh.vertices[1],uMesh.vertices[2]}); //Create and Add Rigidbody Rigidbody2D rigidbody = piece.AddComponent(); rigidbody.velocity = origVelocity; return piece; } public static List GenerateVoronoiPieces(GameObject source, int extraPoints = 0, int subshatterSteps = 0, Material mat = null) { List pieces = new List(); if (mat == null) { mat = createFragmentMaterial(source); } //get transform information Vector3 origScale = source.transform.localScale; source.transform.localScale = Vector3.one; Quaternion origRotation = source.transform.localRotation; source.transform.localRotation = Quaternion.identity; //get rigidbody information Vector2 origVelocity = source.GetComponent().velocity; //get collider information PolygonCollider2D sourcePolyCollider = source.GetComponent(); BoxCollider2D sourceBoxCollider = source.GetComponent(); List points = new List(); List borderPoints = new List(); if (sourcePolyCollider != null) { points = getPoints(sourcePolyCollider); borderPoints = getPoints(sourcePolyCollider); } else if (sourceBoxCollider != null) { points = getPoints(sourceBoxCollider); borderPoints = getPoints(sourceBoxCollider); } Rect rect = getRect(source); for (int i = 0; i < extraPoints; i++) { points.Add(new Vector2(Random.Range(rect.width / -2, rect.width / 2), Random.Range(rect.height / -2, rect.height / 2))); } Voronoi voronoi = new Delaunay.Voronoi(points, null, rect); List> clippedRegions = new List>(); foreach (List region in voronoi.Regions()) { clippedRegions = ClipperHelper.clip(borderPoints, region); foreach (List clippedRegion in clippedRegions) { pieces.Add(generateVoronoiPiece(source, clippedRegion, origVelocity, origScale, origRotation, mat)); } } List morePieces = new List(); if (subshatterSteps > 0) { subshatterSteps--; foreach (GameObject piece in pieces) { morePieces.AddRange(SpriteExploder.GenerateVoronoiPieces(piece, extraPoints, subshatterSteps)); GameObject.DestroyImmediate(piece); } } else { morePieces = pieces; } //reset transform information source.transform.localScale = origScale; source.transform.localRotation = origRotation; Resources.UnloadUnusedAssets(); return morePieces; } private static GameObject generateVoronoiPiece(GameObject source, List region, Vector2 origVelocity, Vector3 origScale, Quaternion origRotation, Material mat) { //Create Game Object and set transform settings properly GameObject piece = new GameObject(source.name + " piece"); piece.transform.position = source.transform.position; piece.transform.rotation = source.transform.rotation; piece.transform.localScale = source.transform.localScale; //Create and Add Mesh Components MeshFilter meshFilter = (MeshFilter)piece.AddComponent(typeof(MeshFilter)); piece.AddComponent(typeof(MeshRenderer)); Mesh uMesh = piece.GetComponent().sharedMesh; if (uMesh == null) { meshFilter.mesh = new Mesh(); uMesh = meshFilter.sharedMesh; } Voronoi voronoi = new Voronoi(region, null, getRect(region)); Vector3[] vertices = calcVerts(voronoi); int[] triangles = calcTriangles(voronoi); uMesh.vertices = vertices; uMesh.triangles = triangles; if (source.GetComponent() != null) { uMesh.uv = calcUV(vertices, source.GetComponent(), source.transform); } else { uMesh.uv = calcUV(vertices, source.GetComponent(), source.transform); } //set transform properties before fixing the pivot for easier rotation piece.transform.localScale = origScale; piece.transform.localRotation = origRotation; Vector3 diff = calcPivotCenterDiff(piece); centerMeshPivot(piece, diff); uMesh.RecalculateBounds(); //setFragmentMaterial(piece, source); piece.GetComponent().sharedMaterial = mat; //assign mesh meshFilter.mesh = uMesh; //Create and Add Polygon Collider PolygonCollider2D collider = piece.AddComponent(); collider.SetPath(0, calcPolyColliderPoints(region,diff)); //Create and Add Rigidbody Rigidbody2D rigidbody = piece.AddComponent(); rigidbody.velocity = origVelocity; return piece; } /// /// generates a list of points from a box collider /// /// source box collider /// list of points private static List getPoints(BoxCollider2D collider) { List points = new List(); Vector2 center = collider.offset; Vector2 size = collider.size; //bottom left points.Add(new Vector2((center.x - size.x / 2), (center.y - size.y / 2))); //top left points.Add(new Vector2((center.x - size.x / 2), (center.y + size.y / 2))); //top right points.Add(new Vector2((center.x + size.x / 2), (center.y + size.y / 2))); //bottom right points.Add(new Vector2((center.x + size.x / 2), (center.y - size.y / 2))); return points; } /// /// generates a list of points from a polygon collider /// /// source polygon collider /// list of points private static List getPoints(PolygonCollider2D collider) { List points = new List(); foreach (Vector2 point in collider.GetPath(0)) { points.Add(point); } return points; } private static List getRendererPoints(GameObject source) { List points = new List(); Bounds bounds = source.GetComponent().bounds; points.Add(new Vector2(bounds.center.x - bounds.extents.x, bounds.center.y - bounds.extents.y) - (Vector2)source.transform.position); points.Add(new Vector2(bounds.center.x + bounds.extents.x, bounds.center.y - bounds.extents.y) - (Vector2)source.transform.position); points.Add(new Vector2(bounds.center.x + bounds.extents.x, bounds.center.y + bounds.extents.y) - (Vector2)source.transform.position); points.Add(new Vector2(bounds.center.x - bounds.extents.x, bounds.center.y + bounds.extents.y) - (Vector2)source.transform.position); return points; } /// /// generates a rectangle based on the rendering bounds of the object /// /// gameobject to get the rectangle from /// a Rectangle representing the rendering bounds of the object private static Rect getRect(GameObject source) { Bounds bounds = source.GetComponent().bounds; //return new Rect(source.transform.localPosition - bounds.extents, bounds.size); return new Rect(bounds.extents.x*-1, bounds.extents.y*-1,bounds.size.x,bounds.size.y); } private static Rect getRect(List region) { Vector2 center = new Vector2(); float minX = region[0].x; float maxX = minX; float minY = region[0].y; float maxY = minY; foreach (Vector2 v in region) { center += v; if (v.x < minX) { minX = v.x; } if (v.x > maxX) { maxX = v.x; } if (v.y < minY) { minY = v.y; } if (v.y > maxY) { maxY = v.y; } } center /= region.Count; Vector2 size = new Vector2(maxX - minX, maxY - minY); return new Rect(center, size); } /// /// calculates the UV coordinates for the given vertices based on the provided Sprite /// /// vertices to generate the UV coordinates for /// Sprite Renderer of original object /// Transform of the original object /// array of UV coordinates for the mesh private static Vector2[] calcUV(Vector3[] vertices, SpriteRenderer sRend, Transform sTransform) { float texHeight = (sRend.bounds.extents.y * 2) / sTransform.localScale.y; float texWidth = (sRend.bounds.extents.x * 2) / sTransform.localScale.x; Vector3 botLeft = sTransform.InverseTransformPoint(new Vector3(sRend.bounds.center.x - sRend.bounds.extents.x, sRend.bounds.center.y - sRend.bounds.extents.y, 0)); Vector2[] uv = new Vector2[vertices.Length]; Vector2[] sourceUV = sRend.sprite.uv; Vector2 uvMin; Vector2 uvMax; getUVRange(out uvMin, out uvMax, sourceUV); for (int i = 0; i < vertices.Length; i++) { float x = (vertices[i].x - botLeft.x) / texWidth; x = scaleRange(x, 0, 1, uvMin.x, uvMax.x); float y = (vertices[i].y - botLeft.y) / texHeight; y = scaleRange(y, 0, 1, uvMin.y, uvMax.y); uv[i] = new Vector2(x, y); } return uv; } private static Vector2[] calcUV(Vector3[] vertices, MeshRenderer mRend, Transform sTransform) { float texHeight = (mRend.bounds.extents.y * 2) / sTransform.localScale.y; float texWidth = (mRend.bounds.extents.x * 2) / sTransform.localScale.x; Vector3 botLeft = sTransform.InverseTransformPoint(new Vector3(mRend.bounds.center.x - mRend.bounds.extents.x, mRend.bounds.center.y - mRend.bounds.extents.y, 0)); Vector2[] uv = new Vector2[vertices.Length]; Vector2[] sourceUV = sTransform.GetComponent().sharedMesh.uv; Vector2 uvMin; Vector2 uvMax; getUVRange(out uvMin, out uvMax, sourceUV); for (int i = 0; i < vertices.Length; i++) { float x = (vertices[i].x - botLeft.x) / texWidth; x = scaleRange(x, 0, 1, uvMin.x, uvMax.x); float y = (vertices[i].y - botLeft.y) / texHeight; y = scaleRange(y, 0, 1, uvMin.y, uvMax.y); uv[i] = new Vector2(x, y); } return uv; } private static void getUVRange(out Vector2 min, out Vector2 max, Vector2[]uv) { min = uv[0]; max = uv[0]; foreach (Vector2 p in uv) { if (p.x < min.x) { min.x = p.x; } if (p.x > max.x) { max.x = p.x; } if (p.y < min.y) { min.y = p.y; } if (p.y > max.y) { max.y = p.y; } } } private static float scaleRange(float target, float oldMin, float oldMax, float newMin, float newMax) { return (target / ((oldMax - oldMin) / (newMax - newMin))) + newMin; } private static Vector3[] calcVerts(Voronoi region) { List sites = region.Sites()._sites; Vector3[] vertices = new Vector3[sites.Count]; int idx = 0; foreach (Site s in sites) { vertices[idx++] = new Vector3(s.x,s.y,0); } return vertices; } private static int[] calcTriangles(Voronoi region) { //calculate unity triangles int[] triangles = new int[region.Triangles().Count*3]; List sites = region.Sites()._sites; int idx = 0; foreach (Triangle t in region.Triangles()) { triangles[idx++] = sites.IndexOf(t.sites[0]); triangles[idx++] = sites.IndexOf(t.sites[1]); triangles[idx++] = sites.IndexOf(t.sites[2]); } return triangles; } private static Vector2[] calcPolyColliderPoints(List points, Vector2 offset) { Vector2[] result = new Vector2[points.Count]; for (int i = 0; i < points.Count; i++) { result[i] = points[i] + offset; } return result; } /// /// calculates the distance between the targets pivot and it's actual center /// /// target gameobject to do the calculation on /// distance between center and pivot private static Vector3 calcPivotCenterDiff(GameObject target) { Mesh uMesh = target.GetComponent().sharedMesh; Vector3[] vertices = uMesh.vertices; Vector3 sum = new Vector3(); for (int i = 0; i < vertices.Length; i++) { sum += vertices[i]; } Vector3 triCenter = sum / vertices.Length; Vector3 pivot = target.transform.InverseTransformPoint(target.transform.position); return pivot - triCenter; } /// /// Sets the pivot of the target object to it's center /// /// Target Gameobject /// the distance from pivot to center private static void centerMeshPivot(GameObject target, Vector3 diff) { //initialize mesh and vertices variables from source Mesh uMesh = target.GetComponent().sharedMesh; Vector3[] vertices = uMesh.vertices; //calculate adjusted vertices for (int i = 0; i < vertices.Length; i++) { vertices[i] += diff; } //set adjusted vertices uMesh.vertices = vertices; //calculate and assign adjusted trasnsform position Vector3 pivot = target.transform.InverseTransformPoint(target.transform.position); target.transform.localPosition = target.transform.TransformPoint(pivot - diff); } /// /// assigns a new material for a fragment /// /// sprite of the fragment /// original gameobject that was shattered private static void setFragmentMaterial(GameObject newSprite, GameObject source) { Material mat = new Material(Shader.Find("Sprites/Default")); SpriteRenderer sRend = source.GetComponent(); if (sRend != null) { mat.SetTexture("_MainTex", sRend.sprite.texture); mat.color = sRend.color; } else { mat = source.GetComponent().sharedMaterial; } newSprite.GetComponent().sharedMaterial = mat; } private static Material createFragmentMaterial(GameObject source) { SpriteRenderer sRend = source.GetComponent(); if (sRend != null) { Material mat = new Material(Shader.Find("Sprites/Default")); mat.SetTexture("_MainTex", sRend.sprite.texture); mat.color = sRend.color; return mat; } else { return source.GetComponent().sharedMaterial; } } }