Skip to content

Instantly share code, notes, and snippets.

@ditzel
Created August 19, 2019 19:02
Star You must be signed in to star a gist
Save ditzel/73f4d1c9028cc3477bb921974f84ed56 to your computer and use it in GitHub Desktop.
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

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

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

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

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

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

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

@Gnomge
Copy link

Gnomge 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

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)"

@luskos
Copy link

luskos commented May 26, 2023

If you need that onto more than one object get the script on all of them and introduce a way to let's say check collision and manually turn the script on to the colliding object, else it cuts all meshes that have the script on.

@ICOnce
Copy link

ICOnce commented Sep 18, 2023

I was trying to make this work with my mouse so that I took the previous mouse position (mouse down) and the one new mouse position (mouse up) but it keeps making things that are so small that they just fall infinitly is there a way to make this actually make cuts of the mesh instead of stupidly small ones?

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