Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Moving an object along the surface of another object

Discussion in 'Scripting' started by RakkuAmiya, Dec 23, 2021.

  1. RakkuAmiya

    RakkuAmiya

    Joined:
    Jan 9, 2016
    Posts:
    53
    This is going to give me nightmares...

    I've been asked to try and add a feature to a unity game I'm helping to develop.

    The simple way to describe it is that when one small object hits a larger object, the criteria is for the small object to move around the surface of the larger object. I think everything in the game has either a mesh collider or a simple cylinder/cube/sphere collider.

    Has anyone any idea on how I might begin to look at this, or has solved a similar problem in the past?

    Many thanks;

    Graeme
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,724
    Well I did make this


    Is that similar to what you're looking for?
     
    RakkuAmiya likes this.
  3. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    I would heavily research normals and how they work as this looks like the topic most relevant to what you want. Now I've been going around topics like this issuing warnings for people but you will always run into accuracy problems no matter what method you implement with regards to any kind of placement or movement on a surface so don't be too frustrated if you can't get it exactly how you want as it's often about careful design and picking the right method for you.



    It is entirely possible to do what you're looking at you've just got to experiment a lot, the more complicated and bumpy the surface is you're working on the more likely you are to run into issues. Start off with relatively simple primitives then work your way up.
     
    Last edited: Dec 23, 2021
    RakkuAmiya likes this.
  4. RakkuAmiya

    RakkuAmiya

    Joined:
    Jan 9, 2016
    Posts:
    53
    Yes that's the sort of behaviour! The only difference would be the object tracking around the mesh in any direction. But otherwise yes that's it!

    Graeme
     
  5. RakkuAmiya

    RakkuAmiya

    Joined:
    Jan 9, 2016
    Posts:
    53
    Yes I'm familiar with normals. I have an idea in my head of the expected behaviour, and I appreciate that nothing will be super accurate. If I can make it work at all that'll be more than I currently expect. It's really got my brain in a twist.

    And yes simple shapes to start. Thankfully nothing in the project is bumpy...

    Thank you both for the replies. :)

    Graeme
     
  6. Serge_Billault

    Serge_Billault

    Joined:
    Aug 26, 2014
    Posts:
    190
    You can use this as a starting point, but it's not really good for anything more than a starting point:

    https://drive.google.com/file/d/1Vu_oEa9DQHsXKjWLLoL-GYEp5WvO1K2E/view?usp=sharing

    Once the project opened, you press Play and the little sphere will crawl all over the donut.

    screen_0.jpg
    Code (CSharp):
    1.  
    2. //************************************************************************
    3. //
    4. //************************************************************************
    5.  
    6. #define ROTATE_CRAWLER
    7.  
    8. //************************************************************************
    9. //
    10. //************************************************************************
    11.  
    12. using System;
    13. using System.Collections;
    14. using System.Collections.Generic;
    15. using UnityEngine;
    16.  
    17. //************************************************************************
    18. //
    19. //************************************************************************
    20.  
    21. static public class Vector3Extension
    22. {
    23.     static public float AbsSumDist( this Vector3 v )
    24.     {
    25.         return ( ( v.x < 0.0f ) ? -v.x : v.x ) + ( ( v.y < 0.0f ) ? -v.y : v.y ) + ( ( v.z < 0.0f ) ? -v.z : v.z );
    26.     }
    27. }
    28.  
    29. //************************************************************************
    30. //
    31. //************************************************************************
    32.  
    33. public class MeshCrawler : MonoBehaviour
    34. {
    35.     //********************************************************************
    36.     //
    37.     //********************************************************************
    38.  
    39.     private struct Contact
    40.     {
    41.         public int     tri;
    42.  
    43.         public Vector3 p;
    44.  
    45.         public Vector3 n;
    46.  
    47.         public float   t;
    48.     }
    49.  
    50.     //********************************************************************
    51.     //
    52.     //********************************************************************
    53.  
    54.     [ SerializeField ] private float           m_radius   = 0.5f;
    55.  
    56.     [ SerializeField ] private float           m_speed    = 1.0f;
    57.  
    58.     [ SerializeField ] private GameObject      m_support  = null;
    59.                                                          
    60.     [ NonSerialized  ] private Transform       m_xform    = null;
    61.                                                          
    62.     [ NonSerialized  ] private MeshFilter      m_filter   = null;
    63.                                                          
    64.     [ NonSerialized  ] private Mesh            m_mesh     = null;
    65.                                                          
    66.     [ NonSerialized  ] private Vector3[]       m_verts    = null;
    67.                                                          
    68.     [ NonSerialized  ] private int    []       m_tris     = null;
    69.                                                          
    70.     [ NonSerialized  ] private Vector3[]       m_normals  = null;
    71.                                                          
    72.     [ NonSerialized  ] private int             m_tri      = -1;
    73.  
    74.     [ NonSerialized  ] private List< Contact > m_contacts = new List< Contact >();
    75.  
    76.     #if ROTATE_CRAWLER
    77.  
    78.     [ NonSerialized  ] private float           m_rot_cur  = 0.0f;
    79.  
    80.     [ NonSerialized  ] private float           m_rot_trg  = 0.0f;
    81.  
    82.     #endif
    83.  
    84.     //********************************************************************
    85.     //
    86.     //********************************************************************
    87.  
    88.     private GameObject support { set { if( m_support != value ) { m_support  = value; UpdateTopology(); } } }
    89.  
    90.     //********************************************************************
    91.     //
    92.     //********************************************************************
    93.  
    94.     private void UpdateTopology()
    95.     {
    96.         m_xform   = ( m_support != null ) ? m_support.transform                  : null;
    97.                                            
    98.         m_filter  = ( m_xform   != null ) ? m_xform.GetComponent< MeshFilter >() : null;
    99.  
    100.         m_mesh    = ( m_filter  != null ) ? m_filter.sharedMesh                  : null;
    101.                                
    102.         m_verts   = ( m_mesh    != null ) ? m_mesh.vertices  : null;
    103.                                        
    104.         m_tris    = ( m_mesh    != null ) ? m_mesh.triangles : null;
    105.                                        
    106.         m_normals = ( m_mesh    != null ) ? m_mesh.normals   : null;
    107.  
    108.         m_tri     = -1;
    109.     }
    110.  
    111.     //********************************************************************
    112.     //
    113.     //********************************************************************
    114.  
    115.     static private bool PointInsideTri( Vector3 p, int tri, Vector3[] verts, int[] tris )
    116.     {
    117.         Vector3 v0 = verts[ tris[ tri     ] ];
    118.  
    119.         Vector3 v1 = verts[ tris[ tri + 1 ] ];
    120.  
    121.         Vector3 v2 = verts[ tris[ tri + 2 ] ];
    122.  
    123.  
    124.         Vector3 u = Vector3.Cross( v1 - v0, p - v0 );
    125.  
    126.         Vector3 v = Vector3.Cross( v2 - v1, p - v1 );
    127.  
    128.         Vector3 w = Vector3.Cross( v0 - v2, p - v2 );
    129.  
    130.  
    131.         if( Vector3.Dot( u, v ) < 0.0f ) return false;
    132.  
    133.         if( Vector3.Dot( u, w ) < 0.0f ) return false;
    134.  
    135.         return true;
    136.     }
    137.  
    138.     //********************************************************************
    139.     //
    140.     //********************************************************************
    141.  
    142.     static private bool RayCast( Vector3 p, Vector3 dir, int tri, Vector3 n, Vector3[] verts, int[] tris, ref Contact contact )
    143.     {
    144.         Vector3 v0 = verts[ tris[ tri ] ];
    145.  
    146.         float  dot = Vector3.Dot( v0 - p, n );
    147.  
    148.         if( dot <= 0.0f )
    149.         {
    150.             float   t = dot / Vector3.Dot( dir, n );
    151.                    
    152.             Vector3 c = p + ( dir * t );
    153.                    
    154.             if( PointInsideTri( c, tri, verts, tris ) )
    155.             {
    156.                 contact.p   = c;
    157.  
    158.                 contact.tri = tri;
    159.  
    160.                 contact.n   = n;
    161.  
    162.                 contact.t   = t;
    163.  
    164.                 return true;
    165.             }
    166.         }
    167.  
    168.         return false;
    169.     }
    170.  
    171.     //********************************************************************
    172.     //
    173.     //********************************************************************
    174.  
    175.     static private Vector3 GetNearestPointOnEdge( Vector3 p, Vector3 e0, Vector3 e1 )
    176.     {
    177.         Vector3 v = p  - e0;
    178.  
    179.         Vector3 V = e1 - e0;
    180.  
    181.         if( Vector3.Dot( v, V )            <= 0.0f ) return e0;
    182.  
    183.         if( Vector3.Dot( p - e1, e0 - e1 ) <= 0.0f ) return e1;
    184.  
    185.         V.Normalize();
    186.  
    187.         return e0 + ( V * Vector3.Dot( v, V ) );
    188.     }
    189.  
    190.     //********************************************************************
    191.     //
    192.     //********************************************************************
    193.  
    194.     static private bool SphereIntersectTri( Vector3 p, float radius, Bounds bnd, int tri, Vector3[] verts, int[] tris, ref Contact contact )
    195.     {
    196.         Vector3 v0 = verts[ tris[ tri     ] ];
    197.  
    198.         Vector3 v1 = verts[ tris[ tri + 1 ] ];
    199.  
    200.         Vector3 v2 = verts[ tris[ tri + 2 ] ];
    201.  
    202.  
    203.         Bounds tri_bnd = default;
    204.  
    205.         tri_bnd.min = new Vector3( Mathf.Min( v0.x, v1.x, v2.x ), Mathf.Min( v0.y, v1.y, v2.y ), Mathf.Min( v0.z, v1.z, v2.z ) );
    206.  
    207.         tri_bnd.max = new Vector3( Mathf.Max( v0.x, v1.x, v2.x ), Mathf.Max( v0.y, v1.y, v2.y ), Mathf.Max( v0.z, v1.z, v2.z ) );
    208.  
    209.         if( bnd.Intersects( tri_bnd ) == false ) return false;
    210.  
    211.  
    212.         Vector3 n  = Vector3.Cross( v1 - v0, v2 - v0 );
    213.  
    214.         if( Vector3.Dot( v0 - p, n ) <= 0.0f )
    215.         {
    216.             n.Normalize();
    217.  
    218.             float sqr_radius = radius * radius;
    219.  
    220.             Contact  ray_hit = default;
    221.  
    222.             if( RayCast( p, -n, tri, n, verts, tris, ref ray_hit ) )
    223.             {
    224.                 if( ( ray_hit.p - p ).sqrMagnitude <= sqr_radius ) { contact = ray_hit; return true; }
    225.             }
    226.  
    227.  
    228.             float   min_dist  = float.MaxValue;
    229.  
    230.             int     nearest   = -1;
    231.  
    232.             Vector3 nearest_0 = GetNearestPointOnEdge( p, v0, v1 ); float dist_0 = ( nearest_0 - p ).AbsSumDist();
    233.  
    234.             Vector3 nearest_1 = GetNearestPointOnEdge( p, v1, v2 ); float dist_1 = ( nearest_1 - p ).AbsSumDist();
    235.  
    236.             Vector3 nearest_2 = GetNearestPointOnEdge( p, v2, v0 ); float dist_2 = ( nearest_2 - p ).AbsSumDist();
    237.  
    238.             if( ( dist_0 <= sqr_radius ) )                          { min_dist = dist_0; nearest = 0; }
    239.  
    240.             if( ( dist_1 <= sqr_radius ) && ( min_dist > dist_1 ) ) { min_dist = dist_1; nearest = 1; }
    241.  
    242.             if( ( dist_2 <= sqr_radius ) && ( min_dist > dist_2 ) ) { min_dist = dist_2; nearest = 2; }
    243.  
    244.             if( nearest >= 0 )
    245.             {
    246.                 contact.tri = tri;
    247.                
    248.                 contact.n   = n;
    249.  
    250.                 switch( nearest )
    251.                 {
    252.                     case 0: contact.p = nearest_0; break;
    253.  
    254.                     case 1: contact.p = nearest_1; break;
    255.  
    256.                     case 2: contact.p = nearest_2; break;
    257.                 }
    258.                
    259.                 return true;
    260.             }
    261.         }
    262.  
    263.         return false;
    264.     }
    265.  
    266.     //********************************************************************
    267.     //
    268.     //********************************************************************
    269.  
    270.     static private void GetNearestTris( Vector3 p, float radius, Vector3[] verts, int[] tris, Vector3[] normals, Transform xform, List< Contact > result )
    271.     {
    272.         result.Clear();
    273.  
    274.         Vector3 local_p = ( xform != null ) ? xform.InverseTransformPoint( p ) : p;
    275.  
    276.         Bounds  bnd     = default;
    277.  
    278.         Contact contact = default;
    279.  
    280.  
    281.         Vector3 bnd_ext = Vector3.one * radius;
    282.  
    283.         bnd.min = local_p - bnd_ext;
    284.  
    285.         bnd.max = local_p + bnd_ext;
    286.  
    287.  
    288.         for( int t = 0, count = tris.Length; t < count; t += 3 )
    289.         {
    290.             if( SphereIntersectTri( local_p, radius, bnd, t, verts, tris, ref contact ) )
    291.             {
    292.                 if( xform != null )
    293.                 {
    294.                     contact.p = xform.TransformPoint    ( contact.p );
    295.  
    296.                     contact.n = xform.TransformDirection( contact.n );
    297.                 }
    298.  
    299.                 result.Add( contact );
    300.             }
    301.         }
    302.     }
    303.  
    304.     //********************************************************************
    305.     //
    306.     //********************************************************************
    307.  
    308.     private void Start()
    309.     {
    310.         UpdateTopology();
    311.     }
    312.  
    313.     //********************************************************************
    314.     //
    315.     //********************************************************************
    316.  
    317.     private void Crawl()
    318.     {
    319.         if( m_xform    == null ) return;
    320.                        
    321.         if( m_verts    == null ) return;
    322.                        
    323.         if( m_tris     == null ) return;
    324.                        
    325.         if( m_normals  == null ) return;
    326.  
    327.         if( m_contacts == null ) return;
    328.  
    329.         GetNearestTris( transform.position, m_radius, m_verts, m_tris, m_normals, m_xform, m_contacts );
    330.  
    331.         if( m_contacts.Count <= 0 ) return;
    332.  
    333.  
    334.         Contact nearest  = default;
    335.  
    336.         float   min_dist = float.MaxValue;
    337.  
    338.         for( int c = 0, count = m_contacts.Count; c < count; ++c )
    339.         {
    340.             Contact contact = m_contacts[ c ];
    341.  
    342.             float dist = ( m_contacts[ c ].p - transform.position ).AbsSumDist();
    343.  
    344.             if( ( min_dist > dist ) || ( ( min_dist == dist ) && ( m_tri != contact.tri ) ) )
    345.             {
    346.                 min_dist = dist;
    347.  
    348.                 nearest  = contact;
    349.  
    350.                 m_tri    = contact.tri;
    351.             }
    352.         }
    353.  
    354.        
    355.  
    356.         #if ROTATE_CRAWLER
    357.  
    358.             if( Mathf.Abs( m_rot_cur ) >= Mathf.Abs( m_rot_trg ) )
    359.             {
    360.                 m_rot_cur = 0.0f;
    361.            
    362.                 m_rot_trg = UnityEngine.Random.Range( -45.0f, 45.0f );
    363.             }
    364.  
    365.             float delta = m_rot_trg * Time.deltaTime;
    366.  
    367.             m_rot_cur   = Mathf.Clamp( m_rot_cur + delta, -m_rot_trg, m_rot_trg );
    368.  
    369.             Quaternion quat = Quaternion.AngleAxis( delta, nearest.n );
    370.  
    371.         #else
    372.  
    373.             Quaternion quat = Quaternion.identity;
    374.  
    375.         #endif
    376.  
    377.  
    378.         Vector3 move_dir = Vector3.Cross( transform.right, nearest.n );
    379.  
    380.         transform.LookAt( transform.position + move_dir, nearest.n );
    381.  
    382.         transform.position = nearest.p + ( transform.forward * m_speed * Mathf.Min( Time.deltaTime, 0.016f ) ) + ( nearest.n * 0.01f );
    383.  
    384.         transform.rotation = quat * transform.rotation;
    385.     }
    386.  
    387.     //********************************************************************
    388.     //
    389.     //********************************************************************
    390.  
    391.     private void Update()
    392.     {
    393.         Crawl();
    394.     }
    395.  
    396.     //********************************************************************
    397.     //
    398.     //********************************************************************
    399.  
    400.     private void OnDrawGizmos()
    401.     {
    402.         Color restore = Gizmos.color;
    403.  
    404.             Vector3  sze = Vector3.one * 0.05f;
    405.  
    406.             Gizmos.color = Color.blue;
    407.  
    408.             for( int c = 0, count = m_contacts.Count; c < count; ++c )
    409.             {
    410.                 Contact contact = m_contacts[ c ];
    411.  
    412.                 Gizmos.DrawCube( contact.p, sze );
    413.             }
    414.  
    415.             Gizmos.color = new Color( Color.yellow.r, Color.yellow.g, Color.yellow.b, 0.25f );
    416.  
    417.             Gizmos.DrawSphere( transform.position, m_radius );
    418.  
    419.         Gizmos.color = restore;
    420.     }
    421. }
    422.  
     
    Last edited: Dec 25, 2021
    quaterhe and RakkuAmiya like this.
  7. Serge_Billault

    Serge_Billault

    Joined:
    Aug 26, 2014
    Posts:
    190
    I have fixed a discrepency in SphereIntersectTri(), added a little optimization, and you can now also increase crawling speed up to detect_radius - epsilon (still I would advise using simplified meshes for that kind of thing because of the thousand of tiny triangles you can have on next gen models). For exemple in the demo below the speed is at 5 Unity units per seconds, but I was able to increase it to 20.

    capture.gif
     
    Last edited: Dec 26, 2021
    Luferau, quaterhe and RakkuAmiya like this.
  8. RakkuAmiya

    RakkuAmiya

    Joined:
    Jan 9, 2016
    Posts:
    53
    Apologies I have been busy with work and I've only just seen this! Thank you for posting this example. You sir are a legend.

    Many thanks;

    Graeme