Search Unity

Character Clipping Protector

Discussion in 'Assets and Asset Store' started by Hines94, May 11, 2021.

  1. Hines94

    Hines94

    Joined:
    Feb 15, 2020
    Posts:
    19
    Character Clipping Protector
    A unity tool to reduce the amount of clipping between clothing layers and characters by hiding areas of the mesh which are occluded by outer layers.



    Works very well, small issues with certain areas such as armpits as described below.

    GITHUB https://github.com/Hines94/CharacterClippingProtector

    Loading the Example:
    • Download the project

    • Open the Scene "Character Clipping Test Scene"

    • Reset the meshes on the occlusion pawn to default

    • Run the "Character Clipping Protector" - Check button - on "HumanMesh_WITHCLIPPINGPROTECTOR"

    • Adjust the sliders for differing results

    • Requires the packages: Burst, Jobs & Editor Coroutines
    Strengths:
    • Fast - could be potentially used sparingly realtime depending on the mesh

    • Good coverage - Gets most areas and flat/slightly rounded surfaces
    Weaknesses:
    • Complex areas - Areas such as the armpits/ inner thighs can be an issue due to the huge variance in angles

    • For large meshes will cause perforance isues

    • Async- must be run in a coroutine

    • Only setup for skinned meshes
    Usage:
    • DO NOT RUN in any pose other than T or A pose - will get varying results

    • Setup a specific layer for testing occlusion and set this in the script

    • In editor use the "check" option as described above to run the simulation

    • To run from code simply set the meshes and then start the RunClippingSimulation coroutine (with optional callback when complete)

    • Adjust margin to add "safety" margins where the edges of the mesh occur
    Tips:
    • ORDER MESHES in terms of layer with the lowest priority first in the inspector (see example)

    • Split meshes up if required: The script will do a reasonable job on all areas but manual culling could be required in certain situations (split arms/legs/torso/head etc)

    • Calculate all mesh combinations upfront in a loading phase and store the results with the "Caching" feature - will then allow quick culling later during gameplay
    Method:
    • Creates a seperate collider mesh to test against for each mesh

    • Uses the new Raycast command to cast for each vertex on each mesh from the outside in

    • Collects results and processes to find areas with overlap

    • Creates a new mesh and hides areas with signigicant overlap
    Debugging:
    • Use the debug options on the tool to show either mesh hits (overlapping zones) or every raycast made.
     
    Last edited: May 11, 2021
    Ony, Necka_, judah4 and 3 others like this.
  2. Necka_

    Necka_

    Joined:
    Jan 22, 2018
    Posts:
    488
    @Hines94
    Quite incredible that I was looking for a solution like this for the past days. But today I decided to go through a lot of answers from another thread getting more and more in despair of not finding anything.

    But at the end of that other thread you posted a link to this. You just released this tool so it's an amazing timing!

    I just tested the demo and it looks really good to be honest.

    I need to do a real life test with my models and hope that it would work.

    Is the tool permanently running once the first check has been done? I saw that moving the cursor "margin distance" changes the vert hiding/showing in real time. Is there a way to not have any performance impact once the check is done?

    I have to ask you though, because maybe you'll think of something great:

    I have multiple character meshes
    For each character I have multiple clothing sets available (let say 10 different clothing sets)

    Seeing your tool I think I'd have to use it at runtime, when the player select his clothing, run your tool once only. If later he swap clothes for another set then run the tool again. No big deal.

    But I was wondering if there could be a solution to save the cache in editor mode.

    Let say:

    I equip clothes 1 on character 1
    Run your tool in editor and save the cache to a file (a scriptable object maybe?)

    Then still in editor I equip clothes 2 on character 1
    Run your tool and save this new cache to a new file.

    Then there could be a method to call, when in game player equip clothes 1 on his character => load the cache file
    If he swap to clothes 2 => load that cache file

    Do you think it could be possible?

    I want to thank you anyway for providing this tool to the community, it's really great. Thank you for this.

    Update: Tried on my character created in Character Creator 3.
    I have two versions of that character:

    -one with 1 mesh and multiple sub materials => this doesn't work properly, the mesh is all messed up

    -one with all materials merged into 1 => this works

    Compared to the demo, the time to run the tool is extremely long (maybe 45sec / 1mn), it also freeze a lot the editor as I guess there is lot of computing to do. But that makes it a no-go for runtime use.

    I hope there is a way that could work to simple save/load the mesh culling for runtime use :)
     
    Last edited: May 12, 2021
  3. Hines94

    Hines94

    Joined:
    Feb 15, 2020
    Posts:
    19
    @Necka_

    Thanks for providing feedback and I am glad it can be of use for you!

    The tool has a basic caching system to store combinations so as long as you replicate the items that you have chosen it will store them. Therefore in your situation I would run the combinations ahead of time and save the results (see update below) and just keep the script on your characters, then every time you change a piece of clothing just "re-run" but it will load your cached data.

    I think your idea for creating a editor caching tool that saves combinations into the "resources" folder for instance would be a good idea. However, you would have to be careful that your project does not balloon in size due to all of the saved meshes!

    The issues that you are experiencing at runtime are due to the fact that your mesh is animated during the "checking" phase.

    I will come out with a version 1.1 at some point when I get the time as I think some improvements are needed, although I will be away for a week or so as of tomorrow.

    I will make the following changes:
    - Editor saving to a static file in resources (with a prompt to tell you the mem size of all your combinations)
    - Creating of a separate skinned mesh renderer and resetting to T pose for runtime
    - Fix multiple materials

    I will let you know on this forum when I have released the next version.
     
  4. Necka_

    Necka_

    Joined:
    Jan 22, 2018
    Posts:
    488
    Hey thanks for the answer, I'm looking forward for such update!

    Oh and the freeze I was speaking about, it's not at runtime at all, I only tried the tool in editor mode so far.
    The models I tried on has quite a lot of verts though, so it's quite normal to me. I don't plan to use the check at runtime outside of course of loading a cache :)
     
  5. Necka_

    Necka_

    Joined:
    Jan 22, 2018
    Posts:
    488
    For my personal use case I actually went with a simpler solution

    As I said I have a main character but with different clothing sets
    As your tool create a temp mesh of the body, I've just included a method to save that mesh

    Now I can just pre-create all the occluded models and assign them at runtime when the player changes clothes :)
     
  6. free-light

    free-light

    Joined:
    Jun 29, 2015
    Posts:
    1
    Hi,

    This is amazing!!! I had to alter CharacterClippingProtector.cs to get it to work with avatars/clothing articles that use more than one sub-mesh material, using the UMA Standard Male avatar the eyes and mouth material were ending up the same texture as my body and the UVs not right like my ear details would be on the side of my face, etc. Anyways, here it is.


    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using Unity.Collections;
    5. using Unity.Jobs;
    6. using UnityEngine;
    7. using System.Linq;
    8. using Unity.Burst;
    9. using System;
    10.  
    11. public enum CharClipDebugLevel
    12. {
    13.     None,
    14.     ShowHits,
    15.     ShowAll,
    16. }
    17. public enum AsyncHarshness
    18. {
    19.     None,
    20.     Soft,
    21.     Harsh
    22. }
    23.  
    24. public class CharacterClippingProtector : MonoBehaviour
    25. {
    26.     [Tooltip("HAS TO BE SINGLE LAYER!! Unique Layer to use for checking meshes.")]
    27.     public LayerMask UniqueLayerMask;
    28.     [Tooltip("Will work through from fist to last")]
    29.     public List<SkinnedMeshRenderer> Meshes = new List<SkinnedMeshRenderer>();
    30.     [Tooltip("Do we want meshes with lower indexes NOT to affect higher index meshes?")]
    31.     public bool PreventLowerChainItems = true;
    32.     [Tooltip("Distance to check from each vertex for overlapping meshes")]
    33.     [Range(0.05f,0.5f)]
    34.     public float OutwardCheckDistance = 0.1f;
    35.     [Tooltip("Distance to check from each vertex inward for overlapping meshes")]
    36.     [Range(0.00f, 0.5f)]
    37.     public float InwardCheckDistance = 0.1f;
    38.     [Tooltip("The ammount of vertices from a non occluded vert to cull")]
    39.     [Range(0.0f, 0.5f)]
    40.     public float MarginDistance = 0.1f;
    41.     [Tooltip("Cache results to prevent multiple runs of the same meshes?")]
    42.     public bool CacheResults = true;
    43.     [Tooltip("Run processes over multiple frames?")]
    44.     public AsyncHarshness RunAsynch = AsyncHarshness.None;
    45.     public CharClipDebugLevel DebugLevel = CharClipDebugLevel.None;
    46.     public float DebugLineTime = 3f;
    47.     [Tooltip("Automatically rerun the simulation when changing a slider value?")]
    48.     public bool AutoRerunOnChange = true;
    49.  
    50.     Color[] MeshCols = new Color[] { Color.red, Color.blue, Color.green, Color.white, Color.grey, Color.cyan };
    51.     Dictionary<SkinnedMeshRenderer,Mesh> OriginalMeshes = new Dictionary<SkinnedMeshRenderer, Mesh>();
    52.     public Dictionary<SkinnedMeshRenderer, Mesh> GetOriginalMeshes() { return OriginalMeshes; }
    53.     List<GameObject> Colliders = new List<GameObject>();
    54.     static Dictionary<ClippingProtectorSetup, ClippingProtectorResult> CachedResults = new Dictionary<ClippingProtectorSetup, ClippingProtectorResult>();
    55.     public Dictionary<ClippingProtectorSetup, ClippingProtectorResult> GetCachedResults() { return CachedResults; }
    56.     public ClippingProtectorSetup LastSetup;
    57.     public void ResetCachedResults() { CachedResults = new Dictionary<ClippingProtectorSetup, ClippingProtectorResult>(); }
    58.  
    59.     /// <summary>
    60.     /// Main method. Runs a raycast simulation and hides parts of mesh that are obscured.
    61.     /// NOTE has to be IEnumerator otherwise created collision meshes do not provide any raycast result on same frame.
    62.     /// </summary>
    63.     public IEnumerator RunClippingSimulation(Action OnSimulationComplete = null)
    64.     {
    65.         //TODO best to run in T or A pose!
    66.         //TODO make sure that we have fully reset the meshes before re-running!!
    67.  
    68.         //Make sure we are working with the proper meshes and not already partially hidden
    69.         ResetMeshesToOriginal();
    70.  
    71.         //Check our cache to see if we have already run
    72.         if (CacheResults)
    73.         {
    74.             List<Mesh> CheckValues = new List<Mesh>();
    75.             foreach(SkinnedMeshRenderer smr in Meshes)
    76.             {
    77.                 if(smr == null || smr.sharedMesh == null) { continue; }
    78.                 CheckValues.Add(smr.sharedMesh);
    79.             }
    80.             ClippingProtectorSetup Check = new ClippingProtectorSetup
    81.             {
    82.                 OutCheckDistance = OutwardCheckDistance,
    83.                 InputMeshes = CheckValues,
    84.                 MarginDistance = MarginDistance,
    85.                 InCheckDistance = InwardCheckDistance,
    86.                 LowerChain = PreventLowerChainItems
    87.             };
    88.             if (CheckCached(Check)) { yield break; }
    89.         }
    90.         if(DebugLevel != CharClipDebugLevel.None) { Debug.Log("Could not find cached simulation. Running clipping check."); }
    91.  
    92.         //Create collider meshes that we can test against
    93.         for (int i = 0; i < Colliders.Count; i++) { DestroyImmediate(Colliders[i]); }
    94.         Colliders = new List<GameObject>();
    95.         CreateColliders(Colliders);
    96.         yield return null; //REQUIRED!
    97.  
    98.         //Foreach mesh in meshes check occlude
    99.         int Index = 0;
    100.         List<Mesh> FinalMeshes = new List<Mesh>();
    101.         foreach (SkinnedMeshRenderer smr in Meshes)
    102.         {
    103.             if (smr == null || smr.sharedMesh == null) { continue; }
    104.  
    105.             //Setup so we don't trigger ourselves or any mesh lower down the chain
    106.             if (PreventLowerChainItems)
    107.             {
    108.                 Colliders[Index].gameObject.SetActive(false);
    109.                 yield return null;
    110.             }
    111.             //if(Index > 0) { Colliders[Index - 1].gameObject.SetActive(true); }
    112.             OriginalMeshes.Add(smr, smr.sharedMesh);
    113.  
    114.             //Raycast for results
    115.             bool[] Results = RaycastToFindOverlap(Colliders, Index);
    116.  
    117.             //If we have nothing to hide then no point to change the mesh etc!
    118.             if (!Results.Contains(true)) { FinalMeshes.Add(smr.sharedMesh); }
    119.  
    120.             int smc = smr.sharedMesh.subMeshCount;
    121.             for (int i = 1; i <= smc; i++) {
    122.                 int[] temptris = null;
    123.  
    124.                 if (smc == 1) {
    125.                     temptris = smr.sharedMesh.triangles.ToArray();              
    126.                 } else {
    127.                     temptris = smr.sharedMesh.GetTriangles(i-1).ToArray();
    128.                 }
    129.  
    130.                 //Identify the triangles that we no longer want and remove them
    131.                 CalculateNewTrisJob newJob = new CalculateNewTrisJob
    132.                 {
    133.                     NewTris = new NativeList<int>(Allocator.TempJob),              
    134.                     OriginalTris = new NativeArray<int>(temptris, Allocator.TempJob),
    135.                     Results = new NativeArray<bool>(Results, Allocator.TempJob)
    136.                 };
    137.                 newJob.Schedule().Complete();
    138.                 if (RunAsynch == AsyncHarshness.Harsh) { yield return null; }
    139.  
    140.                 //Setup instance mesh ready for hiding elements
    141.                 Mesh newm = Instantiate(smr.sharedMesh);
    142.  
    143.                 if (smc == 1)
    144.                 {
    145.                     newm.triangles = newJob.NewTris.ToArray();
    146.                 } else {
    147.                     //use SetTriangles instead of .triangles if we are on a submesh
    148.                     newm.SetTriangles(newJob.NewTris.ToArray(), i-1);
    149.                 }
    150.                 newm.name = "Clone_" + smr.sharedMesh.name;
    151.                 smr.sharedMesh = newm;
    152.                 FinalMeshes.Add(newm);
    153.  
    154.                 //Finalize
    155.                 newJob.Cleanup();
    156.                 //Debug.Log("SubMesh: "+i.ToString() +"/"+smc.ToString());
    157.                 if (RunAsynch == AsyncHarshness.Soft) { yield return null; }
    158.             }
    159.             Index++;
    160.         }
    161.         //Clear up old junk colliders
    162.         for (int i = 0; i < Colliders.Count; i++) { DestroyImmediate(Colliders[i]); }
    163.  
    164.         //Add result to cache for quick rerun of any future meshes
    165.         if(CacheResults)
    166.         {
    167.             LastSetup = new ClippingProtectorSetup
    168.             {
    169.                 OutCheckDistance = OutwardCheckDistance,
    170.                 InputMeshes = OriginalMeshes.Values.ToList(),
    171.                 MarginDistance = MarginDistance,
    172.                 InCheckDistance = InwardCheckDistance,
    173.                 LowerChain = PreventLowerChainItems
    174.             };
    175.  
    176.             CachedResults.Add(LastSetup,
    177.             new ClippingProtectorResult
    178.             {
    179.                 FinalMeshes = FinalMeshes
    180.             });
    181.         }
    182.  
    183.         //Notify of completion
    184.         if(OnSimulationComplete != null) { OnSimulationComplete.Invoke(); }
    185.     }
    186.  
    187.     /// <summary>
    188.     /// Raycasts to find the overlap of meshes from the outside in
    189.     /// </summary>
    190.     private bool[] RaycastToFindOverlap(List<GameObject> Colliders, int Index)
    191.     {
    192.         //Have to get mesh from colliders in case of weird scaling issues
    193.         Mesh M = Colliders[Index].GetComponent<MeshCollider>().sharedMesh;
    194.         //Setup
    195.         List<GameObject> OurCollideGOs = new List<GameObject>() { Colliders[Index].gameObject };
    196.         if (PreventLowerChainItems) { for (int i = 0; i < Index; i++) { OurCollideGOs.Add(Colliders[i]); } }
    197.         Color MeshCol = MeshCols[Index % MeshCols.Count()];
    198.         //Setup ready for raycast
    199.         var results = new NativeArray<RaycastHit>(M.vertexCount, Allocator.TempJob);
    200.         var commands = new NativeArray<RaycastCommand>(M.vertexCount, Allocator.TempJob);
    201.         for (int i = 0; i < M.vertexCount; i++)
    202.         {
    203.             Vector3 Position = Colliders[Index].transform.TransformPoint(M.vertices[i]);
    204.             Vector3 Normal = Colliders[Index].transform.TransformDirection(M.normals[i]);
    205.             commands[i] = new RaycastCommand(Position + (Normal.normalized * OutwardCheckDistance), Normal.normalized * -1, OutwardCheckDistance + InwardCheckDistance, UniqueLayerMask.value);
    206.         }
    207.         // Wait for the batch processing job to complete
    208.         JobHandle handle = RaycastCommand.ScheduleBatch(commands, results, 1, default(JobHandle));
    209.         handle.Complete();
    210.  
    211.         //Run checks as a job for much faster results
    212.         bool[] Results = new bool[results.Length];
    213.         for (int i = 0; i < results.Length; i++)
    214.         {
    215.             if (results[i].collider == null) { Results[i] = true;  }
    216.             else if (OurCollideGOs.Contains(results[i].collider.gameObject)) { Results[i] = true; Debug.Log("Triggered " + results[i].collider.gameObject); }
    217.             else { Results[i] = false; }
    218.         }
    219.  
    220.         //Debug
    221.         if (DebugLevel != CharClipDebugLevel.None)
    222.         {
    223.             for (int i = 0; i < Results.Length; i++)
    224.             {
    225.                 //Hit something that isnt us?
    226.                 if (!Results[i])
    227.                 {
    228.                     if (DebugLevel == CharClipDebugLevel.ShowAll) Debug.DrawLine(commands[i].from, commands[i].from + commands[i].direction * commands[i].distance, Color.white, DebugLineTime);
    229.                     if (DebugLevel == CharClipDebugLevel.ShowHits) Debug.DrawLine(commands[i].from, commands[i].from + commands[i].direction * commands[i].distance, MeshCol, DebugLineTime);
    230.                 }
    231.                 else
    232.                 {
    233.                     if (DebugLevel == CharClipDebugLevel.ShowAll) Debug.DrawLine(commands[i].from, commands[i].from + commands[i].direction * commands[i].distance, MeshCol, DebugLineTime);
    234.                 }
    235.             }
    236.         }
    237.  
    238.         //Run final pass to check nearness to edge
    239.         CheckVertPassedTests CheckTest = new CheckVertPassedTests
    240.         {
    241.             PassedIndividual = new NativeArray<bool>(Results, Allocator.TempJob),
    242.             Margin = MarginDistance,
    243.             Origins = commands,
    244.             FinalPassResult = new NativeArray<bool>(Results, Allocator.TempJob)
    245.         };
    246.         CheckTest.Schedule(Results.Length, 1).Complete();
    247.  
    248.         //Have we passed the test?
    249.         Results = CheckTest.FinalPassResult.ToArray();
    250.  
    251.         //Cleanup
    252.         CheckTest.Cleanup();
    253.         results.Dispose();
    254.         commands.Dispose();
    255.         return Results;
    256.     }
    257.  
    258.     /// <summary>
    259.     /// Create mesh colliders that we can accurately check against
    260.     /// </summary>
    261.     /// <param name="Colliders"></param>
    262.     private void CreateColliders(List<GameObject> Colliders)
    263.     {
    264.         int Found = 0;
    265.         foreach (SkinnedMeshRenderer smr in Meshes)
    266.         {
    267.             if (smr == null || smr.sharedMesh == null) { continue; }
    268.             Mesh collider = new Mesh();
    269.             smr.BakeMesh(collider);
    270.             GameObject NewGO = CreateColliderMeshObject(smr, collider);
    271.             Colliders.Add(NewGO);
    272.         }
    273.     }
    274.  
    275.     /// <summary>
    276.     /// Reset our meshes back to originals
    277.     /// </summary>
    278.     public void ResetMeshesToOriginal()
    279.     {
    280.         if (OriginalMeshes.Count == 0) return;
    281.         foreach(SkinnedMeshRenderer key in OriginalMeshes.Keys)
    282.         {
    283.             if(key == null) { continue; }
    284.             key.sharedMesh = OriginalMeshes[key];
    285.         }
    286.         OriginalMeshes = new Dictionary<SkinnedMeshRenderer, Mesh>();
    287.     }
    288.  
    289.     /// <summary>
    290.     /// Create a temporary object that we can use to raycast against for accurate results
    291.     /// </summary>
    292.     private GameObject CreateColliderMeshObject(SkinnedMeshRenderer smr, Mesh NewMesh)
    293.     {
    294.         GameObject NewGO = new GameObject();
    295.         MeshCollider MC = (MeshCollider)NewGO.AddComponent(typeof(MeshCollider));
    296.         NewGO.name = "TEMP_Collider_" + smr.gameObject.name;
    297.         MC.sharedMesh = NewMesh;
    298.         MC.gameObject.layer = GetLayerFromMask(UniqueLayerMask);
    299.         NewGO.transform.position = smr.transform.position;
    300.         NewGO.transform.rotation = smr.transform.rotation;
    301.         return NewGO;
    302.     }
    303.  
    304.     /// <summary>
    305.     /// Check our cached previous simulations and see if we can skip the simulation process
    306.     /// </summary>
    307.     private bool CheckCached(ClippingProtectorSetup Check)
    308.     {
    309.         //Check local cached
    310.         foreach(ClippingProtectorSetup cs in CachedResults.Keys)
    311.         {
    312.             if(cs.SetupEqual(Check))
    313.             {
    314.                 List<Mesh> NewMeshes = CachedResults[cs].FinalMeshes;
    315.                 SetFromCachedMeshes(NewMeshes);
    316.                 return true;
    317.             }
    318.         }
    319.         //Check Saved cache scriptables
    320.         ClippingProtectorResult Res = CharacterClippingScriptableResult.TryLoadCachedResult(Check);
    321.         if(Res.FinalMeshes != null && Res.FinalMeshes.Count > 0)
    322.         {
    323.             SetFromCachedMeshes(Res.FinalMeshes);
    324.             return true;
    325.         }
    326.         return false;
    327.     }
    328.  
    329.     private void SetFromCachedMeshes(List<Mesh> NewMeshes)
    330.     {
    331.         OriginalMeshes = new Dictionary<SkinnedMeshRenderer, Mesh>();
    332.         if (DebugLevel != CharClipDebugLevel.None) Debug.Log("Existing simulation found!");
    333.         int Ind = 0;
    334.         foreach (SkinnedMeshRenderer smr in Meshes)
    335.         {
    336.             if (smr == null || smr.sharedMesh == null) continue;
    337.             OriginalMeshes.Add(smr, smr.sharedMesh);
    338.             smr.sharedMesh = NewMeshes[Ind];
    339.             Ind++;
    340.         }
    341.     }
    342.  
    343.  
    344.  
    345.     //Benefit from burst to speed this up significantly
    346.     [BurstCompile(CompileSynchronously = true)]
    347.     private struct CheckVertPassedTests : IJobParallelFor
    348.     {
    349.         [NativeDisableParallelForRestriction]
    350.         public NativeArray<RaycastCommand> Origins;
    351.         [NativeDisableParallelForRestriction]
    352.         public NativeArray<bool> FinalPassResult;
    353.         [NativeDisableParallelForRestriction]
    354.         public NativeArray<bool> PassedIndividual;
    355.         [ReadOnly]
    356.         public float Margin;
    357.         [ReadOnly]
    358.         public float OutwardCheckDistance;
    359.  
    360.         public void Cleanup()
    361.         {
    362.             //Dont dispose commands as coming from somewhere else
    363.             FinalPassResult.Dispose();
    364.             PassedIndividual.Dispose();
    365.         }
    366.  
    367.         public void Execute(int index)
    368.         {
    369.             //Not hit anything then we can consider ourselves passed
    370.             if (PassedIndividual[index]) { FinalPassResult[index] = true;return; }
    371.             //Else we still may pass if we are close enough to a pass
    372.             else
    373.             {
    374.                 for (int i = 0; i < Origins.Length; i++)
    375.                 {
    376.                     //If other has passed and we are close enough then we pass anyway
    377.                     if (index == i) { continue; }
    378.                     if (!PassedIndividual[i]) { continue; }
    379.                     float Dist = (GetRealOrigin(Origins[i]) - GetRealOrigin(Origins[index])).magnitude;
    380.                     if(Dist < Margin) { FinalPassResult[index] = true;return; }
    381.                 }
    382.             }
    383.             //All Fail
    384.             FinalPassResult[index] = false;
    385.         }
    386.  
    387.  
    388.         public Vector3 GetRealOrigin(RaycastCommand Command)
    389.         {
    390.             return Command.from + (Command.direction * OutwardCheckDistance);
    391.         }
    392.     }
    393.  
    394.     //Benefit from burst to speed this up significantly
    395.     [BurstCompile(CompileSynchronously = true)]
    396.     private struct CalculateNewTrisJob : IJob
    397.     {
    398.         public NativeArray<int> OriginalTris;
    399.         public NativeArray<bool> Results;
    400.         public NativeList<int> NewTris;
    401.  
    402.         public void Cleanup()
    403.         {
    404.             OriginalTris.Dispose();
    405.             Results.Dispose();
    406.             NewTris.Dispose();
    407.         }
    408.  
    409.         public void Execute()
    410.         {
    411.             for (int t = 0; t < OriginalTris.Length - 1; t += 3)
    412.             {
    413.                 //If all three tris are inside the "Zone"
    414.                 if (!Results[OriginalTris[t]] && !Results[OriginalTris[t + 1]] && !Results[OriginalTris[t + 2]])
    415.                 {
    416.                     //Is not allowed
    417.                 }
    418.                 else
    419.                 {
    420.                     //Is allowed
    421.                     NewTris.Add(OriginalTris[t]);
    422.                     NewTris.Add(OriginalTris[t + 1]);
    423.                     NewTris.Add(OriginalTris[t + 2]);
    424.                 }
    425.             }
    426.         }
    427.     }
    428.  
    429.     /// <summary>
    430.     /// Returns a value between [0;31].
    431.     /// Important: This will only work properly if the LayerMask is one created in the inspector and not a LayerMask
    432.     /// with multiple values.
    433.     /// </summary>
    434.     public static int GetLayerFromMask(LayerMask _mask)
    435.     {
    436.         var bitmask = _mask.value;
    437.         int result = bitmask > 0 ? 0 : 31;
    438.         while (bitmask > 1)
    439.         {
    440.             bitmask = bitmask >> 1;
    441.             result++;
    442.         }
    443.         return result;
    444.     }
    445.  
    446. #if UNITY_EDITOR
    447.     public Unity.EditorCoroutines.Editor.EditorCoroutine EditorCoroutine = null;
    448.     private void OnValidate()
    449.     {
    450.         if (!AutoRerunOnChange || OriginalMeshes.Count == 0) { return; }
    451.         StartEditorCoroutine();
    452.     }
    453.  
    454.     public void StartEditorCoroutine()
    455.     {
    456.         StopAllCoroutines();
    457.         if (EditorCoroutine != null) { Unity.EditorCoroutines.Editor.EditorCoroutineUtility.StopCoroutine(EditorCoroutine); }
    458.         if (Application.isPlaying) { StartCoroutine(RunClippingSimulation()); }
    459.         else { EditorCoroutine = Unity.EditorCoroutines.Editor.EditorCoroutineUtility.StartCoroutine(RunClippingSimulation(), this); }
    460.     }
    461. #endif
    462. }
    463.  
     
    firstuser likes this.