Search Unity

Buoyancy script

Discussion in 'Made With Unity' started by alexzzzz, Jan 6, 2011.

  1. idurvesh

    idurvesh

    Joined:
    Jun 9, 2014
    Posts:
    495
    How to make it work on non flat planes? For example on river where downward slopes and y position will not be same?
     
  2. idurvesh

    idurvesh

    Joined:
    Jun 9, 2014
    Posts:
    495
  3. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Change the method GetWaterLevel(float x, float z) to return the real water level if on river and, say, -1000 if on land.
     
  4. idurvesh

    idurvesh

    Joined:
    Jun 9, 2014
    Posts:
    495
    Yes I tried it, but my river is modeler i.e., different modules joint to make single river so how can I get different position of water dynamically? when I up-down my existing water, its now working but after changing water module dynamically its not working....

    Here is code to how I accomplished dynamic height of water
    Code (CSharp):
    1.  
    2.         RaycastHit hit;
    3.         if (Physics.Raycast(transform.position, Vector3.down * 500, out hit,waterMask))
    4.         {
    5.  
    6.  
    7.          
    8.                 Vector3 temPos= Vector3.zero;
    9.                 temPos.x = x;
    10.                 temPos.z = z;
    11.  
    12.                 float h = hit.transform.worldToLocalMatrix.MultiplyPoint(temPos).y;
    13.  
    14.  
    15.      
    16.                 return h + hit.point.y ;
    17.  
    18.              
    19.  
    20.  
    21.  
    22.  
    23.         }
    24.        
    25.        
     
  5. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Code (CSharp):
    1.     float GetWaterLevel(float x, float z)
    2.     {
    3.         RaycastHit hit;
    4.         if (Physics.Raycast(new Vector3(x, 250, z), Vector3.down * 500, out hit, waterMask))
    5.         {
    6.             return hit.point.y;
    7.         }
    8.         return -1000;
    9.     }
    10.  
    However, the performance will suffer, I guess, from the excessive amount of raycasts.
     
  6. idurvesh

    idurvesh

    Joined:
    Jun 9, 2014
    Posts:
    495
    Thanks for code,I tried it,however it doesn't seem to work properly, Player falls through plane and raycast is spawning at different place than expected.Here is screenshot of it.

    http://imgur.com/oRJKRK2
     
  7. chushuxiang

    chushuxiang

    Joined:
    Jan 27, 2015
    Posts:
    1
    HI alexzzzz and guys
    ,thanks for share.It is very great work.
    I confused in the script why you used"
    1. var originalRotation = transform.rotation;
    2. var originalPosition = transform.position;
    3. transform.rotation = Quaternion.identity;
    4. transform.position = Vector3.zero;
    "and then restore the transform.Why did u that in script and what is mean of it ? I am a student from Asia. Look forward to your reply. Very very thanks! Forgive my English ....
     
  8. dienat

    dienat

    Joined:
    May 27, 2016
    Posts:
    417
    Is this script public domain, free to use for commercial games?
     
  9. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Sorry, I didn't think about the license. Let it officially be WTFPL version 2.
     
    dienat likes this.
  10. dienat

    dienat

    Joined:
    May 27, 2016
    Posts:
    417
    Thanks and good job with this script
     
  11. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I hope it still works.
     
  12. dienat

    dienat

    Joined:
    May 27, 2016
    Posts:
    417
    I notice that when i have the cam attached to the ship with buoyancy the cam jitters with big waves ( i am using Ceto ), do you know why this happens?
     
  13. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Most likely it's because the physics frequency and framerate are different. Two possible solution:
    1. Enable interpolation/extrapolation for the ship's rigidbody.
    2. Detach the camera from the ship and control it with some script that will make the camera smoothly follow a target.
     
  14. dienat

    dienat

    Joined:
    May 27, 2016
    Posts:
    417
    I found the solution, it was a Ceto problem, thanks anyway.
     
  15. Wolfshadow

    Wolfshadow

    Joined:
    Jul 7, 2015
    Posts:
    2
    Brilliant!
     
  16. Wolfshadow

    Wolfshadow

    Joined:
    Jul 7, 2015
    Posts:
    2
    How does this scale of 1000 as middle relate to grams per cubic centimeters?
     
  17. jmarcos007

    jmarcos007

    Joined:
    Mar 2, 2017
    Posts:
    11
    Hi! First, excuse my english. I´m new on Unity and working on a script for buoyancy. I'd like a function or script to return the vertical distance (depth) from an object to water surface. I´m using 'Water4Advanced' (from Unity) that have waves with dynamic mesh. I spent lots of hours looking for, but a can´t implement any found idea. Thats because I´m new on C# and Unity. Somebody can help me? Thanks.
     
  18. rockbyte

    rockbyte

    Joined:
    May 1, 2017
    Posts:
    6
    Below are the changes I performed to the v2.1 script to work well with my Unity 5.5. What I changed:
    - Added requirement for RigidBody/Collider (does not need to perform this check in the code now)
    - Use RigidBody and Collider Components, instead of the legacy properties

    Code (csharp):
    1.  
    2. // Buoyancy.cs
    3. // by Alex Zhdankin
    4. // Version 2.1.1?
    5. //
    6. // https://forum.unity3d.com/threads/buoyancy-script.72974/
    7. //
    8. // Terms of use: do whatever you like
    9.  
    10.     using System.Collections.Generic;
    11.     using UnityEngine;
    12.  
    13.     [RequireComponent(typeof(Rigidbody))]
    14.     [RequireComponent(typeof(Collider))]
    15.     public class Buoyancy : MonoBehaviour
    16.     {
    17.         public float density = 500;
    18.         public int slicesPerAxis = 2;
    19.         public bool isConcave = false;
    20.         public int voxelsLimit = 16;
    21.  
    22.         private const float DAMPFER = 0.1f;
    23.         private const float WATER_DENSITY = 1000;
    24.  
    25.         private float voxelHalfHeight;
    26.         private Vector3 localArchimedesForce;
    27.         private List<Vector3> voxels;
    28.         private bool isMeshCollider;
    29.         private List<Vector3[]> forces; // For drawing force gizmos
    30.  
    31.         private Rigidbody rigidBody;
    32.         private Collider collider;
    33.  
    34.         /// <summary>
    35.         /// Provides initialization.
    36.         /// </summary>
    37.         private void Start()
    38.         {
    39.             forces = new List<Vector3[]>(); // For drawing force gizmos
    40.             rigidBody = GetComponent<Rigidbody>();
    41.             collider = GetComponent<Collider>();
    42.  
    43.             // Store original rotation and position
    44.             var originalRotation = transform.rotation;
    45.             var originalPosition = transform.position;
    46.             transform.rotation = Quaternion.identity;
    47.             transform.position = Vector3.zero;
    48.  
    49.             isMeshCollider = GetComponent<MeshCollider>() != null;
    50.  
    51.             var bounds = collider.bounds;
    52.             if (bounds.size.x < bounds.size.y)
    53.             {
    54.                 voxelHalfHeight = bounds.size.x;
    55.             }
    56.             else
    57.             {
    58.                 voxelHalfHeight = bounds.size.y;
    59.             }
    60.             if (bounds.size.z < voxelHalfHeight)
    61.             {
    62.                 voxelHalfHeight = bounds.size.z;
    63.             }
    64.             voxelHalfHeight /= 2 * slicesPerAxis;
    65.  
    66.             rigidBody.centerOfMass = new Vector3(0, -bounds.extents.y * 0f, 0) + transform.InverseTransformPoint(bounds.center);
    67.  
    68.             voxels = SliceIntoVoxels(isMeshCollider && isConcave);
    69.  
    70.             // Restore original rotation and position
    71.             transform.rotation = originalRotation;
    72.             transform.position = originalPosition;
    73.  
    74.             float volume = rigidBody.mass / density;
    75.  
    76.             WeldPoints(voxels, voxelsLimit);
    77.  
    78.             float archimedesForceMagnitude = WATER_DENSITY * Mathf.Abs(Physics.gravity.y) * volume;
    79.             localArchimedesForce = new Vector3(0, archimedesForceMagnitude, 0) / voxels.Count;
    80.  
    81.             Debug.Log(string.Format("[Buoyancy.cs] Name=\"{0}\" volume={1:0.0}, mass={2:0.0}, density={3:0.0}", name, volume, rigidBody.mass, density));
    82.         }
    83.  
    84.         /// <summary>
    85.         /// Slices the object into number of voxels represented by their center points.
    86.         /// <param name="concave">Whether the object have a concave shape.</param>
    87.         /// <returns>List of voxels represented by their center points.</returns>
    88.         /// </summary>
    89.         private List<Vector3> SliceIntoVoxels(bool concave)
    90.         {
    91.             var points = new List<Vector3>(slicesPerAxis * slicesPerAxis * slicesPerAxis);
    92.  
    93.             if (concave)
    94.             {
    95.                 var meshCol = GetComponent<MeshCollider>();
    96.  
    97.                 var convexValue = meshCol.convex;
    98.                 meshCol.convex = false;
    99.  
    100.                 // Concave slicing
    101.                 var bounds = collider.bounds;
    102.                 for (int ix = 0; ix < slicesPerAxis; ix++)
    103.                 {
    104.                     for (int iy = 0; iy < slicesPerAxis; iy++)
    105.                     {
    106.                         for (int iz = 0; iz < slicesPerAxis; iz++)
    107.                         {
    108.                             float x = bounds.min.x + bounds.size.x / slicesPerAxis * (0.5f + ix);
    109.                             float y = bounds.min.y + bounds.size.y / slicesPerAxis * (0.5f + iy);
    110.                             float z = bounds.min.z + bounds.size.z / slicesPerAxis * (0.5f + iz);
    111.  
    112.                             var p = transform.InverseTransformPoint(new Vector3(x, y, z));
    113.  
    114.                             if (PointIsInsideMeshCollider(meshCol, p))
    115.                             {
    116.                                 points.Add(p);
    117.                             }
    118.                         }
    119.                     }
    120.                 }
    121.                 if (points.Count == 0)
    122.                 {
    123.                     points.Add(bounds.center);
    124.                 }
    125.  
    126.                 meshCol.convex = convexValue;
    127.             }
    128.             else
    129.             {
    130.                 // Convex slicing
    131.                 var bounds = GetComponent<Collider>().bounds;
    132.                 for (int ix = 0; ix < slicesPerAxis; ix++)
    133.                 {
    134.                     for (int iy = 0; iy < slicesPerAxis; iy++)
    135.                     {
    136.                         for (int iz = 0; iz < slicesPerAxis; iz++)
    137.                         {
    138.                             float x = bounds.min.x + bounds.size.x / slicesPerAxis * (0.5f + ix);
    139.                             float y = bounds.min.y + bounds.size.y / slicesPerAxis * (0.5f + iy);
    140.                             float z = bounds.min.z + bounds.size.z / slicesPerAxis * (0.5f + iz);
    141.  
    142.                             var p = transform.InverseTransformPoint(new Vector3(x, y, z));
    143.  
    144.                             points.Add(p);
    145.                         }
    146.                     }
    147.                 }
    148.             }
    149.  
    150.             return points;
    151.         }
    152.  
    153.         /// <summary>
    154.         /// Returns whether the point is inside the mesh collider.
    155.         /// </summary>
    156.         /// <param name="c">Mesh collider.</param>
    157.         /// <param name="p">Point.</param>
    158.         /// <returns>True - the point is inside the mesh collider. False - the point is outside of the mesh collider. </returns>
    159.         private static bool PointIsInsideMeshCollider(Collider c, Vector3 p)
    160.         {
    161.             Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };
    162.  
    163.             foreach (var ray in directions)
    164.             {
    165.                 RaycastHit hit;
    166.                 if (c.Raycast(new Ray(p - ray * 1000, ray), out hit, 1000f) == false)
    167.                 {
    168.                     return false;
    169.                 }
    170.             }
    171.  
    172.             return true;
    173.         }
    174.  
    175.         /// <summary>
    176.         /// Returns two closest points in the list.
    177.         /// </summary>
    178.         /// <param name="list">List of points.</param>
    179.         /// <param name="firstIndex">Index of the first point in the list. It's always less than the second index.</param>
    180.         /// <param name="secondIndex">Index of the second point in the list. It's always greater than the first index.</param>
    181.         private static void FindClosestPoints(IList<Vector3> list, out int firstIndex, out int secondIndex)
    182.         {
    183.             float minDistance = float.MaxValue, maxDistance = float.MinValue;
    184.             firstIndex = 0;
    185.             secondIndex = 1;
    186.  
    187.             for (int i = 0; i < list.Count - 1; i++)
    188.             {
    189.                 for (int j = i + 1; j < list.Count; j++)
    190.                 {
    191.                     float distance = Vector3.Distance(list[i], list[j]);
    192.                     if (distance < minDistance)
    193.                     {
    194.                         minDistance = distance;
    195.                         firstIndex = i;
    196.                         secondIndex = j;
    197.                     }
    198.                     if (distance > maxDistance)
    199.                     {
    200.                         maxDistance = distance;
    201.                     }
    202.                 }
    203.             }
    204.         }
    205.  
    206.         /// <summary>
    207.         /// Welds closest points.
    208.         /// </summary>
    209.         /// <param name="list">List of points.</param>
    210.         /// <param name="targetCount">Target number of points in the list.</param>
    211.         private static void WeldPoints(IList<Vector3> list, int targetCount)
    212.         {
    213.             if (list.Count <= 2 || targetCount < 2)
    214.             {
    215.                 return;
    216.             }
    217.  
    218.             while (list.Count > targetCount)
    219.             {
    220.                 int first, second;
    221.                 FindClosestPoints(list, out first, out second);
    222.  
    223.                 var mixed = (list[first] + list[second]) * 0.5f;
    224.                 list.RemoveAt(second); // the second index is always greater that the first => removing the second item first
    225.                 list.RemoveAt(first);
    226.                 list.Add(mixed);
    227.             }
    228.         }
    229.  
    230.         /// <summary>
    231.         /// Returns the water level at given location.
    232.         /// </summary>
    233.         /// <param name="x">x-coordinate</param>
    234.         /// <param name="z">z-coordinate</param>
    235.         /// <returns>Water level</returns>
    236.         private float GetWaterLevel(float x, float z)
    237.         {
    238. //    return ocean == null ? 0.0f : ocean.GetWaterHeightAtLocation(x, z);
    239.             return 5.0f;
    240.         }
    241.  
    242.         /// <summary>
    243.         /// Calculates physics.
    244.         /// </summary>
    245.         private void FixedUpdate()
    246.         {
    247.             forces.Clear(); // For drawing force gizmos
    248.  
    249.             foreach (var point in voxels)
    250.             {
    251.                 var wp = transform.TransformPoint(point);
    252.                 float waterLevel = GetWaterLevel(wp.x, wp.z);
    253.  
    254.                 if (wp.y - voxelHalfHeight < waterLevel)
    255.                 {
    256.                     float k = (waterLevel - wp.y) / (2 * voxelHalfHeight) + 0.5f;
    257.                     if (k > 1)
    258.                     {
    259.                         k = 1f;
    260.                     }
    261.                     else if (k < 0)
    262.                     {
    263.                         k = 0f;
    264.                     }
    265.  
    266.                     var velocity = rigidBody.GetPointVelocity(wp);
    267.                     var localDampingForce = -velocity * DAMPFER * rigidBody.mass;
    268.                     var force = localDampingForce + Mathf.Sqrt(k) * localArchimedesForce;
    269.                     rigidBody.AddForceAtPosition(force, wp);
    270.  
    271.                     forces.Add(new[] { wp, force }); // For drawing force gizmos
    272.                 }
    273.             }
    274.         }
    275.  
    276.         /// <summary>
    277.         /// Draws gizmos.
    278.         /// </summary>
    279.         private void OnDrawGizmos()
    280.         {
    281.             if (voxels == null || forces == null)
    282.             {
    283.                 return;
    284.             }
    285.  
    286.             const float gizmoSize = 0.05f;
    287.             Gizmos.color = Color.yellow;
    288.  
    289.             foreach (var p in voxels)
    290.             {
    291.                 Gizmos.DrawCube(transform.TransformPoint(p), new Vector3(gizmoSize, gizmoSize, gizmoSize));
    292.             }
    293.  
    294.             Gizmos.color = Color.cyan;
    295.  
    296.             foreach (var force in forces)
    297.             {
    298.                 Gizmos.DrawCube(force[0], new Vector3(gizmoSize, gizmoSize, gizmoSize));
    299.                 Gizmos.DrawLine(force[0], force[0] + force[1] / rigidBody.mass);
    300.             }
    301.         }
    302.     }
    303.  
     
    Last edited: May 28, 2017
    Vytek and cartridgegamestudio like this.
  19. kotaleks2990

    kotaleks2990

    Joined:
    May 28, 2017
    Posts:
    1


    I have a mistake: Assets/_Script/Buoyancy.cs(190,30): error CS1502: The best overloaded method match for `UnityEngine.Vector3.Distance(UnityEngine.Vector3, UnityEngine.Vector3)' has some invalid arguments
     
  20. rockbyte

    rockbyte

    Joined:
    May 1, 2017
    Posts:
    6
    You're right, somehow when I placed the script here I must have taken out the array selection by accident. the problem is in line 191:
    Code (csharp):
    1. float distance = Vector3.Distance(list, list[j]);
    which should be:

    Code (csharp):
    1. float distance = Vector3.Distance(list[i], list[j]);
    I have corrected the original post now.
     
  21. JC_LEON

    JC_LEON

    Joined:
    May 20, 2014
    Posts:
    520
    thanks for the script..but how cna i do to make it to work if i have water on different heights??
    and make items bouyancy only when collide with water??
     
  22. doppie320

    doppie320

    Joined:
    Feb 13, 2017
    Posts:
    7
    hey, im having a little problem, you commented out the ocean and others, but can you tell me how to set it up? because as far as i know, ocean is in unity
     
  23. Deleted User

    Deleted User

    Guest

    Thank you for making this one free to use. It's a really cool piece of works man, and it produces really realistic-looking results. I'm glad I tried it, it's gonna end up be the first thing I use that's %100 borrowed other than Unity itself. (which is ALOT^2) I know this thread's mega old but it's still high on the search results.

    So like, good job, thanks.

    For anyone following, at least the first C# file in this thread, you have a static 0-level for the water in there. There's an "ocean" commented out. It's suggesting you could use arrays to get a height-per-(x,y position) I think. If it's just "steady froth/churn/waves" but on a plane, a fixed height in the middle should look OK. If it's a river going down a hill, there's a place where you need to make an "= 0" equal something other than 0 for the height based on a position. Personally, I would just use another raycast to test the height.
     
    valentingurkov likes this.
  24. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    Hi,

    thanks for the awesome asset. I have a problem though.

    There is an offset between the collider and the voxels which are used to compute the up-force. When I place the GameObject at (0,0,0) it works fine, but when I move it in any direction from the origin the offset gets bigger. Am I doing something wrong?

     
  25. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    Figured it out, the bounds are not up to date after the transform is moved to the origin in the Start method. (Line 46) They can be updated via calling
    Physics.SyncTransforms();
    right after moving it.
     
    deekpyro likes this.
  26. warman88

    warman88

    Joined:
    Dec 27, 2020
    Posts:
    6
    It says the first line public Ocean ocean; is a error
     
  27. McGravity

    McGravity

    Joined:
    Nov 2, 2013
    Posts:
    60
    What's the error message?
     
  28. RealDogGuy

    RealDogGuy

    Joined:
    Oct 23, 2021
    Posts:
    14
    If an object is not at 0,0,0 the points move to the direction its at and it doesnt center and it floats in the air
     

    Attached Files:

  29. alezxdigital2020

    alezxdigital2020

    Joined:
    Oct 9, 2022
    Posts:
    1
    does not work at all
     
  30. inyourpc

    inyourpc

    Joined:
    Jul 14, 2013
    Posts:
    9
    Use "buyont object" from boat attack unity official github project, or just download the project from github for learning?
     
  31. Chabidou

    Chabidou

    Joined:
    Oct 14, 2022
    Posts:
    1
    Bonjour,
    Merci beaucoup pour ce script, c'est génial !
    Il fonctionne parfaitement sur l'effet flotteur et lorsque je tourne.
    Malheureusement, quand j'avance ou recule avec mon bateau, il tourne sur lui-même sans avancer.
    Avez-vous une idée de ce qui pourrait causer cela s'il vous plaît?
    Dans la console Unity, l'erreur affichée est que je dois activer la cinématique mais lorsque je l'active l'effet flottant ne fonctionne plus du tout.
    Je me forme au dev de jeu et je ne comprends pas encore tout :)
    Merci d'avance
    Charlotte
     
  32. NyghtHawk

    NyghtHawk

    Joined:
    Apr 21, 2018
    Posts:
    1
    Updated script. Including required
    Physics.SyncTransforms();
    mentioned above.

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. // Buoyancy.cs
    7. // Version 3.0.0?
    8. // https://forum.unity3d.com/threads/buoyancy-script.72974/
    9. // Terms of use: do whatever you like
    10. public class Buoyancy : MonoBehaviour
    11. {
    12.     [Header("Settings")]
    13.     [SerializeField] private float density = 500;
    14.     [SerializeField] private int slicesPerAxis = 2;
    15.     [SerializeField] private bool isConcave = false;
    16.     [SerializeField] private int voxelsLimit = 16;
    17.  
    18.     [Header("Components")]
    19.     [SerializeField] private Transform waterTransform;
    20.     [SerializeField] private bool autoGetComponents = true;
    21.     [SerializeField] private Rigidbody buoyancyRigidbody;
    22.     [SerializeField] private Collider buoyancyCollider;
    23.     [SerializeField] private MeshCollider buoyancyMeshCollider;
    24.  
    25.     private const float Dampener = 0.1f;
    26.     private const float WaterDensity = 500;
    27.  
    28.     private float _voxelHalfHeight;
    29.     private Vector3 _localArchimedesForce;
    30.     private List<Vector3> _voxels = new List<Vector3>();
    31.     private bool _hasMeshCollider;
    32.     private Vector3 _forceOffset;
    33.  
    34.     #if UNITY_EDITOR
    35.     private readonly List<Vector3[]> _forces = new List<Vector3[]>(); // For drawing force gizmos
    36.     #endif
    37.  
    38.     /// <summary>
    39.     /// Init object components
    40.     /// </summary>
    41.     private void Awake()
    42.     {
    43.         if (!autoGetComponents)
    44.         {
    45.             return;
    46.         }
    47.  
    48.         if (TryGetComponent(out Rigidbody foundRigidbody))
    49.         {
    50.             buoyancyRigidbody = foundRigidbody;
    51.         }
    52.          
    53.         if (TryGetComponent(out Collider foundCollider))
    54.         {
    55.             buoyancyCollider = foundCollider;
    56.         }
    57.          
    58.         if (TryGetComponent(out MeshCollider foundMeshCollider))
    59.         {
    60.             buoyancyMeshCollider = foundMeshCollider;
    61.         }
    62.     }
    63.  
    64.     /// <summary>
    65.     /// Setup physics.
    66.     /// </summary>
    67.     private IEnumerator Start()
    68.     {
    69.         if (isConcave)
    70.         {
    71.             if (buoyancyMeshCollider == null)
    72.             {
    73.                 Debug.LogError("Concave Buoyant object requires a mesh collider", gameObject);
    74.             }
    75.             else
    76.             {
    77.                 _hasMeshCollider = true;
    78.             }
    79.         }
    80.         else if (buoyancyCollider == null)
    81.         {
    82.             Debug.LogError("Non-Concave Buoyant object requires a mesh collider", gameObject);
    83.         }
    84.  
    85.         var originalRotation = buoyancyRigidbody.rotation;
    86.         var originalPosition = buoyancyRigidbody.position;
    87.         buoyancyRigidbody.rotation = Quaternion.identity;
    88.         buoyancyRigidbody.position = Vector3.zero;
    89.  
    90.         yield return new WaitForFixedUpdate();
    91.  
    92.         var bounds = buoyancyCollider.bounds;
    93.         _voxelHalfHeight = bounds.size.x < bounds.size.y ? bounds.size.x : bounds.size.y;
    94.         if (bounds.size.z < _voxelHalfHeight)
    95.         {
    96.             _voxelHalfHeight = bounds.size.z;
    97.         }
    98.         _voxelHalfHeight /= 2 * slicesPerAxis;
    99.  
    100.         buoyancyRigidbody.centerOfMass = new Vector3(0, -bounds.extents.y * 0f, 0) + transform.InverseTransformPoint(bounds.center);
    101.  
    102.         _voxels = _hasMeshCollider && isConcave ? SliceIntoConcaveVoxels() : SliceIntoConvexVoxels();
    103.  
    104.         float volume = buoyancyRigidbody.mass / density;
    105.  
    106.         WeldPoints(_voxels, voxelsLimit);
    107.  
    108.         float archimedesForceMagnitude = WaterDensity * Mathf.Abs(Physics.gravity.y) * volume;
    109.         _localArchimedesForce = new Vector3(0, archimedesForceMagnitude, 0) / _voxels.Count;
    110.  
    111.         Physics.SyncTransforms();
    112.      
    113.         buoyancyRigidbody.rotation = originalRotation;
    114.         buoyancyRigidbody.position = originalPosition;
    115.      
    116.         buoyancyRigidbody.velocity = Vector3.zero;
    117.         buoyancyRigidbody.angularVelocity = Vector3.zero;
    118.  
    119.         #if UNITY_EDITOR
    120.         Debug.Log($"[Buoyancy.cs] Name=\"{name}\" volume={volume:0.0}, mass={buoyancyRigidbody.mass:0.0}, density={density:0.0}", gameObject);
    121.         #endif
    122.     }
    123.  
    124.     /// <summary>
    125.     /// Update force offset
    126.     /// </summary>
    127.     public void UpdateForceOffset(Vector3 forceOffset)
    128.     {
    129.         _forceOffset = forceOffset;
    130.     }
    131.  
    132.     /// <summary>
    133.     /// Slices the object into number of voxels represented by their center points.
    134.     /// <returns>List of voxels represented by their center points.</returns>
    135.     /// </summary>
    136.     private List<Vector3> SliceIntoConvexVoxels()
    137.     {
    138.         var points = new List<Vector3>(slicesPerAxis * slicesPerAxis * slicesPerAxis);
    139.  
    140.         // Convex slicing
    141.         var bounds = buoyancyCollider.bounds;
    142.         for (int ix = 0; ix < slicesPerAxis; ix++)
    143.         {
    144.             for (int iy = 0; iy < slicesPerAxis; iy++)
    145.             {
    146.                 for (int iz = 0; iz < slicesPerAxis; iz++)
    147.                 {
    148.                     float x = bounds.min.x + bounds.size.x / slicesPerAxis * (0.5f + ix);
    149.                     float y = bounds.min.y + bounds.size.y / slicesPerAxis * (0.5f + iy);
    150.                     float z = bounds.min.z + bounds.size.z / slicesPerAxis * (0.5f + iz);
    151.  
    152.                     var point = transform.InverseTransformPoint(new Vector3(x, y, z));
    153.  
    154.                     points.Add(point);
    155.                 }
    156.             }
    157.         }
    158.  
    159.         return points;
    160.     }
    161.  
    162.     /// <summary>
    163.     /// Slices the object into number of voxels represented by their center points.
    164.     /// <returns>List of voxels represented by their center points.</returns>
    165.     /// </summary>
    166.     private List<Vector3> SliceIntoConcaveVoxels()
    167.     {
    168.         var points = new List<Vector3>(slicesPerAxis * slicesPerAxis * slicesPerAxis);
    169.      
    170.         var convexValue = buoyancyMeshCollider.convex;
    171.         buoyancyMeshCollider.convex = false;
    172.  
    173.         // Concave slicing
    174.         var bounds = buoyancyCollider.bounds;
    175.         for (int ix = 0; ix < slicesPerAxis; ix++)
    176.         {
    177.             for (int iy = 0; iy < slicesPerAxis; iy++)
    178.             {
    179.                 for (int iz = 0; iz < slicesPerAxis; iz++)
    180.                 {
    181.                     float x = bounds.min.x + bounds.size.x / slicesPerAxis * (0.5f + ix);
    182.                     float y = bounds.min.y + bounds.size.y / slicesPerAxis * (0.5f + iy);
    183.                     float z = bounds.min.z + bounds.size.z / slicesPerAxis * (0.5f + iz);
    184.  
    185.                     var point = transform.InverseTransformPoint(new Vector3(x, y, z));
    186.  
    187.                     if (PointIsInsideMeshCollider(buoyancyMeshCollider, point))
    188.                     {
    189.                         points.Add(point);
    190.                     }
    191.                 }
    192.             }
    193.         }
    194.          
    195.         if (points.Count == 0)
    196.         {
    197.             points.Add(bounds.center);
    198.         }
    199.  
    200.         buoyancyMeshCollider.convex = convexValue;
    201.  
    202.         return points;
    203.     }
    204.  
    205.     /// <summary>
    206.     /// Returns whether the point is inside the mesh collider.
    207.     /// </summary>
    208.     /// <param name="collider">Mesh collider.</param>
    209.     /// <param name="point">Point.</param>
    210.     /// <returns>True - the point is inside the mesh collider. False - the point is outside of the mesh collider. </returns>
    211.     private static bool PointIsInsideMeshCollider(Collider collider, Vector3 point)
    212.     {
    213.         Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };
    214.  
    215.         foreach (var ray in directions)
    216.         {
    217.             if (collider.Raycast(new Ray(point - ray * 1000, ray), out RaycastHit _, 1000f) == false)
    218.             {
    219.                 return false;
    220.             }
    221.         }
    222.  
    223.         return true;
    224.     }
    225.  
    226.     /// <summary>
    227.     /// Returns two closest points in the list.
    228.     /// </summary>
    229.     /// <param name="list">List of points.</param>
    230.     /// <param name="firstIndex">Index of the first point in the list. It's always less than the second index.</param>
    231.     /// <param name="secondIndex">Index of the second point in the list. It's always greater than the first index.</param>
    232.     private static void FindClosestPoints(IList<Vector3> list, out int firstIndex, out int secondIndex)
    233.     {
    234.         float minDistance = float.MaxValue, maxDistance = float.MinValue;
    235.         firstIndex = 0;
    236.         secondIndex = 1;
    237.  
    238.         for (int i = 0; i < list.Count - 1; i++)
    239.         {
    240.             for (int j = i + 1; j < list.Count; j++)
    241.             {
    242.                 float distance = Vector3.Distance(list[i], list[j]);
    243.                 if (distance < minDistance)
    244.                 {
    245.                     minDistance = distance;
    246.                     firstIndex = i;
    247.                     secondIndex = j;
    248.                 }
    249.                 if (distance > maxDistance)
    250.                 {
    251.                     maxDistance = distance;
    252.                 }
    253.             }
    254.         }
    255.     }
    256.  
    257.     /// <summary>
    258.     /// Welds closest points.
    259.     /// </summary>
    260.     /// <param name="list">List of points.</param>
    261.     /// <param name="targetCount">Target number of points in the list.</param>
    262.     private static void WeldPoints(IList<Vector3> list, int targetCount)
    263.     {
    264.         if (list.Count <= 2 || targetCount < 2)
    265.         {
    266.             return;
    267.         }
    268.  
    269.         while (list.Count > targetCount)
    270.         {
    271.             FindClosestPoints(list, out int first, out int second);
    272.             var mixed = (list[first] + list[second]) * 0.5f;
    273.             list.RemoveAt(second); // the second index is always greater that the first => removing the second item first
    274.             list.RemoveAt(first);
    275.             list.Add(mixed);
    276.         }
    277.     }
    278.  
    279.     /// <summary>
    280.     /// Returns the water level at given location.
    281.     /// </summary>
    282.     /// <param name="x">x-coordinate</param>
    283.     /// <param name="z">z-coordinate</param>
    284.     /// <returns>Water level</returns>
    285.     private float GetWaterLevel(float x, float z)
    286.     {
    287.         // Adjust for variable water level
    288.         return waterTransform.position.y;
    289.     }
    290.  
    291.     /// <summary>
    292.     /// Calculates physics.
    293.     /// </summary>
    294.     private void FixedUpdate()
    295.     {
    296.         #if UNITY_EDITOR
    297.         _forces.Clear(); // For drawing force gizmos
    298.         #endif
    299.  
    300.         foreach (var point in _voxels)
    301.         {
    302.             var worldPoint = transform.TransformPoint(point);
    303.             float waterLevel = GetWaterLevel(worldPoint.x, worldPoint.z);
    304.  
    305.             if (worldPoint.y - _voxelHalfHeight >= waterLevel)
    306.             {
    307.                 continue;
    308.             }
    309.  
    310.             float underWaterDepth = (waterLevel - worldPoint.y) / (_voxelHalfHeight * 2) + 0.5f;
    311.             underWaterDepth = Mathf.Clamp01(underWaterDepth);
    312.  
    313.             var velocity = buoyancyRigidbody.GetPointVelocity(worldPoint);
    314.             var localDampingForce = -velocity * Dampener * buoyancyRigidbody.mass;
    315.             var force = localDampingForce + Mathf.Sqrt(underWaterDepth) * _localArchimedesForce + _forceOffset;
    316.             buoyancyRigidbody.AddForceAtPosition(force, worldPoint);
    317.  
    318.             #if UNITY_EDITOR
    319.             _forces.Add(new[] { worldPoint, force }); // For drawing force gizmos
    320.             #endif
    321.         }
    322.     }
    323.  
    324.     #if UNITY_EDITOR
    325.     /// <summary>
    326.     /// Draws gizmos.
    327.     /// </summary>
    328.     private void OnDrawGizmos()
    329.     {
    330.         if (_voxels == null || _forces == null)
    331.         {
    332.             return;
    333.         }
    334.  
    335.         const float gizmoSize = 0.05f;
    336.         Gizmos.color = Color.yellow;
    337.  
    338.         foreach (var p in _voxels)
    339.         {
    340.             Gizmos.DrawCube(transform.TransformPoint(p), new Vector3(gizmoSize, gizmoSize, gizmoSize));
    341.         }
    342.  
    343.         Gizmos.color = Color.cyan;
    344.         foreach (var force in _forces)
    345.         {
    346.             Gizmos.DrawCube(force[0], new Vector3(gizmoSize, gizmoSize, gizmoSize));
    347.             Gizmos.DrawLine(force[0], force[0] + force[1] / buoyancyRigidbody.mass);
    348.         }
    349.     }
    350.     #endif
    351. }
    352.