Search Unity

Raycast without colliders?

Discussion in 'Editor & General Support' started by Darkrobyn, Oct 27, 2008.

  1. Darkrobyn

    Darkrobyn

    Joined:
    Feb 7, 2008
    Posts:
    23
    Before posting an official 'feature request', I was wondering if there is any way right now to do a "ray cast" on objects for object picking. I need accurate object selection based on the current state of a mesh or skinned mesh. I don't need physics in my project at all.

    I'm currently using Physics.Raycast to do this, but there are two key drawbacks:

    1) When a skinned mesh animates, the attached MeshCollider doesn't update with it. I currently must re-calculate the MeshCollider whenever an animation moves the skinned mesh (slow annoying).

    2) When adding a lot of MeshColliders, it takes a very long time where Unity is non-responsive. For example, one scene I load has ~150k triangles. It takes approximately one second to load from an Assetbundle. When I add MeshColliders to it, it then takes about 16 seconds to load from the AssetBundle (and I can't even show a 'loading...' progress bar).

    I know it may not be fast to use the same method that the Unity editor itself uses, but at least it CAN do selection on objects without colliders and with arbitrary skinned mesh animations.

    --
    David Robinson
    Coole Immersive, Inc.
     
  2. jcarpay

    jcarpay

    Joined:
    Aug 15, 2008
    Posts:
    514
    I wonder how you re-calculate the MeshCollider, can you give me some info about this?
    Thanks!
     
  3. Darkrobyn

    Darkrobyn

    Joined:
    Feb 7, 2008
    Posts:
    23
    Here's the component I use to do the collision mesh update. You just attach it to the object that has a SkinnedMeshRenderer and MeshCollider, and whenever you need to update the collision mesh (triggered by playing another animation, etc.) just set the "forceUpdate" flag on this component. It only works with Unity 2.1 (or later?)

    --
    David Robinson
    Coole Immersive, Inc.


    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class SkinnedCollisionHelper : MonoBehaviour
    6. {
    7.     // Public variables
    8.     public bool forceUpdate;
    9.  
    10.     // Instance variables
    11.     private CWeightList[]   nodeWeights;    // array of node weights (one per node)
    12.     private Vector3[]       newVert;        // array for the regular update of the collision mesh
    13.    
    14.     private Mesh            mesh;       // the dynamically-updated collision mesh
    15.     private MeshCollider    collide;    // quick pointer to the mesh collider that we're updating
    16.    
    17.    
    18.     // Function:    Start
    19.     //      This basically translates the information about the skinned mesh into
    20.     // data that we can internally use to quickly update the collision mesh.
    21.     void Start()
    22.     {
    23.         SkinnedMeshRenderer rend = GetComponent(typeof(SkinnedMeshRenderer)) as SkinnedMeshRenderer;
    24.         collide = GetComponent(typeof(MeshCollider)) as MeshCollider;
    25.        
    26.         if (collide!=null  rend!=null)
    27.         {
    28.             Mesh baseMesh = rend.sharedMesh;
    29.             mesh = new Mesh();
    30.             mesh.vertices = baseMesh.vertices;
    31.             mesh.uv = baseMesh.uv;
    32.             mesh.triangles = baseMesh.triangles;
    33.             newVert = new Vector3[baseMesh.vertices.Length];
    34.            
    35.             short i;
    36.             // Make a CWeightList for each bone in the skinned mesh        
    37.             nodeWeights = new CWeightList[rend.bones.Length];
    38.             for ( i=0 ; i<rend.bones.Length ; i++ )
    39.             {
    40.                 nodeWeights[i] = new CWeightList();
    41.                 nodeWeights[i].transform = rend.bones[i];
    42.             }
    43.  
    44.             // Create a bone weight list for each bone, ready for quick calculation during an update...
    45.             Vector3 localPt;
    46.             for ( i=0 ; i<baseMesh.vertices.Length ; i++ )
    47.             {
    48.                 BoneWeight bw = baseMesh.boneWeights[i];
    49.                 if (bw.weight0!=0.0f)
    50.                 {
    51.                     localPt = baseMesh.bindposes[bw.boneIndex0].MultiplyPoint3x4( baseMesh.vertices[i] );
    52.                     nodeWeights[bw.boneIndex0].weights.Add( new CVertexWeight( i, localPt, bw.weight0 ) );
    53.                 }
    54.                 if (bw.weight1!=0.0f)
    55.                 {
    56.                     localPt = baseMesh.bindposes[bw.boneIndex1].MultiplyPoint3x4( baseMesh.vertices[i] );
    57.                     nodeWeights[bw.boneIndex1].weights.Add( new CVertexWeight( i, localPt, bw.weight1 ) );
    58.                 }
    59.                 if (bw.weight2!=0.0f)
    60.                 {
    61.                     localPt = baseMesh.bindposes[bw.boneIndex2].MultiplyPoint3x4( baseMesh.vertices[i] );
    62.                     nodeWeights[bw.boneIndex2].weights.Add( new CVertexWeight( i, localPt, bw.weight2 ) );
    63.                 }
    64.                 if (bw.weight3!=0.0f)
    65.                 {
    66.                     localPt = baseMesh.bindposes[bw.boneIndex3].MultiplyPoint3x4( baseMesh.vertices[i] );
    67.                     nodeWeights[bw.boneIndex3].weights.Add( new CVertexWeight( i, localPt, bw.weight3 ) );
    68.                 }
    69.             }
    70.  
    71.             UpdateCollisionMesh();
    72.         }
    73.         else
    74.         {
    75.             Debug.LogError(gameObject.name + ": SkinnedCollisionHelper: this object either has no SkinnedMeshRenderer or has no MeshCollider!");
    76.         }
    77.  
    78.     }
    79.  
    80.  
    81.     // Function:    UpdateCollisionMesh
    82.     //  Manually recalculates the collision mesh of the skinned mesh on this
    83.     // object.
    84.     void UpdateCollisionMesh()
    85.     {
    86.         if (mesh!=null)
    87.         {
    88.             // Start by initializing all vertices to 'empty'
    89.             for ( int i=0 ; i<newVert.Length ; i++ )
    90.             {
    91.                 newVert[i] = new Vector3(0,0,0);
    92.             }
    93.  
    94.             // Now get the local positions of all weighted indices...
    95.             foreach ( CWeightList wList in nodeWeights )
    96.             {
    97.                 foreach ( CVertexWeight vw in wList.weights )
    98.                 {
    99.                     newVert[vw.index] += wList.transform.localToWorldMatrix.MultiplyPoint3x4( vw.localPosition ) * vw.weight;
    100.                 }
    101.             }
    102.  
    103.             // Now convert each point into local coordinates of this object.
    104.             for ( int i=0 ; i<newVert.Length ; i++ )
    105.             {
    106.                 newVert[i] = transform.InverseTransformPoint( newVert[i] );
    107.             }
    108.  
    109.             // Update the mesh ( collider) with the updated vertices
    110.             mesh.vertices = newVert;
    111.             mesh.RecalculateBounds();
    112.             collide.sharedMesh = mesh;
    113.         }
    114.     }
    115.  
    116.  
    117.     // Function:    Update
    118.     //  If the 'forceUpdate' flag is set, updates the collision mesh for the skinned mesh on this object
    119.     void Update()
    120.     {
    121.         if (forceUpdate)
    122.         {
    123.             forceUpdate = false;
    124.             UpdateCollisionMesh();
    125.         }
    126.     }
    127. }
    128.  
    129.  
     
  4. jcarpay

    jcarpay

    Joined:
    Aug 15, 2008
    Posts:
    514
    Awesome, thanks!
     
  5. sgs

    sgs

    Joined:
    Dec 15, 2010
    Posts:
    1
    First thanks for the code, I was looking for that kind of thing.

    Second, I'd suggest you implement your own sphere-casting routine, where you check your ray manually for hitting any of your skinned meshes within a radius, see pseudo code for dist_Point_to_Line :

    http://softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm

    Then update only the collision skin of the nearest animated mesh for which they ray will hit within a radius. This should save you quite a bit of CPU cycles.

    Third, perhaps I'm not versed enough in Unity's API - but IMHO it would be useful to have a function to test against a single colider and not all of coliders, I suppose you could change layers of objects temporarily to achieve that.
     
  6. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    Collider.Raycast will test vs. a single collider.

    Regarding doing testing on very complex meshes without Colliders (150k tris worth of MeshColliders is definitely not going to end in good things) is to render the scene to a render target with replacement shaders. Give each object a 24 bit ID. Then Render the scene to a RenderTarget with each selectable object being rendered. Set the colour in the replacement shader to be R = the first 8 bits of the ID, G = the next 8 bits, B = the last 8 bits. Then get the pixel that was clicked on in the scene and convert it's RGB value into a 24bit value (stack the 3 values one after another). Then you can retrieve the object clicked on based on ID. This allows you to have 16 777 216 unique objects (if you need less you can only use a single channel). And will remove the massive weight you are putting on the physics system. It will also reuse the skinning done for rendering the actual frame.

    Edit: I'm not sure if Unity supports Multiple Render Targets, but if it does, you should be able to do all this in the original Rendering pass (unless deferred is already using too many RTs).
     
    Last edited: Dec 15, 2010
  7. Quietus2

    Quietus2

    Joined:
    Mar 28, 2008
    Posts:
    2,060
    1) Try to not raycast against meshes with a large amount of triangles. Unity has to search through all the triangles to find which one was hit. No matter how efficient Unity might be at sorting through them with a quad-tree or whatnot, it obviously will take a lot of time.

    2) Use culling masks to eliminate things you don't want to raycast against. Be selective as you can. If you're casting a ray to fire at an enemy player, don't need to include objects that aren't important to what you're trying to accomplish.
     
  8. mattirwin

    mattirwin

    Joined:
    Jan 3, 2011
    Posts:
    13
    Great script, thanks Darkrobyn!
     
  9. mattirwin

    mattirwin

    Joined:
    Jan 3, 2011
    Posts:
    13
    To make it work, I added collide.sharedMesh = null;
    before your line: collide.sharedMesh = mesh;

    And added two classes to the bottom of the script file:

    Code (csharp):
    1. class CVertexWeight
    2. {
    3.  
    4.     public int index;
    5.     public Vector3 localPosition;
    6.     public float weight;
    7.  
    8.     public CVertexWeight(int i, Vector3 p, float w)
    9.     {
    10.  
    11.         index = i;
    12.         localPosition = p;
    13.         weight = w;
    14.     }
    15.  
    16. }
    17.  
    18. class CWeightList
    19. {
    20.  
    21.     public Transform transform;
    22.     public ArrayList weights;
    23.     public CWeightList()
    24.     {
    25.         weights = new ArrayList();
    26.     }
    27.    
    28. }
     
  10. dvochin2

    dvochin2

    Joined:
    Jul 14, 2012
    Posts:
    84
    Hi, thanks for sharing this awesome work! Very useful!

    I just wanted to fix a very small but that will cause your script to crash if the mesh has over 32K vertices (max is 64K)

    To fix, simply change the 'short i' with a 'int i' on line 34 so very large meshes can be iterated as well.

    Congrats!

    Dan,
     
  11. ImogenPoot

    ImogenPoot

    Joined:
    Jul 2, 2012
    Posts:
    214
    Thanks for this code, it really helped. BUT it has several severe performance issues that I fixed. In the following is a "high-performance" version that is apx. a thousand times faster if not more... Tested with Unity 4.0.
    It could be further improved by using two vertex buffers and swapping them on each frame, so you would also save the memory allocation... But I didn't need that since I am just writing a mesh converter.

    In the future, please keep in mind, NEVER EVER access Mesh.vertices stuff within a loop. It will create a copy every-time you access it. (First I thought your code crashed Unity, but after a few minutes it was done with my mesh LOL)

    Code (csharp):
    1.  
    2.     void ApplySkinningToTriangles(SkinnedMeshRenderer skin, Mesh mesh)
    3.     {
    4.         // Make a CWeightList for each bone in the skinned mesh  
    5.         var vertices = mesh.vertices;
    6.         var bindposes = mesh.bindposes;
    7.         var boneWeights = mesh.boneWeights;
    8.         var bones = skin.bones;
    9.  
    10.         // Start by initializing all vertices to 'empty'
    11.         var newVert = new Vector3[vertices.Length];
    12.         for (int i = 0; i < newVert.Length; i++)
    13.         {
    14.             newVert[i] = new Vector3(0, 0, 0);
    15.         }
    16.        
    17.         // Create a bone weight list for each bone, ready for quick calculation during an update...
    18.         Vector3 localPt;
    19.  
    20.         for (int i = 0; i < vertices.Length; i++)
    21.         {
    22.             BoneWeight bw = boneWeights[i];
    23.  
    24.             if (Math.Abs(bw.weight0) > 0.00001)
    25.             {
    26.                 localPt = bindposes[bw.boneIndex0].MultiplyPoint3x4(vertices[i]);
    27.                 newVert[i] += bones[bw.boneIndex0].transform.localToWorldMatrix.MultiplyPoint3x4(localPt) * bw.weight0;
    28.             }
    29.  
    30.             if (Math.Abs(bw.weight1) > 0.00001)
    31.             {
    32.                 localPt = bindposes[bw.boneIndex1].MultiplyPoint3x4(vertices[i]);
    33.                 newVert[i] += bones[bw.boneIndex1].transform.localToWorldMatrix.MultiplyPoint3x4(localPt) * bw.weight1;
    34.  
    35.             }
    36.  
    37.             if (Math.Abs(bw.weight2) > 0.00001)
    38.             {
    39.                 localPt = bindposes[bw.boneIndex2].MultiplyPoint3x4(vertices[i]);
    40.                 newVert[i] += bones[bw.boneIndex2].transform.localToWorldMatrix.MultiplyPoint3x4(localPt) * bw.weight2;
    41.             }
    42.  
    43.             if (Math.Abs(bw.weight3) > 0.00001)
    44.             {
    45.                 localPt = bindposes[bw.boneIndex3].MultiplyPoint3x4(vertices[i]);
    46.                 newVert[i] += bones[bw.boneIndex3].transform.localToWorldMatrix.MultiplyPoint3x4(localPt) * bw.weight3;
    47.             }
    48.         }
    49.  
    50.         mesh.vertices = newVert;
    51.     }
    52.  
     
    Last edited: Mar 13, 2013
  12. Golesy

    Golesy

    Joined:
    Apr 10, 2013
    Posts:
    32
    @ImogenPoot Whereabouts in the code would I place your updated version?
     
  13. Cawas

    Cawas

    Joined:
    Jan 14, 2010
    Posts:
    121
    @Golesy I don't think his code were made to be placed anywhere. To me, it seems like it's either to be used as a reference or mostly replace the `Start` of @Darkrobyn. And, to my brief experiments, just copying the vertices to a local var didn't bring any performance improvement.

    I'm no expert with messing with meshes, but I do know one thing or two and I agree with him that the first script contains many performance issues. So, this is my updated complete version, which is working as fine as I could make it work (in a few moments) on Unity 4:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class SkinnedCollisionHelper : MonoBehaviour
    6. {
    7.     class CVertexWeight
    8.     {
    9.         public int index;
    10.         public Vector3 localPosition;
    11.         public float weight;
    12.      
    13.         public CVertexWeight(int i, Vector3 p, float w)
    14.         {
    15.             index = i;
    16.             localPosition = p;
    17.             weight = w;
    18.         }
    19.     }
    20.      
    21.     class CWeightList
    22.     {
    23.         public Transform transform;
    24.         public ArrayList weights;
    25.         public CWeightList()
    26.         {
    27.             weights = new ArrayList();
    28.         }
    29.     }
    30.    
    31.     public bool forceUpdate;
    32.     public bool updateOncePerFrame = true;
    33.    
    34.     private CWeightList[] nodeWeights; // one per node
    35.    
    36.     private SkinnedMeshRenderer skinnedMeshRenderer;
    37.     private MeshCollider meshCollider;
    38.    
    39.    
    40.     /// <summary>
    41.     ///  This basically translates the information about the skinned mesh into
    42.     /// data that we can internally use to quickly update the collision mesh.
    43.     /// </summary>
    44.     void Start()
    45.     {
    46.         skinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
    47.         meshCollider = GetComponent<MeshCollider>();
    48.        
    49.         if (meshCollider != null  skinnedMeshRenderer != null)
    50.         {
    51.             // Cache used values rather than accessing straight from the mesh on the loop below
    52.             Vector3[] cachedVertices = skinnedMeshRenderer.sharedMesh.vertices;
    53.             Matrix4x4[] cachedBindposes = skinnedMeshRenderer.sharedMesh.bindposes;
    54.             BoneWeight[] cachedBoneWeights = skinnedMeshRenderer.sharedMesh.boneWeights;
    55.            
    56.             // Make a CWeightList for each bone in the skinned mesh
    57.             nodeWeights = new CWeightList[skinnedMeshRenderer.bones.Length];
    58.             for ( int i = 0 ; i < skinnedMeshRenderer.bones.Length ; i++ )
    59.             {
    60.                 nodeWeights[i] = new CWeightList();
    61.                 nodeWeights[i].transform = skinnedMeshRenderer.bones[i];
    62.             }
    63.  
    64.             // Create a bone weight list for each bone, ready for quick calculation during an update...
    65.             for ( int i = 0 ; i < cachedVertices.Length ; i++ )
    66.             {
    67.                 BoneWeight bw = cachedBoneWeights[i];
    68.                 if (bw.weight0 != 0.0f)
    69.                 {
    70.                     Vector3 localPt = cachedBindposes[bw.boneIndex0].MultiplyPoint3x4( cachedVertices[i] );
    71.                     nodeWeights[bw.boneIndex0].weights.Add( new CVertexWeight( i, localPt, bw.weight0 ) );
    72.                 }
    73.                 if (bw.weight1 != 0.0f)
    74.                 {
    75.                     Vector3 localPt = cachedBindposes[bw.boneIndex1].MultiplyPoint3x4( cachedVertices[i] );
    76.                     nodeWeights[bw.boneIndex1].weights.Add( new CVertexWeight( i, localPt, bw.weight1 ) );
    77.                 }
    78.                 if (bw.weight2 != 0.0f)
    79.                 {
    80.                     Vector3 localPt = cachedBindposes[bw.boneIndex2].MultiplyPoint3x4( cachedVertices[i] );
    81.                     nodeWeights[bw.boneIndex2].weights.Add( new CVertexWeight( i, localPt, bw.weight2 ) );
    82.                 }
    83.                 if (bw.weight3 != 0.0f)
    84.                 {
    85.                     Vector3 localPt = cachedBindposes[bw.boneIndex3].MultiplyPoint3x4( cachedVertices[i] );
    86.                     nodeWeights[bw.boneIndex3].weights.Add( new CVertexWeight( i, localPt, bw.weight3 ) );
    87.                 }
    88.             }
    89.  
    90.             UpdateCollisionMesh();
    91.         }
    92.         else
    93.         {
    94.             Debug.LogError("[SkinnedCollisionHelper] "+ gameObject.name +" is missing SkinnedMeshRenderer or MeshCollider!");
    95.         }
    96.    
    97.     }
    98.    
    99.     /// <summary>
    100.     /// Manually recalculates the collision mesh of the skinned mesh on this object.
    101.     /// </summary>
    102.     public void UpdateCollisionMesh()
    103.     {
    104.         Mesh mesh = new Mesh();
    105.        
    106.         Vector3[] newVert = new Vector3[skinnedMeshRenderer.sharedMesh.vertices.Length];
    107.        
    108.         // Now get the local positions of all weighted indices...
    109.         foreach ( CWeightList wList in nodeWeights )
    110.         {
    111.             foreach ( CVertexWeight vw in wList.weights )
    112.             {
    113.                 newVert[vw.index] += wList.transform.localToWorldMatrix.MultiplyPoint3x4( vw.localPosition ) * vw.weight;
    114.             }
    115.         }
    116.  
    117.         // Now convert each point into local coordinates of this object.
    118.         for ( int i = 0 ; i < newVert.Length ; i++ )
    119.         {
    120.             newVert[i] = transform.InverseTransformPoint( newVert[i] );
    121.         }
    122.  
    123.         // Update the mesh ( collider) with the updated vertices
    124.         mesh.vertices = newVert;
    125.         mesh.uv = skinnedMeshRenderer.sharedMesh.uv; // is this even needed here?
    126.         mesh.triangles = skinnedMeshRenderer.sharedMesh.triangles;
    127.         mesh.RecalculateBounds();
    128.         mesh.MarkDynamic(); // says it should improve performance, but I couldn't see it happening
    129.         meshCollider.sharedMesh = mesh;
    130.     }
    131.    
    132.     /// <summary>
    133.     /// If the 'forceUpdate' flag is set, updates the collision mesh for the skinned mesh on this object
    134.     /// </summary>
    135.     void Update()
    136.     {
    137.         if (forceUpdate)
    138.         {
    139.             if (updateOncePerFrame) forceUpdate = false;
    140.             UpdateCollisionMesh();
    141.         }
    142.     }
    143. }
    144.  
    Also, rather here is an actual answer to the OP question: http://forum.unity3d.com/threads/134554-A-solution-for-accurate-raycasting-without-mesh-colliders
     
  14. Dorque

    Dorque

    Joined:
    Jul 19, 2012
    Posts:
    16
    Thank you very much for posting this. This did the trick for me. Thanks to Darkrobyn as well for the original code.
     
  15. rajat

    rajat

    Joined:
    Feb 4, 2013
    Posts:
    1
    Thanks a lot for this, this is amazing :cool: .
    $conana_approves.gif
     
  16. AZ2Tonez

    AZ2Tonez

    Joined:
    Feb 23, 2013
    Posts:
    1
    Very awesome - works perfectly!
     
  17. drudiverse

    drudiverse

    Joined:
    May 16, 2013
    Posts:
    210
    From a trick i saw on Fractal Forums:

    Take a flat pink object, place it backwards and forwards along the intended ray line, moving it backwards and forwrads by distances devided by 2, and compare the pixel color of that position every time it moves, and slowly narrow in on the distance that makes the screen pixel change from pink to not pink. then you have the precise position of the object, then you can find the nearest bounding box and the nearest vertex to that position. it could be abit difficult to implement with FOV?
     
  18. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    623
    Guys you're using ArrayList?! that's brutal! That's been deprecated ages ago when generics were introduced. Use a generic list instead.
     
  19. psyhova

    psyhova

    Joined:
    Oct 3, 2013
    Posts:
    32
    Hello Guys This is a great Script. But how can I delete the whole animated Mesh collision by preessing a key?
    nodeWeights.Clear(); does not work.