using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
#if UNITY_EDITOR
using UnityEditor;
#endif
public static class Fragmenter
{
///
/// Generates the mesh fragments based on the provided options. The generated fragment objects are
/// stored as children of `fragmentParent`
///
/// The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider.
/// Options for the fragmenter
/// The template GameObject that each fragment will clone
/// The parent transform for the fragment objects
/// If true, the generated fragment meshes will be saved to disk so they can be re-used in prefabs.
/// The save location for the fragments.
///
public static void Fracture(GameObject sourceObject,
FractureOptions options,
GameObject fragmentTemplate,
Transform parent,
bool saveToDisk = false,
string saveFolderPath = "")
{
// Define our source mesh data for the fracturing
FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent().sharedMesh);
// We begin by fragmenting the source mesh, then process each fragment in a FIFO queue
// until we achieve the target fragment count.
var fragments = new Queue();
fragments.Enqueue(sourceMesh);
// Subdivide the mesh into multiple fragments until we reach the fragment limit
FragmentData topSlice, bottomSlice;
while (fragments.Count < options.fragmentCount)
{
FragmentData meshData = fragments.Dequeue();
meshData.CalculateBounds();
// Select an arbitrary fracture plane normal
Vector3 normal = new Vector3(
options.xAxis ? Random.Range(-1f, 1f) : 0f,
options.yAxis ? Random.Range(-1f, 1f) : 0f,
options.zAxis ? Random.Range(-1f, 1f) : 0f);
// Slice and dice!
MeshSlicer.Slice(meshData,
normal,
meshData.Bounds.center,
options.textureScale,
options.textureOffset,
out topSlice,
out bottomSlice);
fragments.Enqueue(topSlice);
fragments.Enqueue(bottomSlice);
}
int i = 0;
foreach(FragmentData meshData in fragments)
{
CreateFragment(meshData,
sourceObject,
fragmentTemplate,
parent,
saveToDisk,
saveFolderPath,
options.detectFloatingFragments,
ref i);
}
}
///
/// Asynchronously generates the mesh fragments based on the provided options. The generated fragment objects are
/// stored as children of `fragmentParent`
///
/// The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider.
/// Options for the fragmenter
/// The template GameObject that each fragment will clone
/// The parent transform for the fragment objects
///
public static IEnumerator FractureAsync(GameObject sourceObject,
FractureOptions options,
GameObject fragmentTemplate,
Transform parent,
Action onCompletion)
{
// Define our source mesh data for the fracturing
FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent().sharedMesh);
// We begin by fragmenting the source mesh, then process each fragment in a FIFO queue
// until we achieve the target fragment count.
var fragments = new Queue();
fragments.Enqueue(sourceMesh);
// Subdivide the mesh into multiple fragments until we reach the fragment limit
FragmentData topSlice, bottomSlice;
while (fragments.Count < options.fragmentCount)
{
FragmentData meshData = fragments.Dequeue();
meshData.CalculateBounds();
// Select an arbitrary fracture plane normal
Vector3 normal = new Vector3(
options.xAxis ? Random.Range(-1f, 1f) : 0f,
options.yAxis ? Random.Range(-1f, 1f) : 0f,
options.zAxis ? Random.Range(-1f, 1f) : 0f);
// Slice and dice!
MeshSlicer.Slice(meshData,
normal,
meshData.Bounds.center,
options.textureScale,
options.textureOffset,
out topSlice,
out bottomSlice);
// Perform next slice on the next frame
yield return null;
fragments.Enqueue(topSlice);
fragments.Enqueue(bottomSlice);
}
int i = 0;
foreach(FragmentData meshData in fragments)
{
CreateFragment(meshData,
sourceObject,
fragmentTemplate,
parent,
false,
"",
options.detectFloatingFragments,
ref i);
}
onCompletion?.Invoke();
}
///
/// Generates the mesh fragments based on the provided options. The generated fragment objects are
/// stored as children of `fragmentParent`
///
/// The source object to slice. This object must have a MeshFilter, a RigidBody and a Collider.
/// The normal of the cut plane in the local frame of sourceObject.
/// The origin of the cut plane in the local frame of sourceObject.
/// Options for the slicer
/// The template GameObject that each slice will clone
/// The parent transform for the fragment objects
///
public static void Slice(GameObject sourceObject,
Vector3 sliceNormal,
Vector3 sliceOrigin,
SliceOptions options,
GameObject fragmentTemplate,
Transform parent)
{
// Define our source mesh data for the fracturing
FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent().sharedMesh);
// Subdivide the mesh into multiple fragments until we reach the fragment limit
FragmentData topSlice, bottomSlice;
// Slice and dice!
MeshSlicer.Slice(sourceMesh,
sliceNormal,
sliceOrigin,
options.textureScale,
options.textureOffset,
out topSlice,
out bottomSlice);
int i = 0;
CreateFragment(topSlice,
sourceObject,
fragmentTemplate,
parent,
false,
"",
options.detectFloatingFragments,
ref i);
CreateFragment(bottomSlice,
sourceObject,
fragmentTemplate,
parent,
false,
"",
options.detectFloatingFragments,
ref i);
}
///
/// Creates a new GameObject from the fragment data
///
/// Geometry of the fragment produced by the slicer
/// The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider.
/// The template GameObject that each fragment will clone
/// The parent transform for the fragment objects
/// Fragment counter
private static void CreateFragment(FragmentData fragmentMeshData,
GameObject sourceObject,
GameObject fragmentTemplate,
Transform parent,
bool saveToDisk,
string saveFolderPath,
bool detectFloatingFragments,
ref int i)
{
// If there is no mesh data, don't create an object
if (fragmentMeshData.Triangles.Length == 0)
{
return;
}
Mesh[] meshes;
Mesh fragmentMesh = fragmentMeshData.ToMesh();
// If the "Detect Floating Fragments" option is enabled, take the fragment mesh and
// identify disconnected sets of geometry within it, treating each of these as a
// separate physical object
if (detectFloatingFragments)
{
meshes = MeshUtils.FindDisconnectedMeshes(fragmentMesh);
}
else
{
meshes = new Mesh[] { fragmentMesh };
}
var parentSize = sourceObject.GetComponent().sharedMesh.bounds.size;
var parentMass = sourceObject.GetComponent().mass;
for(int k = 0; k < meshes.Length; k++)
{
GameObject fragment = GameObject.Instantiate(fragmentTemplate, parent);
fragment.name = $"Fragment{i}";
fragment.transform.localPosition = Vector3.zero;
fragment.transform.localRotation = Quaternion.identity;
fragment.transform.localScale = sourceObject.transform.localScale;
meshes[k].name = System.Guid.NewGuid().ToString();
// Update mesh to the new sliced mesh
var meshFilter = fragment.GetComponent();
meshFilter.sharedMesh = meshes[k];
var collider = fragment.GetComponent();
// If fragment collisions are disabled, collider will be null
collider.sharedMesh = meshes[k];
collider.convex = true;
collider.sharedMaterial = fragment.GetComponent().sharedMaterial;
// Compute mass of the sliced object by dividing mesh bounds by density
var parentRigidBody = sourceObject.GetComponent();
var rigidBody = fragment.GetComponent();
var size = fragmentMesh.bounds.size;
float density = (parentSize.x * parentSize.y * parentSize.z) / parentMass;
rigidBody.mass = (size.x * size.y * size.z) / density;
// This code only compiles for the editor
#if UNITY_EDITOR
if (saveToDisk)
{
string path = $"{saveFolderPath}/{meshes[k].name}.asset";
AssetDatabase.CreateAsset(meshes[k], path);
}
#endif
i++;
}
}
}