645 lines
22 KiB
C#
645 lines
22 KiB
C#
using System;
|
||
using System.Linq;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
#if UNITY_EDITOR
|
||
using UnityEditor;
|
||
#endif
|
||
|
||
namespace RayFire
|
||
{
|
||
|
||
|
||
[AddComponentMenu("RayFire/Rayfire Shatter")]
|
||
[HelpURL("https://rayfirestudios.com/unity-online-help/components/unity-shatter-component/")]
|
||
public class RayfireShatter : MonoBehaviour
|
||
{
|
||
public enum FragLastMode
|
||
{
|
||
New = 0,
|
||
ToLast = 1
|
||
}
|
||
|
||
// UI
|
||
public FragType type = FragType.Voronoi;
|
||
public RFVoronoi voronoi = new RFVoronoi();
|
||
public RFSplinters splinters = new RFSplinters();
|
||
public RFSplinters slabs = new RFSplinters();
|
||
public RFRadial radial = new RFRadial();
|
||
public RFHexagon hexagon = new RFHexagon();
|
||
public RFCustom custom = new RFCustom();
|
||
public RFMirrored mirrored = new RFMirrored();
|
||
public RFSlice slice = new RFSlice();
|
||
public RFBricks bricks = new RFBricks();
|
||
public RFVoxels voxels = new RFVoxels();
|
||
public RFTets tets = new RFTets();
|
||
public FragmentMode mode = FragmentMode.Editor;
|
||
public RFSurface material = new RFSurface();
|
||
public RFShatterCluster clusters = new RFShatterCluster();
|
||
public RFShatterAdvanced advanced = new RFShatterAdvanced();
|
||
public RFMeshExport export = new RFMeshExport();
|
||
|
||
// Center
|
||
public bool showCenter;
|
||
public Vector3 centerPosition;
|
||
public Quaternion centerDirection;
|
||
|
||
// Components
|
||
public Transform transForm;
|
||
public MeshFilter meshFilter;
|
||
public MeshRenderer meshRenderer;
|
||
public SkinnedMeshRenderer skinnedMeshRend;
|
||
public List<MeshFilter> meshFilters;
|
||
|
||
// Vars
|
||
[NonSerialized] Mesh[] meshes;
|
||
[NonSerialized] Vector3[] pivots;
|
||
[NonSerialized] RFDictionary[] rfOrigSubMeshIds;
|
||
public List<Transform> rootChildList = new List<Transform>();
|
||
public List<GameObject> fragmentsAll = new List<GameObject>();
|
||
public List<GameObject> fragmentsLast = new List<GameObject>();
|
||
public Material[] materials;
|
||
|
||
// Hidden
|
||
public int shatterMode = 1;
|
||
public bool colorPreview;
|
||
public bool scalePreview = true;
|
||
public float previewScale;
|
||
public float size;
|
||
public float rescaleFix = 1f;
|
||
public Vector3 originalScale;
|
||
public Bounds bound;
|
||
public bool resetState;
|
||
public bool interactive;
|
||
|
||
// Static
|
||
static float minSize = 0.01f;
|
||
|
||
/// /////////////////////////////////////////////////////////
|
||
/// Common
|
||
/// /////////////////////////////////////////////////////////
|
||
|
||
// Reset
|
||
private void Reset()
|
||
{
|
||
ResetCenter();
|
||
}
|
||
|
||
// Set default vars before fragment
|
||
void SetVariables()
|
||
{
|
||
size = 0f;
|
||
rescaleFix = 1f;
|
||
originalScale = transForm.localScale;
|
||
rfOrigSubMeshIds = null;
|
||
}
|
||
|
||
/// /////////////////////////////////////////////////////////
|
||
/// Checks
|
||
/// /////////////////////////////////////////////////////////
|
||
|
||
// Basic proceed check
|
||
bool MainCheck()
|
||
{
|
||
// Check if prefab
|
||
if (gameObject.scene.rootCount == 0)
|
||
{
|
||
Debug.Log ("RayFire Shatter: " + name + " Can't fragment prefab because prefab unable to store Unity mesh. Fragment prefab in scene.", gameObject);
|
||
return false;
|
||
}
|
||
|
||
// Single mesh mode
|
||
if (advanced.combineChildren == false)
|
||
if (SingleMeshCheck() == false)
|
||
return false;
|
||
|
||
// Multiple mesh mode
|
||
if (advanced.combineChildren == true)
|
||
{
|
||
// Has no children meshes
|
||
if (meshFilters.Count == 1)
|
||
if (SingleMeshCheck() == false)
|
||
return false;
|
||
|
||
// Remove no meshes
|
||
if (meshFilters.Count > 0)
|
||
for (int i = meshFilters.Count - 1; i >= 0; i--)
|
||
if (meshFilters[i].sharedMesh == null)
|
||
{
|
||
Debug.Log ("RayFire Shatter: " + meshFilters[i].name + " MeshFilter has no Mesh, object excluded.", meshFilters[i].gameObject);
|
||
meshFilters.RemoveAt (i);
|
||
}
|
||
|
||
// Remove no readable meshes
|
||
if (meshFilters.Count > 0)
|
||
for (int i = meshFilters.Count - 1; i >= 0; i--)
|
||
if (meshFilters[i].sharedMesh.isReadable == false)
|
||
{
|
||
Debug.Log ("RayFire Shatter: " + meshFilters[i].name + " Mesh is not Readable, object excluded.", meshFilters[i].gameObject);
|
||
meshFilters.RemoveAt (i);
|
||
}
|
||
|
||
// No meshes left
|
||
if (meshFilters.Count == 0)
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// Single mesh mode checks
|
||
bool SingleMeshCheck()
|
||
{
|
||
// No mesh storage components
|
||
if (meshFilter == null && skinnedMeshRend == null)
|
||
{
|
||
Debug.Log ("RayFire Shatter: " + name + " Object has no mesh to fragment.", gameObject);
|
||
return false;
|
||
}
|
||
|
||
// Has mesh filter
|
||
if (meshFilter != null)
|
||
{
|
||
// No shared mesh
|
||
if (meshFilter.sharedMesh == null)
|
||
{
|
||
Debug.Log ("RayFire Shatter: " + name + " Object has no mesh to fragment.", gameObject);
|
||
return false;
|
||
}
|
||
|
||
// Not readable mesh
|
||
if (meshFilter.sharedMesh.isReadable == false)
|
||
{
|
||
Debug.Log ("RayFire Shatter: " + name + "Mesh is not readable. Open Import Settings and turn On Read/Write Enabled", gameObject);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Has skinned mesh
|
||
if (skinnedMeshRend != null && skinnedMeshRend.sharedMesh == null)
|
||
{
|
||
Debug.Log ("RayFire Shatter: " + name + " Object has no mesh to fragment.", gameObject);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// /////////////////////////////////////////////////////////
|
||
/// Methods
|
||
/// /////////////////////////////////////////////////////////
|
||
|
||
// Cache variables
|
||
bool DefineComponents()
|
||
{
|
||
// Mesh storage
|
||
transForm = GetComponent<Transform>();
|
||
meshFilter = GetComponent<MeshFilter>();
|
||
meshRenderer = GetComponent<MeshRenderer>();
|
||
skinnedMeshRend = GetComponent<SkinnedMeshRenderer>();
|
||
|
||
// Multymesh fragmentation
|
||
meshFilters = new List<MeshFilter>();
|
||
if (advanced.combineChildren == true)
|
||
meshFilters = GetComponentsInChildren<MeshFilter>().ToList();
|
||
|
||
// Basic proceed check
|
||
if (MainCheck() == false)
|
||
return false;
|
||
|
||
// Mesh renderer
|
||
if (skinnedMeshRend == null)
|
||
{
|
||
if (meshRenderer == null)
|
||
meshRenderer = gameObject.AddComponent<MeshRenderer>();
|
||
bound = meshRenderer.bounds;
|
||
}
|
||
|
||
// Skinned mesh
|
||
if (skinnedMeshRend != null)
|
||
bound = skinnedMeshRend.bounds;
|
||
|
||
return true;
|
||
}
|
||
|
||
// Get bounds
|
||
public Bounds GetBound()
|
||
{
|
||
// Mesh renderer
|
||
if (meshRenderer == null)
|
||
{
|
||
meshRenderer = GetComponent<MeshRenderer>();
|
||
if (meshRenderer != null)
|
||
return meshRenderer.bounds;
|
||
}
|
||
else
|
||
return meshRenderer.bounds;
|
||
|
||
// Skinned mesh
|
||
if (skinnedMeshRend == null)
|
||
{
|
||
skinnedMeshRend = GetComponent<SkinnedMeshRenderer>();
|
||
if (skinnedMeshRend != null)
|
||
return skinnedMeshRend.bounds;
|
||
}
|
||
|
||
return new Bounds();
|
||
}
|
||
|
||
/// /////////////////////////////////////////////////////////
|
||
/// Methods
|
||
/// /////////////////////////////////////////////////////////
|
||
|
||
// Fragment this object by shatter properties List<GameObject>
|
||
public void Fragment(FragLastMode fragmentMode = FragLastMode.New)
|
||
{
|
||
// Cache variables
|
||
if (DefineComponents() == false)
|
||
return;
|
||
|
||
// Cache default vars
|
||
SetVariables();
|
||
|
||
// Check if object is too small
|
||
ScaleCheck();
|
||
|
||
// Cache
|
||
RFFragment.CacheMeshes(ref meshes, ref pivots, ref rfOrigSubMeshIds, this);
|
||
|
||
// Stop
|
||
if (meshes == null)
|
||
return;
|
||
|
||
// Create fragments
|
||
if (fragmentMode == FragLastMode.ToLast)
|
||
{
|
||
if (rootChildList[rootChildList.Count - 1] != null)
|
||
fragmentsLast = CreateFragments(rootChildList[rootChildList.Count - 1]);
|
||
else
|
||
fragmentMode = FragLastMode.New;
|
||
}
|
||
|
||
// Create new fragments
|
||
if (fragmentMode == FragLastMode.New)
|
||
fragmentsLast = CreateFragments();
|
||
|
||
// Limitation fragment
|
||
RFShatterAdvanced.Limitations(this);
|
||
|
||
// Collect to all fragments
|
||
fragmentsAll.AddRange(fragmentsLast);
|
||
|
||
// Reset original object back if it was scaled
|
||
transForm.localScale = originalScale;
|
||
}
|
||
|
||
// Create fragments by mesh and pivots array
|
||
List<GameObject> CreateFragments(Transform root = null)
|
||
{
|
||
// No mesh were cached
|
||
if (meshes == null)
|
||
return null;
|
||
|
||
// Clear array for new fragments
|
||
GameObject[] fragArray = new GameObject[meshes.Length];
|
||
|
||
// Create root object
|
||
if (root == null)
|
||
{
|
||
GameObject rootGo = new GameObject (gameObject.name + "_root");
|
||
rootGo.transform.position = transForm.position;
|
||
rootGo.transform.rotation = transForm.rotation;
|
||
rootGo.tag = gameObject.tag;
|
||
rootGo.layer = gameObject.layer;
|
||
root = rootGo.transform;
|
||
rootChildList.Add (root);
|
||
}
|
||
|
||
// Create instance for fragments
|
||
GameObject fragInstance;
|
||
if (advanced.copyComponents == true)
|
||
{
|
||
fragInstance = Instantiate(gameObject);
|
||
fragInstance.transform.rotation = Quaternion.identity;
|
||
fragInstance.transform.localScale = Vector3.one;
|
||
|
||
// Destroy shatter
|
||
DestroyImmediate(fragInstance.GetComponent<RayfireShatter>());
|
||
}
|
||
else
|
||
{
|
||
fragInstance = new GameObject();
|
||
fragInstance.AddComponent<MeshFilter>();
|
||
fragInstance.AddComponent<MeshRenderer>();
|
||
}
|
||
|
||
// Get original mats. in case of combined meshes it is already defined in CombineShatter()
|
||
if (advanced.combineChildren == false)
|
||
materials = skinnedMeshRend != null
|
||
? skinnedMeshRend.sharedMaterials
|
||
: meshRenderer.sharedMaterials;
|
||
|
||
// Vars
|
||
string baseName = gameObject.name + "_sh_";
|
||
|
||
// Create fragment objects
|
||
MeshFilter mf;
|
||
GameObject go;
|
||
MeshCollider mc;
|
||
MeshRenderer rn;
|
||
for (int i = 0; i < meshes.Length; ++i)
|
||
{
|
||
// Rescale mesh
|
||
if (rescaleFix != 1f)
|
||
RFFragment.RescaleMesh (meshes[i], rescaleFix);
|
||
|
||
// Instantiate. IMPORTANT do not parent when Instantiate
|
||
go = Instantiate(fragInstance);
|
||
go.transform.localScale = Vector3.one;
|
||
|
||
// Set multymaterial
|
||
rn = go.GetComponent<MeshRenderer>();
|
||
RFSurface.SetMaterial(rfOrigSubMeshIds, materials, material, rn, i, meshes.Length);
|
||
|
||
// Set fragment object name and tm
|
||
go.name = baseName + (i + 1);
|
||
go.transform.position = root.transform.position + (pivots[i] / rescaleFix);
|
||
go.transform.parent = root.transform;
|
||
go.tag = gameObject.tag;
|
||
go.layer = gameObject.layer;
|
||
|
||
// Set fragment mesh
|
||
mf = go.GetComponent<MeshFilter>();
|
||
mf.sharedMesh = meshes[i];
|
||
mf.sharedMesh.name = go.name;
|
||
|
||
// Set mesh collider
|
||
mc = go.GetComponent<MeshCollider>();
|
||
if (mc != null)
|
||
mc.sharedMesh = meshes[i];
|
||
|
||
// Add in array
|
||
fragArray[i] = go;
|
||
}
|
||
|
||
// Root back to original parent
|
||
root.transform.parent = transForm.parent;
|
||
|
||
// Reset scale for mesh fragments. IMPORTANT: skinned mesh fragments root should not be rescaled
|
||
if (skinnedMeshRend == null)
|
||
root.transform.localScale = Vector3.one;
|
||
|
||
// Destroy instance
|
||
DestroyImmediate(fragInstance);
|
||
|
||
// Empty lists
|
||
meshes = null;
|
||
pivots = null;
|
||
rfOrigSubMeshIds = null;
|
||
|
||
return fragArray.ToList();
|
||
}
|
||
|
||
// Fragment by limitations
|
||
public void LimitationFragment(int ind)
|
||
{
|
||
RayfireShatter shat = fragmentsLast[ind].AddComponent<RayfireShatter>();
|
||
shat.voronoi.amount = 10;
|
||
|
||
shat.Fragment ();
|
||
|
||
if (shat.fragmentsLast.Count > 0)
|
||
{
|
||
fragmentsLast.AddRange (shat.fragmentsLast);
|
||
DestroyImmediate (shat.gameObject);
|
||
fragmentsLast.RemoveAt (ind);
|
||
|
||
// Parent and destroy root
|
||
foreach (var frag in shat.fragmentsLast)
|
||
frag.transform.parent = rootChildList[rootChildList.Count - 1];
|
||
DestroyImmediate (shat.rootChildList[rootChildList.Count - 1].gameObject);
|
||
}
|
||
}
|
||
|
||
/// /////////////////////////////////////////////////////////
|
||
/// Deleting
|
||
/// /////////////////////////////////////////////////////////
|
||
|
||
// Delete fragments from last Fragment method
|
||
public void DeleteFragmentsLast(int destroyMode = 0)
|
||
{
|
||
// Destroy last fragments
|
||
if (destroyMode == 1)
|
||
for (int i = fragmentsLast.Count - 1; i >= 0; i--)
|
||
if (fragmentsLast[i] != null)
|
||
DestroyImmediate (fragmentsLast[i]);
|
||
|
||
// Clean fragments list pre
|
||
fragmentsLast.Clear();
|
||
for (int i = fragmentsAll.Count - 1; i >= 0; i--)
|
||
if (fragmentsAll[i] == null)
|
||
fragmentsAll.RemoveAt (i);
|
||
|
||
// Check for all roots
|
||
for (int i = rootChildList.Count - 1; i >= 0; i--)
|
||
if (rootChildList[i] == null)
|
||
rootChildList.RemoveAt (i);
|
||
|
||
// No roots
|
||
if (rootChildList.Count == 0)
|
||
return;
|
||
|
||
// Destroy with root
|
||
if (destroyMode == 0)
|
||
{
|
||
// Destroy root with fragments
|
||
DestroyImmediate (rootChildList[rootChildList.Count - 1].gameObject);
|
||
|
||
// Remove from list
|
||
rootChildList.RemoveAt (rootChildList.Count - 1);
|
||
}
|
||
|
||
// Clean all fragments list post
|
||
for (int i = fragmentsAll.Count - 1; i >= 0; i--)
|
||
if (fragmentsAll[i] == null)
|
||
fragmentsAll.RemoveAt (i);
|
||
}
|
||
|
||
// Delete all fragments and roots
|
||
public void DeleteFragmentsAll()
|
||
{
|
||
// Clear lists
|
||
fragmentsLast.Clear();
|
||
fragmentsAll.Clear();
|
||
|
||
// Check for all roots
|
||
for (int i = rootChildList.Count - 1; i >= 0; i--)
|
||
if (rootChildList[i] != null)
|
||
DestroyImmediate(rootChildList[i].gameObject);
|
||
rootChildList.Clear();
|
||
}
|
||
|
||
// Reset center helper
|
||
public void ResetCenter()
|
||
{
|
||
centerPosition = Vector3.zero;
|
||
centerDirection = Quaternion.identity;
|
||
|
||
Renderer rend = GetComponent<Renderer>();
|
||
if (rend != null)
|
||
centerPosition = transform.InverseTransformPoint (rend.bounds.center);
|
||
}
|
||
|
||
/// /////////////////////////////////////////////////////////
|
||
/// Scale
|
||
/// /////////////////////////////////////////////////////////
|
||
|
||
// Check if object is too small
|
||
void ScaleCheck()
|
||
{
|
||
// Geе size from renderers
|
||
if (meshRenderer != null)
|
||
size = meshRenderer.bounds.size.magnitude;
|
||
if (skinnedMeshRend != null)
|
||
size = skinnedMeshRend.bounds.size.magnitude;
|
||
|
||
// Get rescaleFix if too small
|
||
if (size != 0f && size < minSize)
|
||
{
|
||
// Get rescaleFix factor
|
||
rescaleFix = 1f / size;
|
||
|
||
// Scale small object up to shatter
|
||
Vector3 newScale = transForm.localScale * rescaleFix;
|
||
transForm.localScale = newScale;
|
||
|
||
// Warning
|
||
Debug.Log ("Warning. Object " + name + " is too small.");
|
||
}
|
||
}
|
||
|
||
// Reset original object and fragments scale
|
||
public void ResetScale (float scaleValue)
|
||
{
|
||
// Reset scale
|
||
if (resetState == true && scaleValue == 0f)
|
||
{
|
||
if (skinnedMeshRend != null)
|
||
skinnedMeshRend.enabled = true;
|
||
|
||
if (meshRenderer != null)
|
||
meshRenderer.enabled = true;
|
||
|
||
if (fragmentsLast.Count > 0)
|
||
foreach (GameObject fragment in fragmentsLast)
|
||
if (fragment != null)
|
||
fragment.transform.localScale = Vector3.one;
|
||
|
||
resetState = false;
|
||
}
|
||
}
|
||
|
||
/// /////////////////////////////////////////////////////////
|
||
/// Copy
|
||
/// /////////////////////////////////////////////////////////
|
||
|
||
// Copy shatter component
|
||
public static void CopyRootMeshShatter (RayfireRigid source, List<RayfireRigid> targets)
|
||
{
|
||
// No shatter
|
||
if (source.meshDemolition.sht == null)
|
||
return;
|
||
|
||
// Copy shatter
|
||
for (int i = 0; i < targets.Count; i++)
|
||
{
|
||
targets[i].meshDemolition.sht = targets[i].gameObject.AddComponent<RayfireShatter>();
|
||
targets[i].meshDemolition.sht.CopyFrom (source.meshDemolition.sht);
|
||
}
|
||
}
|
||
|
||
// Copy from
|
||
void CopyFrom (RayfireShatter shatter)
|
||
{
|
||
type = shatter.type;
|
||
|
||
voronoi = new RFVoronoi(shatter.voronoi);
|
||
splinters = new RFSplinters(shatter.splinters);
|
||
slabs = new RFSplinters(shatter.slabs);
|
||
radial = new RFRadial(shatter.radial);
|
||
custom = new RFCustom(shatter.custom);
|
||
slice = new RFSlice(shatter.slice);
|
||
tets = new RFTets(shatter.tets);
|
||
|
||
mode = shatter.mode;
|
||
material.CopyFrom (shatter.material);
|
||
clusters = new RFShatterCluster(shatter.clusters);
|
||
advanced = new RFShatterAdvanced(shatter.advanced);
|
||
}
|
||
|
||
|
||
|
||
/// /////////////////////////////////////////////////////////
|
||
/// Interactive
|
||
/// /////////////////////////////////////////////////////////
|
||
|
||
public void InteractiveStart()
|
||
{
|
||
// Create shatter
|
||
// Cache meshes
|
||
// Weld
|
||
// Save original
|
||
// Set welded mesh
|
||
}
|
||
|
||
public void InteractiveStop()
|
||
{
|
||
// Set original mesh
|
||
// Destroy shatter
|
||
}
|
||
|
||
public void InteractiveChange()
|
||
{
|
||
if (interactive == false)
|
||
return;
|
||
|
||
// update shatter with new props
|
||
// cache new meshes
|
||
// weld RFShatter.WeldMeshes ()
|
||
// set welded mesh
|
||
}
|
||
|
||
/*
|
||
enum PrefabMode
|
||
{
|
||
Scene,
|
||
Asset,
|
||
PrefabEditingMode
|
||
}
|
||
|
||
// Get prefab mode
|
||
PrefabMode GetPrefabMode (GameObject go)
|
||
{
|
||
// scene, prefab, mode
|
||
// Debug.Log (go.scene.path); // fullpath.unity, null, ""
|
||
// Debug.Log (go.scene.name); // scene name, null, box_pf
|
||
// Debug.Log (go.scene.rootCount); // 4, 0, 1
|
||
// Debug.Log (go.scene.isLoaded); // true, false, true
|
||
// Debug.Log (go.scene.IsValid()); // true, false, true
|
||
// return PrefabMode.Asset;
|
||
|
||
// Prefab is asset
|
||
if (go.scene.path.EndsWith(".prefab"))
|
||
return PrefabMode.Asset;
|
||
|
||
// Prefab is in editing mode
|
||
if (string.IsNullOrEmpty(go.scene.path))
|
||
return PrefabMode.PrefabEditingMode;
|
||
|
||
// Prefab is in scene
|
||
return PrefabMode.Scene;
|
||
}
|
||
*/
|
||
}
|
||
} |