Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
MeshDestroy => Put it on a game object with a mesh filter and renderer. Make sure to have read/write enabled on fbx import
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MeshDestroy : MonoBehaviour
{
private bool edgeSet = false;
private Vector3 edgeVertex = Vector3.zero;
private Vector2 edgeUV = Vector2.zero;
private Plane edgePlane = new Plane();
public int CutCascades = 1;
public float ExplodeForce = 0;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
DestroyMesh();
}
}
private void DestroyMesh()
{
var originalMesh = GetComponent<MeshFilter>().mesh;
originalMesh.RecalculateBounds();
var parts = new List<PartMesh>();
var subParts = new List<PartMesh>();
var mainPart = new PartMesh()
{
UV = originalMesh.uv,
Vertices = originalMesh.vertices,
Normals = originalMesh.normals,
Triangles = new int[originalMesh.subMeshCount][],
Bounds = originalMesh.bounds
};
for (int i = 0; i < originalMesh.subMeshCount; i++)
mainPart.Triangles[i] = originalMesh.GetTriangles(i);
parts.Add(mainPart);
for (var c = 0; c < CutCascades; c++)
{
for (var i = 0; i < parts.Count; i++)
{
var bounds = parts[i].Bounds;
bounds.Expand(0.5f);
var plane = new Plane(UnityEngine.Random.onUnitSphere, new Vector3(UnityEngine.Random.Range(bounds.min.x, bounds.max.x),
UnityEngine.Random.Range(bounds.min.y, bounds.max.y),
UnityEngine.Random.Range(bounds.min.z, bounds.max.z)));
subParts.Add(GenerateMesh(parts[i], plane, true));
subParts.Add(GenerateMesh(parts[i], plane, false));
}
parts = new List<PartMesh>(subParts);
subParts.Clear();
}
for (var i = 0; i < parts.Count; i++)
{
parts[i].MakeGameobject(this);
parts[i].GameObject.GetComponent<Rigidbody>().AddForceAtPosition(parts[i].Bounds.center * ExplodeForce, transform.position);
}
Destroy(gameObject);
}
private PartMesh GenerateMesh(PartMesh original, Plane plane, bool left)
{
var partMesh = new PartMesh() { };
var ray1 = new Ray();
var ray2 = new Ray();
for (var i = 0; i < original.Triangles.Length; i++)
{
var triangles = original.Triangles[i];
edgeSet = false;
for (var j = 0; j < triangles.Length; j = j + 3)
{
var sideA = plane.GetSide(original.Vertices[triangles[j]]) == left;
var sideB = plane.GetSide(original.Vertices[triangles[j + 1]]) == left;
var sideC = plane.GetSide(original.Vertices[triangles[j + 2]]) == left;
var sideCount = (sideA ? 1 : 0) +
(sideB ? 1 : 0) +
(sideC ? 1 : 0);
if (sideCount == 0)
{
continue;
}
if (sideCount == 3)
{
partMesh.AddTriangle(i,
original.Vertices[triangles[j]], original.Vertices[triangles[j + 1]], original.Vertices[triangles[j + 2]],
original.Normals[triangles[j]], original.Normals[triangles[j + 1]], original.Normals[triangles[j + 2]],
original.UV[triangles[j]], original.UV[triangles[j + 1]], original.UV[triangles[j + 2]]);
continue;
}
//cut points
var singleIndex = sideB == sideC ? 0 : sideA == sideC ? 1 : 2;
ray1.origin = original.Vertices[triangles[j + singleIndex]];
var dir1 = original.Vertices[triangles[j + ((singleIndex + 1) % 3)]] - original.Vertices[triangles[j + singleIndex]];
ray1.direction = dir1;
plane.Raycast(ray1, out var enter1);
var lerp1 = enter1 / dir1.magnitude;
ray2.origin = original.Vertices[triangles[j + singleIndex]];
var dir2 = original.Vertices[triangles[j + ((singleIndex + 2) % 3)]] - original.Vertices[triangles[j + singleIndex]];
ray2.direction = dir2;
plane.Raycast(ray2, out var enter2);
var lerp2 = enter2 / dir2.magnitude;
//first vertex = ancor
AddEdge(i,
partMesh,
left ? plane.normal * -1f : plane.normal,
ray1.origin + ray1.direction.normalized * enter1,
ray2.origin + ray2.direction.normalized * enter2,
Vector2.Lerp(original.UV[triangles[j + singleIndex]], original.UV[triangles[j + ((singleIndex + 1) % 3)]], lerp1),
Vector2.Lerp(original.UV[triangles[j + singleIndex]], original.UV[triangles[j + ((singleIndex + 2) % 3)]], lerp2));
if (sideCount == 1)
{
partMesh.AddTriangle(i,
original.Vertices[triangles[j + singleIndex]],
//Vector3.Lerp(originalMesh.vertices[triangles[j + singleIndex]], originalMesh.vertices[triangles[j + ((singleIndex + 1) % 3)]], lerp1),
//Vector3.Lerp(originalMesh.vertices[triangles[j + singleIndex]], originalMesh.vertices[triangles[j + ((singleIndex + 2) % 3)]], lerp2),
ray1.origin + ray1.direction.normalized * enter1,
ray2.origin + ray2.direction.normalized * enter2,
original.Normals[triangles[j + singleIndex]],
Vector3.Lerp(original.Normals[triangles[j + singleIndex]], original.Normals[triangles[j + ((singleIndex + 1) % 3)]], lerp1),
Vector3.Lerp(original.Normals[triangles[j + singleIndex]], original.Normals[triangles[j + ((singleIndex + 2) % 3)]], lerp2),
original.UV[triangles[j + singleIndex]],
Vector2.Lerp(original.UV[triangles[j + singleIndex]], original.UV[triangles[j + ((singleIndex + 1) % 3)]], lerp1),
Vector2.Lerp(original.UV[triangles[j + singleIndex]], original.UV[triangles[j + ((singleIndex + 2) % 3)]], lerp2));
continue;
}
if (sideCount == 2)
{
partMesh.AddTriangle(i,
ray1.origin + ray1.direction.normalized * enter1,
original.Vertices[triangles[j + ((singleIndex + 1) % 3)]],
original.Vertices[triangles[j + ((singleIndex + 2) % 3)]],
Vector3.Lerp(original.Normals[triangles[j + singleIndex]], original.Normals[triangles[j + ((singleIndex + 1) % 3)]], lerp1),
original.Normals[triangles[j + ((singleIndex + 1) % 3)]],
original.Normals[triangles[j + ((singleIndex + 2) % 3)]],
Vector2.Lerp(original.UV[triangles[j + singleIndex]], original.UV[triangles[j + ((singleIndex + 1) % 3)]], lerp1),
original.UV[triangles[j + ((singleIndex + 1) % 3)]],
original.UV[triangles[j + ((singleIndex + 2) % 3)]]);
partMesh.AddTriangle(i,
ray1.origin + ray1.direction.normalized * enter1,
original.Vertices[triangles[j + ((singleIndex + 2) % 3)]],
ray2.origin + ray2.direction.normalized * enter2,
Vector3.Lerp(original.Normals[triangles[j + singleIndex]], original.Normals[triangles[j + ((singleIndex + 1) % 3)]], lerp1),
original.Normals[triangles[j + ((singleIndex + 2) % 3)]],
Vector3.Lerp(original.Normals[triangles[j + singleIndex]], original.Normals[triangles[j + ((singleIndex + 2) % 3)]], lerp2),
Vector2.Lerp(original.UV[triangles[j + singleIndex]], original.UV[triangles[j + ((singleIndex + 1) % 3)]], lerp1),
original.UV[triangles[j + ((singleIndex + 2) % 3)]],
Vector2.Lerp(original.UV[triangles[j + singleIndex]], original.UV[triangles[j + ((singleIndex + 2) % 3)]], lerp2));
continue;
}
}
}
partMesh.FillArrays();
return partMesh;
}
private void AddEdge(int subMesh, PartMesh partMesh, Vector3 normal, Vector3 vertex1, Vector3 vertex2, Vector2 uv1, Vector2 uv2)
{
if (!edgeSet)
{
edgeSet = true;
edgeVertex = vertex1;
edgeUV = uv1;
}
else
{
edgePlane.Set3Points(edgeVertex, vertex1, vertex2);
partMesh.AddTriangle(subMesh,
edgeVertex,
edgePlane.GetSide(edgeVertex + normal) ? vertex1 : vertex2,
edgePlane.GetSide(edgeVertex + normal) ? vertex2 : vertex1,
normal,
normal,
normal,
edgeUV,
uv1,
uv2);
}
}
public class PartMesh
{
private List<Vector3> _Verticies = new List<Vector3>();
private List<Vector3> _Normals = new List<Vector3>();
private List<List<int>> _Triangles = new List<List<int>>();
private List<Vector2> _UVs = new List<Vector2>();
public Vector3[] Vertices;
public Vector3[] Normals;
public int[][] Triangles;
public Vector2[] UV;
public GameObject GameObject;
public Bounds Bounds = new Bounds();
public PartMesh()
{
}
public void AddTriangle(int submesh, Vector3 vert1, Vector3 vert2, Vector3 vert3, Vector3 normal1, Vector3 normal2, Vector3 normal3, Vector2 uv1, Vector2 uv2, Vector2 uv3)
{
if (_Triangles.Count - 1 < submesh)
_Triangles.Add(new List<int>());
_Triangles[submesh].Add(_Verticies.Count);
_Verticies.Add(vert1);
_Triangles[submesh].Add(_Verticies.Count);
_Verticies.Add(vert2);
_Triangles[submesh].Add(_Verticies.Count);
_Verticies.Add(vert3);
_Normals.Add(normal1);
_Normals.Add(normal2);
_Normals.Add(normal3);
_UVs.Add(uv1);
_UVs.Add(uv2);
_UVs.Add(uv3);
Bounds.min = Vector3.Min(Bounds.min, vert1);
Bounds.min = Vector3.Min(Bounds.min, vert2);
Bounds.min = Vector3.Min(Bounds.min, vert3);
Bounds.max = Vector3.Min(Bounds.max, vert1);
Bounds.max = Vector3.Min(Bounds.max, vert2);
Bounds.max = Vector3.Min(Bounds.max, vert3);
}
public void FillArrays()
{
Vertices = _Verticies.ToArray();
Normals = _Normals.ToArray();
UV = _UVs.ToArray();
Triangles = new int[_Triangles.Count][];
for (var i = 0; i < _Triangles.Count; i++)
Triangles[i] = _Triangles[i].ToArray();
}
public void MakeGameobject(MeshDestroy original)
{
GameObject = new GameObject(original.name);
GameObject.transform.position = original.transform.position;
GameObject.transform.rotation = original.transform.rotation;
GameObject.transform.localScale = original.transform.localScale;
var mesh = new Mesh();
mesh.name = original.GetComponent<MeshFilter>().mesh.name;
mesh.vertices = Vertices;
mesh.normals = Normals;
mesh.uv = UV;
for(var i = 0; i < Triangles.Length; i++)
mesh.SetTriangles(Triangles[i], i, true);
Bounds = mesh.bounds;
var renderer = GameObject.AddComponent<MeshRenderer>();
renderer.materials = original.GetComponent<MeshRenderer>().materials;
var filter = GameObject.AddComponent<MeshFilter>();
filter.mesh = mesh;
var collider = GameObject.AddComponent<MeshCollider>();
collider.convex = true;
var rigidbody = GameObject.AddComponent<Rigidbody>();
var meshDestroy = GameObject.AddComponent<MeshDestroy>();
meshDestroy.CutCascades = original.CutCascades;
meshDestroy.ExplodeForce = original.ExplodeForce;
}
}
}
@IChristianPlayzI
Copy link

IChristianPlayzI commented Jun 4, 2020

so I put it on this sword i made in blender (which i imported into unity with read and write enabled), and it wont slice anything and does it have to be an fbx?

@ditzel
Copy link
Author

ditzel commented Aug 12, 2020

so I put it on this sword i made in blender (which i imported into unity with read and write enabled), and it wont slice anything and does it have to be an fbx?

yes

@RamonBeast
Copy link

RamonBeast commented Nov 16, 2020

Hi @ditzel, thanks for this great script. I am playing with it but I noticed that some fragments are created directly 50m or 60m below the origin and they keep falling forever. This happens with cascade set to 1 and explosion force set to 0 (it happens with all settings, but this is easier for me to debug) usually after the first 3 cuts. Would you have a suggestion on why it might happen? Thank you

image

@Xquality
Copy link

Xquality commented Dec 11, 2020

Hey @RamonBeast, you can add this at the top of the MakeGameobject function, right before GameObject = new GameObject(original.name);

if (Vertices.Length <= 100) return;

Just change the number to something that works for you. It all depends on how low-poly the mesh is.
I noticed that a lot of pieces generated have a mesh with 0 vertices, and that's probably what you're encountering :)

@DelapaixStudio
Copy link

DelapaixStudio commented Dec 28, 2020

Hi @ditzel, your script is working very well !
Can I use this script in my video game ?
You can contact me at delapaix@tutanota.com

@RamonBeast
Copy link

RamonBeast commented Dec 30, 2020

Hey @Xquality I eventually resolved that by quickly calculating the volume of each fragment and excluding all polygons smaller than a certain threshold, this was to prevent the generation of very thin slices that wouldn't be caught by calculating only the amount of vertices generated. Thanks a lot for your feedback!

@SwixDevs
Copy link

SwixDevs commented Mar 11, 2021

Hey @Xquality I eventually resolved that by quickly calculating the volume of each fragment and excluding all polygons smaller than a certain threshold, this was to prevent the generation of very thin slices that wouldn't be caught by calculating only the amount of vertices generated. Thanks a lot for your feedback!

Hello @RamonBeast. Do you mind providing me the edited code?

@an01f01
Copy link

an01f01 commented May 10, 2021

This is a great script, I added a variable to the MeshDestroy class
public int MaxDestroyLevel = 3;
Then modified the MakeGameObject method in the PartMesh class to destroy the meshes after they hit a random range time when they get too small. That way it doesn't keep breaking down the object into too many small parts. The changes are as follows:

    public void MakeGameobject(MeshDestroy original)
    {
        GameObject = new GameObject(original.name);
        GameObject.transform.position = original.transform.position;
        GameObject.transform.rotation = original.transform.rotation;
        GameObject.transform.localScale = original.transform.localScale;

        var mesh = new Mesh();
        mesh.name = original.GetComponent<MeshFilter>().mesh.name;

        mesh.vertices = Vertices;
        mesh.normals = Normals;
        mesh.uv = UV;
        for (var i = 0; i < Triangles.Length; i++)
            mesh.SetTriangles(Triangles[i], i, true);
        Bounds = mesh.bounds;

        var renderer = GameObject.AddComponent<MeshRenderer>();
        renderer.materials = original.GetComponent<MeshRenderer>().materials;

        var filter = GameObject.AddComponent<MeshFilter>();
        filter.mesh = mesh;

        var collider = GameObject.AddComponent<MeshCollider>();
        collider.convex = true;

        var rigidbody = GameObject.AddComponent<Rigidbody>();
        var meshDestroy = GameObject.AddComponent<MeshDestroy>();
        meshDestroy.CutCascades = original.CutCascades;
        meshDestroy.ExplodeForce = original.ExplodeForce;
        meshDestroy.MaxDestroyLevel = original.MaxDestroyLevel - 1;
        if (original.MaxDestroyLevel <= 0)
        {
            meshDestroy.enabled = false;
            Destroy(GameObject, Random.Range(5, 20));
        }

    }

@TheMightySpud
Copy link

TheMightySpud commented Jun 1, 2021

Hmm. I'm getting an 'Index was outside the bounds of the array' error on this part........

if (sideCount == 3) { partMesh.AddTriangle(i, original.Vertices[triangles[j]], original.Vertices[triangles[j + 1]], original.Vertices[triangles[j + 2]], original.Normals[triangles[j]], original.Normals[triangles[j + 1]], original.Normals[triangles[j + 2]], original.UV[triangles[j]], original.UV[triangles[j + 1]], original.UV[triangles[j + 2]]); continue; }

And not entirely sure what's going wrong. :-/

@azizbch
Copy link

azizbch commented Jul 23, 2021

I adjusted it to have a smoother and more basic version, so you can use it if you need it for mobile dev.

=> link
=> preview

hello, ty for sharing with us this , but I have a problem if i put many object like stones and I want to destroy just the stone clicked but I click in one they destroy all stones , what can i do to destroy just one stone clicked :/

@clovelt
Copy link

clovelt commented Jan 26, 2022

Hey! Found a fix for the 'Index was outside the bounds of the array' error. Probulderize the mesh and it works like magic! Tools -> Probuilder -> Object -> Probulderize

@OdisDev
Copy link

OdisDev commented Apr 3, 2022

Hi, I'm trying to turn this script into something similar of R6S which means making it react to bullets, not affected by gravity that much, and leave holes instead of crumbling. So if anyone would be kind enough to help me or redirect me about that, thx

@pearcircuitmike
Copy link

pearcircuitmike commented Apr 22, 2022

I get "ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
System.Collections.Generic.List`1[T].get_Item (System.Int32 index) (at :0)
MeshDestroy+PartMesh.AddTriangle (System.Int32 submesh, UnityEngine.Vector3 vert1, UnityEngine.Vector3 vert2, UnityEngine.Vector3 vert3, UnityEngine.Vector3 normal1, UnityEngine.Vector3 normal2, UnityEngine.Vector3 normal3, UnityEngine.Vector2 uv1, UnityEngine.Vector2 uv2, UnityEngine.Vector2 uv3) (at Assets/Scripts/Utility Scripts/MeshDestroy.cs:235)
MeshDestroy.GenerateMesh (MeshDestroy+PartMesh original, UnityEngine.Plane plane, System.Boolean left) (at Assets/Scripts/Utility Scripts/MeshDestroy.cs:98)
MeshDestroy.DestroyMesh () (at Assets/Scripts/Utility Scripts/MeshDestroy.cs:60)
MeshDestroy.Update () (at Assets/Scripts/Utility Scripts/MeshDestroy.cs:22)"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment