// 27 Slicer
// Copyright 2021 Deftly Games
// https://slicer.deftly.games/
using Slicer.Core;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using UnityEngine;
namespace Slicer
{
///
/// This component will search for any Colliders that are a descendant of this component. It will then slice any valid types of Collider.
///
///
///
[REFERENCE MANUAL](xref:manual\components\collider_slicer_component)
///
[ExecuteAlways]
[DisallowMultipleComponent]
[AddComponentMenu("Slicer/Collider Slicer Component")]
[HelpURL(SlicerConfiguration.SiteUrl + SlicerConfiguration.ComponentsManualPath + "collider_slicer_component.html")]
public class ColliderSlicerComponent : SlicerComponent
{
///
/// Should the vertices of the mesh colliders be sliced.
///
/// false by default.
[Tooltip("Should the vertices of the mesh colliders be sliced.")]
public bool SkipVertices = false;
// Unity serialization has no support for polymorphism, as a work around we have a list of each derived type.
// Lots of extra work needs to be done because of this.
//
// https://docs.unity3d.com/2019.4/Documentation/Manual/script-Serialization.html
[HideInInspector, SerializeField]
private List meshColliderDetailsList = new List();
[HideInInspector, SerializeField]
private List meshSlicerManagedMeshColliderDetailsList = new List();
[HideInInspector, SerializeField]
private List boxColliderDetailsList = new List();
[HideInInspector, SerializeField]
private List unsupportedColliderDetailsList = new List();
///
/// A read only collection of Mesh Colliders that are being managed by this .
///
public ReadOnlyCollection MeshColliderDetailsList { get { return meshColliderDetailsList.AsReadOnly(); } }
///
/// A read only collection of Mesh Colliders that are being tracked by this Collider Slicer and Managed by a .
///
public ReadOnlyCollection MeshSlicerManagedMeshColliderDetailsList { get { return meshSlicerManagedMeshColliderDetailsList.AsReadOnly(); } }
///
/// A read only collection of Box Colliders that are being managed by this .
///
public ReadOnlyCollection BoxColliderDetailsList { get { return boxColliderDetailsList.AsReadOnly(); } }
///
/// A read only collection of Unsupported Colliders that are being tracked by this .
///
public ReadOnlyCollection UnsupportedColliderDetailsList { get { return unsupportedColliderDetailsList.AsReadOnly(); } }
private IEnumerable allColliderDetails => meshColliderDetailsList.Cast()
.Concat(meshSlicerManagedMeshColliderDetailsList)
.Concat(boxColliderDetailsList)
.Concat(unsupportedColliderDetailsList);
private MeshSlicerComponent meshSlicerSibling;
private void Awake()
{
meshSlicerSibling = GetComponent();
}
private void OnDestroy()
{
if (Application.isPlaying)
{
foreach (var cd in allColliderDetails)
{
if (cd == null)
{
continue;
}
cd.Destroy();
}
}
}
///
public override void PreGatherDetails()
{
#if UNITY_EDITOR
if (Application.isPlaying)
{
meshSlicerSibling = GetComponent();
}
#endif
foreach (var cd in allColliderDetails)
{
if (cd == null)
{
continue;
}
cd.Remove = true;
}
}
///
public override void GatherDetails(Transform childTransform, Transform rootTransform)
{
TempCollections.Colliders.Clear();
childTransform.GetComponents(TempCollections.Colliders);
foreach (var childCollider in TempCollections.Colliders)
{
GatherDetails(childCollider, childTransform, rootTransform);
}
TempCollections.Colliders.Clear();
}
private void GatherDetails(Collider childCollider, Transform childTransform, Transform rootTransform)
{
if (childCollider == null || childCollider.enabled == false)
{
return;
}
if (childCollider is MeshCollider meshCollider)
{
var mcd = GetColliderDetails(meshColliderDetailsList, childCollider, childTransform);
var msmmcd = GetColliderDetails(meshSlicerManagedMeshColliderDetailsList, childCollider, childTransform);
if (msmmcd != null && meshSlicerSibling == null)
{
// This collider was previously managed by a mesh slicer, but it was probably destroyed.
if (msmmcd.MeshSlicerDetails != null)
{
meshCollider.sharedMesh = msmmcd.MeshSlicerDetails.OriginalSharedMesh;
}
msmmcd = null;
}
else if (meshCollider.sharedMesh == null)
{
return;
}
Mesh meshToSearchWith;
if (msmmcd != null && msmmcd.MeshSlicerDetails != null)
{
if (msmmcd.MeshSlicerDetails.SlicedMesh != msmmcd.MeshCollider.sharedMesh && SlicingEnabled)
{
meshToSearchWith = meshCollider.sharedMesh;
}
else if (msmmcd.MeshSlicerDetails.OriginalSharedMesh != msmmcd.MeshCollider.sharedMesh && !SlicingEnabled)
{
meshToSearchWith = meshCollider.sharedMesh;
}
else
{
meshToSearchWith = msmmcd.MeshSlicerDetails.OriginalSharedMesh;
}
}
else if (mcd != null)
{
if (mcd.SlicedMesh != mcd.MeshCollider.sharedMesh && SlicingEnabled)
{
meshToSearchWith = meshCollider.sharedMesh;
}
else if (mcd.OriginalSharedMesh != mcd.MeshCollider.sharedMesh && !SlicingEnabled)
{
meshToSearchWith = meshCollider.sharedMesh;
}
else
{
meshToSearchWith = mcd.OriginalSharedMesh;
}
}
else
{
var slicedMeshInParent = GetSlicedMeshInParent(rootTransform, meshCollider.sharedMesh);
if (slicedMeshInParent != null)
{
meshToSearchWith = slicedMeshInParent;
}
else
{
meshToSearchWith = meshCollider.sharedMesh;
}
}
MeshDetails md = null;
if (meshSlicerSibling != null)
{
// See if there is already a Mesh Slicer that is managing this mesh
md = meshSlicerSibling.GetMeshDetailsByMesh(childTransform, meshToSearchWith);
if ((md != null && md.MeshRenderer != null && !md.MeshRenderer.enabled) || (meshSlicerSibling != null && !meshSlicerSibling.SlicingEnabled && SlicingEnabled))
{
// This collider was previously managed by a mesh slicer, but it is not anymore.
meshCollider.sharedMesh = md.OriginalSharedMesh;
md = null;
}
}
if (md == null)
{
MeshColliderDetails_GatherChildDetails(mcd, meshCollider, childTransform, rootTransform);
}
else
{
MeshSlicerManagedMeshCollider_GatherChildDetails(msmmcd, md, meshCollider, childTransform);
}
}
else if (childCollider is BoxCollider boxCollider)
{
var bcd = GetColliderDetails(boxColliderDetailsList, boxCollider, childTransform);
BoxColliderDetails_GatherChildDetails(bcd, boxCollider, childTransform, rootTransform);
}
else
{
var ucd = GetColliderDetails(unsupportedColliderDetailsList, childCollider, childTransform);
UnsupportedColliderDetails_GatherChildDetails(ucd, childCollider, childTransform);
}
}
private static MeshColliderDetails GetColliderDetails(List colliderDetailsList, Collider childCollider, Transform childTransform)
{
var cd = colliderDetailsList.FirstOrDefault(e => e != null && e.Transform == childTransform && e.MeshCollider == childCollider);
return cd;
}
private static MeshSlicerManagedMeshColliderDetails GetColliderDetails(List colliderDetailsList, Collider childCollider, Transform childTransform)
{
var cd = colliderDetailsList.FirstOrDefault(e => e != null && e.Transform == childTransform && e.MeshCollider == childCollider);
return cd;
}
private static BoxColliderDetails GetColliderDetails(List colliderDetailsList, Collider childCollider, Transform childTransform)
{
var cd = colliderDetailsList.FirstOrDefault(e => e != null && e.Transform == childTransform && e.BoxCollider == childCollider);
return cd;
}
private static UnsupportedColliderDetails GetColliderDetails(List colliderDetailsList, Collider childCollider, Transform childTransform)
{
var cd = colliderDetailsList.FirstOrDefault(e => e != null && e.Transform == childTransform && (e.Collider == childCollider));
return cd;
}
///
/// Gathers details for Mesh Collider whose mesh is managed by a Mesh Slicer already
///
private void MeshSlicerManagedMeshCollider_GatherChildDetails(MeshSlicerManagedMeshColliderDetails mcd, MeshDetails md, MeshCollider meshCollider, Transform childTransform)
{
if (mcd == null)
{
mcd = new MeshSlicerManagedMeshColliderDetails();
mcd.Id = meshCollider.GetInstanceID();
mcd.Transform = childTransform;
mcd.MeshCollider = meshCollider;
mcd.MeshSlicerDetails = md;
meshSlicerManagedMeshColliderDetailsList.Add(mcd);
}
else if (mcd.Id != meshCollider.GetInstanceID())
{
mcd.Id = meshCollider.GetInstanceID();
mcd.Transform = childTransform;
mcd.MeshCollider = meshCollider;
mcd.MeshSlicerDetails = md;
mcd.ResetHashes();
}
else if (mcd.MeshSlicerDetails != md)
{
mcd.MeshSlicerDetails = md;
mcd.ResetHashes();
}
if (mcd.MeshSlicerDetails != null && mcd.MeshSlicerDetails.OriginalBounds.HasValue)
{
mcd.OriginalBounds = mcd.MeshSlicerDetails.OriginalBounds.Value;
}
if (SlicingEnabled)
{
if (mcd.MeshCollider.sharedMesh != mcd.MeshSlicerDetails.SlicedMesh)
{
mcd.EnableSlicing();
}
}
else
{
if (mcd.MeshCollider.sharedMesh != mcd.MeshSlicerDetails.OriginalSharedMesh)
{
mcd.DisableSlicing();
}
}
mcd.Remove = false;
}
///
/// Gathers details for a Mesh Collider with its own distinct mesh
///
private void MeshColliderDetails_GatherChildDetails(MeshColliderDetails mcd, MeshCollider meshCollider, Transform childTransform, Transform rootTransform)
{
if (mcd == null)
{
if (meshCollider.sharedMesh == null)
{
return;
}
mcd = new MeshColliderDetails();
mcd.Id = meshCollider.GetInstanceID();
mcd.Transform = childTransform;
mcd.MeshCollider = meshCollider;
// See if the shared mesh for this model is set as modified mesh for a different model
// This will happen if a model is duplicated or instanced
var copiedModifiedMesh = meshColliderDetailsList.FirstOrDefault(e => e.SlicedMesh == meshCollider.sharedMesh);
if (copiedModifiedMesh != null && copiedModifiedMesh.OriginalSharedMesh != null)
{
mcd.OriginalSharedMesh = copiedModifiedMesh.OriginalSharedMesh;
}
else
{
var slicedMeshInParent = GetSlicedMeshInParent(rootTransform, meshCollider.sharedMesh);
if (slicedMeshInParent != null)
{
mcd.OriginalSharedMesh = slicedMeshInParent;
}
else
{
mcd.OriginalSharedMesh = meshCollider.sharedMesh;
}
}
if (mcd.OriginalSharedMesh != null && mcd.OriginalSharedMesh.isReadable)
{
CopySharedMeshToSlicedMesh(mcd);
}
meshColliderDetailsList.Add(mcd);
}
else if (mcd.Id != meshCollider.GetInstanceID())
{
mcd.Id = meshCollider.GetInstanceID();
mcd.Transform = childTransform;
mcd.MeshCollider = meshCollider;
if (mcd.OriginalSharedMesh != null && mcd.SlicedMesh == null && mcd.OriginalSharedMesh.isReadable)
{
CopySharedMeshToSlicedMesh(mcd);
}
}
else if (((mcd.SlicedMesh != mcd.MeshCollider.sharedMesh && SlicingEnabled) || // The user just replaced the mesh while slicing is enabled
(mcd.MeshCollider.sharedMesh != mcd.OriginalSharedMesh && !SlicingEnabled)) && // The user just replaced the mesh while slicing is disabled
(mcd.MeshCollider.sharedMesh == null || mcd.MeshCollider.sharedMesh.isReadable))
{
mcd.OriginalSharedMesh = mcd.MeshCollider.sharedMesh;
CopySharedMeshToSlicedMesh(mcd);
}
else if (mcd.SlicedMesh == null && mcd.OriginalSharedMesh != null && mcd.OriginalSharedMesh.isReadable)
{
// The mesh collider details has a original mesh, but no mesh to slice
CopySharedMeshToSlicedMesh(mcd);
}
if (mcd.OriginalSharedMesh == null)
{
return;
}
if (mcd.OriginalSharedMesh.isReadable)
{
mcd.OriginalBounds = MeshUtility.CalculateBounds(mcd.OriginalSharedMesh, mcd.Transform, rootTransform);
}
if (SlicingEnabled)
{
if (mcd.MeshCollider.sharedMesh != mcd.SlicedMesh)
{
mcd.EnableSlicing();
}
}
else
{
if (mcd.MeshCollider.sharedMesh != mcd.OriginalSharedMesh)
{
mcd.DisableSlicing();
}
}
mcd.Remove = false;
}
private Mesh GetSlicedMeshInParent(Transform transform, Mesh mesh)
{
var parentColliderSlicer = GetColliderSlicerInParent(transform);
if (parentColliderSlicer != null)
{
var mcd = parentColliderSlicer.meshColliderDetailsList.FirstOrDefault(e => e.SlicedMesh == mesh);
if (mcd != null && mcd.OriginalSharedMesh != null)
{
return mcd.OriginalSharedMesh;
}
var msmmcd = parentColliderSlicer.meshSlicerManagedMeshColliderDetailsList.FirstOrDefault(e => e.MeshSlicerDetails.SlicedMesh == mesh);
if (msmmcd != null && msmmcd.MeshSlicerDetails.OriginalSharedMesh != null)
{
return msmmcd.MeshSlicerDetails.OriginalSharedMesh;
}
}
return null;
}
private ColliderSlicerComponent GetColliderSlicerInParent(Transform transform)
{
var parentTransform = transform.parent;
if (parentTransform == null)
{
return null;
}
var colliderSlicer = parentTransform.GetComponent();
if (colliderSlicer != null)
{
return colliderSlicer;
}
return GetColliderSlicerInParent(parentTransform);
}
private void CopySharedMeshToSlicedMesh(MeshColliderDetails mcd)
{
mcd.DestroySlicedMesh();
if (mcd.OriginalSharedMesh == null)
{
mcd.SlicedMesh = null;
}
else
{
mcd.SlicedMesh = MeshUtility.CopyMesh(mcd.OriginalSharedMesh);
}
mcd.ResetHashes();
}
///
/// Gathers details for a Box Collider
///
private void BoxColliderDetails_GatherChildDetails(BoxColliderDetails bcd, BoxCollider collider, Transform childTransform, Transform rootTransform)
{
if (bcd == null)
{
bcd = new BoxColliderDetails();
bcd.Id = collider.GetInstanceID();
bcd.Transform = childTransform;
bcd.BoxCollider = collider;
if (SlicingEnabled)
{
// This collider was made outside of edit mode
// When unity makes a slicer it likes to auto fit the collider
// But that auto fit would be for the sliced version of the mesh
// We want to auto fit to the unsliced version
if (meshSlicerSibling != null && collider.center == Vector3.zero && collider.size == Vector3.one)
{
var md = meshSlicerSibling.GetMeshDetailsByTransform(childTransform);
if (md.OriginalSharedMesh)
{
collider.center = md.OriginalSharedMesh.bounds.center;
collider.size = md.OriginalSharedMesh.bounds.size;
}
}
}
bcd.OriginalColliderProperties = BoundsUtility.AsBounds(collider);
boxColliderDetailsList.Add(bcd);
}
else if (bcd.BoxCollider != null && !SlicingEnabled)
{
bcd.OriginalColliderProperties = BoundsUtility.AsBounds(collider);
}
bcd.OriginalBounds = BoundsUtility.CalculateBounds(bcd.OriginalColliderProperties, childTransform, rootTransform);
bcd.Remove = false;
}
///
/// Gathers details for a Unsupported Collider
///
private void UnsupportedColliderDetails_GatherChildDetails(UnsupportedColliderDetails ucd, Collider collider, Transform childTransform)
{
if (ucd == null)
{
ucd = new UnsupportedColliderDetails();
ucd.Id = collider.GetInstanceID();
ucd.Transform = childTransform;
ucd.Collider = collider;
unsupportedColliderDetailsList.Add(ucd);
}
else if (ucd.Id != collider.GetInstanceID())
{
ucd.Id = collider.GetInstanceID();
ucd.Transform = childTransform;
ucd.Collider = collider;
}
else if (ucd.Collider != collider)
{
ucd.Collider = collider;
}
ucd.Remove = false;
}
///
public override Hash128 PostGatherDetails()
{
var hash = base.PostGatherDetails();
var skipVertHash = HashUtility.CalculateHash(SkipVertices, 3);
HashUtility.AppendHash(skipVertHash, ref hash);
Hash128 tempHash;
tempHash = PostGatherDetails(meshColliderDetailsList);
HashUtility.AppendHash(tempHash, ref hash);
tempHash = PostGatherDetails(meshSlicerManagedMeshColliderDetailsList);
HashUtility.AppendHash(tempHash, ref hash);
tempHash = PostGatherDetails(boxColliderDetailsList);
HashUtility.AppendHash(tempHash, ref hash);
tempHash = PostGatherDetails(unsupportedColliderDetailsList);
HashUtility.AppendHash(tempHash, ref hash);
return hash;
}
private static Hash128 PostGatherDetails(List colliderDetailsList) where T : ColliderDetails
{
Hash128 hash = new Hash128();
for (int i = colliderDetailsList.Count - 1; i >= 0; i--)
{
var cd = colliderDetailsList[i];
if (cd == null)
{
colliderDetailsList.RemoveAt(i);
continue;
}
if (!cd.Remove)
{
var tempHash = cd.CalculateHash();
HashUtility.AppendHash(tempHash, ref hash);
continue;
}
cd.DisableSlicing();
cd.Destroy();
colliderDetailsList.RemoveAt(i);
}
return hash;
}
///
public override Bounds? CalculateBounds()
{
if (SkipBoundsCalculation)
{
return null;
}
Bounds? encapsulatedBounds = null;
foreach (var md in allColliderDetails)
{
encapsulatedBounds = BoundsUtility.Encapsulate(encapsulatedBounds, md.OriginalBounds);
}
return encapsulatedBounds;
}
///
public override void Slice(Vector3 size, Transform rootTransform, Bounds completeBounds, Bounds slicedBounds, Vector3 slices)
{
foreach (var msmmcd in meshSlicerManagedMeshColliderDetailsList)
{
if (msmmcd.MeshCollider == null || msmmcd.MeshSlicerDetails == null)
{
continue;
}
if (msmmcd.LastVertHash == msmmcd.MeshSlicerDetails.SlicedVertHash && SlicerConfiguration.SkipUnmodifiedSlices)
{
continue;
}
msmmcd.MeshCollider.sharedMesh = msmmcd.MeshSlicerDetails.SlicedMesh;
msmmcd.LastVertHash = msmmcd.MeshSlicerDetails.SlicedVertHash;
}
foreach (var mcd in meshColliderDetailsList)
{
if (mcd.OriginalSharedMesh == null)
{
continue;
}
if (!mcd.OriginalSharedMesh.isReadable)
{
continue;
}
var vertHash = SliceUtility.SliceVerts(mcd.OriginalSharedMesh, mcd.SlicedMesh, mcd.Transform, size, rootTransform, completeBounds, slicedBounds, SkipVertices, mcd.SlicedVertHash);
if (vertHash != mcd.SlicedVertHash || !SlicerConfiguration.SkipUnmodifiedSlices)
{
mcd.SlicedMesh.RecalculateBounds();
mcd.MeshCollider.sharedMesh = mcd.SlicedMesh;
mcd.SlicedVertHash = vertHash;
}
}
foreach (var bcd in boxColliderDetailsList)
{
bcd.SlicedColliderProperties = SliceUtility.SliceVerts(bcd.OriginalColliderProperties, bcd.Transform, size, rootTransform, completeBounds, slicedBounds);
bcd.BoxCollider.center = bcd.SlicedColliderProperties.center;
bcd.BoxCollider.size = bcd.SlicedColliderProperties.size;
}
}
///
public override void DisableSlicing()
{
base.DisableSlicing();
foreach (var cd in allColliderDetails)
{
cd.DisableSlicing();
}
}
///
public override void EnableSlicing()
{
base.EnableSlicing();
foreach (var cd in allColliderDetails)
{
cd.EnableSlicing();
}
}
///
public override void FinalizeSlicing()
{
foreach (var colliderDetail in meshColliderDetailsList)
{
colliderDetail.FinalizeSlicing();
}
meshColliderDetailsList.Clear();
foreach (var colliderDetail in meshSlicerManagedMeshColliderDetailsList)
{
colliderDetail.FinalizeSlicing();
}
meshSlicerManagedMeshColliderDetailsList.Clear();
foreach (var colliderDetail in boxColliderDetailsList)
{
colliderDetail.FinalizeSlicing();
}
boxColliderDetailsList.Clear();
foreach (var colliderDetail in unsupportedColliderDetailsList)
{
colliderDetail.FinalizeSlicing();
}
unsupportedColliderDetailsList.Clear();
SlicerController.SafeDestroy(this);
}
}
}