Дано: вода, бревно
Задача: заставить бревно реалистично плавать
---------------------------------------------------------------------------------------------------------------------------------------
На бревно в воде действуют две силы: сила тяготения и сила Архимеда, выталкавающая бревно наверх. Тяготение и так уже есть силами Unity, так что остаётся только смоделировать выталкивание.
Известно, что сила Архимеда действует только на погруженную в воду часть тела и численно она равна:
Сила Архимеда = (плотность воды) * (ускорение свободного падения) * (объем тела, погруженный в воду)
Плотность воды ― константа, 1000 кг/(м^3).
Ускорение свободного падения ― тоже константа. Для универсальности её можно прочитать из Physics.gravity.
Объем тела, погруженный в воду ― с этим сложнее. При большом желании его можно было бы расчитать, но во-первых, для произвольного тела это сложно, а во-вторых, заниматься этим в рилтайме в функции обработки физики нехорошо, т.к. долго. Будем прикидывать приблизительно:
Путь у нас будет не бревно, а параллепипед. Разобьём его по всём трём осям на сектора, из каждого сектора будем брать центральную точку и смотреть, находится она над водой или под водой:
Синтаксис:
Используется csharp
const int SECTORS = 3;
List<Vector3> points;
var collider = GetComponent<Collider>();
var bounds = collider.bounds;
for (int ix = 0; ix < SECTORS; ix++)
{
for (int iy = 0; iy < SECTORS; iy++)
{
for (int iz = 0; iz < SECTORS; iz++)
{
float x = bounds.min.x + bounds.size.x / SECTORS * (0.5f + ix);
float y = bounds.min.y + bounds.size.y / SECTORS * (0.5f + iy);
float z = bounds.min.z + bounds.size.z / SECTORS * (0.5f + iz);
// Переводим точку из мировой в локальную систему координат
var p = transform.InverseTransformPoint(new Vector3(x, y, z));
points.Add(p);
}
}
}
List<Vector3> points;
var collider = GetComponent<Collider>();
var bounds = collider.bounds;
for (int ix = 0; ix < SECTORS; ix++)
{
for (int iy = 0; iy < SECTORS; iy++)
{
for (int iz = 0; iz < SECTORS; iz++)
{
float x = bounds.min.x + bounds.size.x / SECTORS * (0.5f + ix);
float y = bounds.min.y + bounds.size.y / SECTORS * (0.5f + iy);
float z = bounds.min.z + bounds.size.z / SECTORS * (0.5f + iz);
// Переводим точку из мировой в локальную систему координат
var p = transform.InverseTransformPoint(new Vector3(x, y, z));
points.Add(p);
}
}
}
Сила Архимеда для каждого сектора = 1000 кг/(м^3) * 9,81 м/(c^2) * (объем сектора)
Суммарная сила Архимеда = 1000 * 9,81 * (объем сектора) * (количество секторов под водой)
Если всё тело целиком находится под водой, то сила Архимеда = 1000 * 9,81 * (объем тела)
Где взять объём тела? Высчитывать по сетке объекта не будем (всё равно не каждая сетка это позволит), возьмём грубо половину объёма от bounding box:
Синтаксис:
Используется csharp
var collider = GetComponent<Collider>();
var bounds = collider.bounds;
float volume = bounds.size.x * bounds.size.y * bounds.size.z / 2;
var bounds = collider.bounds;
float volume = bounds.size.x * bounds.size.y * bounds.size.z / 2;
Все сектора у нас одинаковые и нет смысла считать силу Архимеда для каждого из них, потому что она будет равна (силе Архимеда для всего тела) / (количество секторов). Поэтому расчитаем силу только один раз и заранее:
Синтаксис:
Используется csharp
const float WATER_DENSITY = 1000;
float archimedesForceMagnitude = WATER_DENSITY * Mathf.Abs(Physics.gravity.y) * volume;
Vector3 archimedesForce = new Vector3(0, archimedesForceMagnitude, 0);
float archimedesForceMagnitude = WATER_DENSITY * Mathf.Abs(Physics.gravity.y) * volume;
Vector3 archimedesForce = new Vector3(0, archimedesForceMagnitude, 0);
Теперь все исходные данные у нас есть, можно приступать к прикладыванию силы Архимеда к объекту:
Синтаксис:
Используется csharp
void FixedUpdate()
{
foreach (var point in points)
{
// Переводим точку из локальной в мировую систему координат
var wp = transform.TransformPoint(point);
// Функция GetWaterLevel(x,z) возвращает или просто ноль, или высоту воды в заданной позиции, если на воде есть волны.
var waterLevel = GetWaterLevel(wp.x, wp.z);
if (wp.y < waterLevel)
{
var force = archimedesForce / points.Count;
rigidbody.AddForceAtPosition(force, wp);
}
}
}
{
foreach (var point in points)
{
// Переводим точку из локальной в мировую систему координат
var wp = transform.TransformPoint(point);
// Функция GetWaterLevel(x,z) возвращает или просто ноль, или высоту воды в заданной позиции, если на воде есть волны.
var waterLevel = GetWaterLevel(wp.x, wp.z);
if (wp.y < waterLevel)
{
var force = archimedesForce / points.Count;
rigidbody.AddForceAtPosition(force, wp);
}
}
}
---------------------------------------------------------------------------------------------------------------------------------------
Практика показала, что оно работает, но есть две проблемки:
1) Если тело падает в воду, то оно из неё выпрыгивает обратно, потом опять падает и так до бесконечности.
2) Если тело дрейфует по воде, оно заметно дёргается вверх-вниз, потому что сила Архимеда, приложенная к объекту, никогда не совпадает точно с силой тяжести, потому что изменяется дискретно. Один сектор ушёл под воду ― сила резко увеличилась, поднялся над водой ― резко уменьшилась.
Первую проблему можно решить демпфером ― будем гасить скорость тем секторам, которые оказались под водой. Будет даже реалистично. Приложим силу, пропорциональную скорости движения, но обратную по направлению:
Синтаксис:
Используется csharp
const float DAMPFER = 100;
var velocity = rigidbody.GetPointVelocity(wp);
var localDampingForce = -velocity * DAMPFER;
var velocity = rigidbody.GetPointVelocity(wp);
var localDampingForce = -velocity * DAMPFER;
А чтобы убрать дискретность у силы выталкивания, добавим к ней коэффициент, который будет плавно меняться, когда центр сектора будет около поверхности воды:
delta ― это половина высоты сектора.
Синтаксис:
Используется csharp
float delta;
var collider = GetComponent<Collider>();
var bounds = collider.bounds;
if (bounds.size.x < bounds.size.y)
{
delta = bounds.size.x;
}
else
{
delta = bounds.size.y;
}
if (bounds.size.z < delta)
{
delta = bounds.size.z;
}
delta /= 2 * SECTORS;
var collider = GetComponent<Collider>();
var bounds = collider.bounds;
if (bounds.size.x < bounds.size.y)
{
delta = bounds.size.x;
}
else
{
delta = bounds.size.y;
}
if (bounds.size.z < delta)
{
delta = bounds.size.z;
}
delta /= 2 * SECTORS;
Когда весь сектор под водой, коэффициент = 1,0 и на сектор будет действовать полная сила Архимеда. Когда только полсектора под водой, то коэффициент = 0,5. Если весь сектор над водой, коэффициент = 0 и сила выталкивания будет равна нулю:
Синтаксис:
Используется csharp
void FixedUpdate()
{
foreach (var point in points)
{
var wp = transform.TransformPoint(point);
var waterLevel = GetWaterLevel(wp.x, wp.z);
if (wp.y - delta < waterLevel)
{
var velocity = rigidbody.GetPointVelocity(wp);
float k = (waterLevel - wp.y) / (2 * delta) + 0.5f;
if (k > 1)
{
k = 1f;
}
else if (k < 0)
{
k = 0f;
}
var localDampingForce = -velocity * DAMPFER;
var localArchimedesForce = Mathf.Sqrt(k) * archimedesForce / points.Count;
var force = localDampingForce + localArchimedesForce;
rigidbody.AddForceAtPosition(force, wp);
}
}
}
{
foreach (var point in points)
{
var wp = transform.TransformPoint(point);
var waterLevel = GetWaterLevel(wp.x, wp.z);
if (wp.y - delta < waterLevel)
{
var velocity = rigidbody.GetPointVelocity(wp);
float k = (waterLevel - wp.y) / (2 * delta) + 0.5f;
if (k > 1)
{
k = 1f;
}
else if (k < 0)
{
k = 0f;
}
var localDampingForce = -velocity * DAMPFER;
var localArchimedesForce = Mathf.Sqrt(k) * archimedesForce / points.Count;
var force = localDampingForce + localArchimedesForce;
rigidbody.AddForceAtPosition(force, wp);
}
}
}
Квадратный корень из коэффициента Mathf.Sqrt(k) можно было и не брать, но с ним показалось естественнее.
---------------------------------------------------------------------------------------------------------------------------------------
F ― полный экран
левая кнопка мыши ― бросить бревно
R ― вкл/выкл отражения
С ― «срубить» дерево
левый Shift ― ускорение времени
HTML код для вашего блога :
---------------------------------------------------------------------------------------------------------------------------------------
Версия 2.1
― Для удобства плотность объекта задаётся вручную; измеряется в кг/(м^3). У воды 1000, у древесины 500―700.
― Умеет работать с вогнутыми объектами.
― В целях отладки умеет рисовать gizmos с точками приложения силы выталкивания.
Синтаксис:
Используется csharp
// Buoyancy.cs
// by Alex Zhdankin
// Version 2.1
//
// http://forum.unity3d.com/threads/72974-Buoyancy-script
//
// Terms of use: do whatever you like
using System.Collections.Generic;
using UnityEngine;
public class Buoyancy : MonoBehaviour
{
// public Ocean ocean;
public float density = 500;
public int slicesPerAxis = 2;
public bool isConcave = false;
public int voxelsLimit = 16;
private const float DAMPFER = 0.1f;
private const float WATER_DENSITY = 1000;
private float voxelHalfHeight;
private Vector3 localArchimedesForce;
private List<Vector3> voxels;
private bool isMeshCollider;
private List<Vector3[]> forces; // For drawing force gizmos
/// <summary>
/// Provides initialization.
/// </summary>
private void Start()
{
forces = new List<Vector3[]>(); // For drawing force gizmos
// Store original rotation and position
var originalRotation = transform.rotation;
var originalPosition = transform.position;
transform.rotation = Quaternion.identity;
transform.position = Vector3.zero;
// The object must have a collider
if (collider == null)
{
gameObject.AddComponent<MeshCollider>();
Debug.LogWarning(string.Format("[Buoyancy.cs] Object \"{0}\" had no collider. MeshCollider has been added.", name));
}
isMeshCollider = GetComponent<MeshCollider>() != null;
var bounds = collider.bounds;
if (bounds.size.x < bounds.size.y)
{
voxelHalfHeight = bounds.size.x;
}
else
{
voxelHalfHeight = bounds.size.y;
}
if (bounds.size.z < voxelHalfHeight)
{
voxelHalfHeight = bounds.size.z;
}
voxelHalfHeight /= 2 * slicesPerAxis;
// The object must have a RidigBody
if (rigidbody == null)
{
gameObject.AddComponent<Rigidbody>();
Debug.LogWarning(string.Format("[Buoyancy.cs] Object \"{0}\" had no Rigidbody. Rigidbody has been added.", name));
}
rigidbody.centerOfMass = new Vector3(0, -bounds.extents.y * 0f, 0) + transform.InverseTransformPoint(bounds.center);
voxels = SliceIntoVoxels(isMeshCollider && isConcave);
// Restore original rotation and position
transform.rotation = originalRotation;
transform.position = originalPosition;
float volume = rigidbody.mass / density;
WeldPoints(voxels, voxelsLimit);
float archimedesForceMagnitude = WATER_DENSITY * Mathf.Abs(Physics.gravity.y) * volume;
localArchimedesForce = new Vector3(0, archimedesForceMagnitude, 0) / voxels.Count;
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));
}
/// <summary>
/// Slices the object into number of voxels represented by their center points.
/// <param name="concave">Whether the object have a concave shape.</param>
/// <returns>List of voxels represented by their center points.</returns>
/// </summary>
private List<Vector3> SliceIntoVoxels(bool concave)
{
var points = new List<Vector3>(slicesPerAxis * slicesPerAxis * slicesPerAxis);
if (concave)
{
var meshCol = GetComponent<MeshCollider>();
var convexValue = meshCol.convex;
meshCol.convex = false;
// Concave slicing
var bounds = collider.bounds;
for (int ix = 0; ix < slicesPerAxis; ix++)
{
for (int iy = 0; iy < slicesPerAxis; iy++)
{
for (int iz = 0; iz < slicesPerAxis; iz++)
{
float x = bounds.min.x + bounds.size.x / slicesPerAxis * (0.5f + ix);
float y = bounds.min.y + bounds.size.y / slicesPerAxis * (0.5f + iy);
float z = bounds.min.z + bounds.size.z / slicesPerAxis * (0.5f + iz);
var p = transform.InverseTransformPoint(new Vector3(x, y, z));
if (PointIsInsideMeshCollider(meshCol, p))
{
points.Add(p);
}
}
}
}
if (points.Count == 0)
{
points.Add(bounds.center);
}
meshCol.convex = convexValue;
}
else
{
// Convex slicing
var bounds = GetComponent<Collider>().bounds;
for (int ix = 0; ix < slicesPerAxis; ix++)
{
for (int iy = 0; iy < slicesPerAxis; iy++)
{
for (int iz = 0; iz < slicesPerAxis; iz++)
{
float x = bounds.min.x + bounds.size.x / slicesPerAxis * (0.5f + ix);
float y = bounds.min.y + bounds.size.y / slicesPerAxis * (0.5f + iy);
float z = bounds.min.z + bounds.size.z / slicesPerAxis * (0.5f + iz);
var p = transform.InverseTransformPoint(new Vector3(x, y, z));
points.Add(p);
}
}
}
}
return points;
}
/// <summary>
/// Returns whether the point is inside the mesh collider.
/// </summary>
/// <param name="c">Mesh collider.</param>
/// <param name="p">Point.</param>
/// <returns>True - the point is inside the mesh collider. False - the point is outside of the mesh collider. </returns>
private static bool PointIsInsideMeshCollider(Collider c, Vector3 p)
{
Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };
foreach (var ray in directions)
{
RaycastHit hit;
if (c.Raycast(new Ray(p - ray * 1000, ray), out hit, 1000f) == false)
{
return false;
}
}
return true;
}
/// <summary>
/// Returns two closest points in the list.
/// </summary>
/// <param name="list">List of points.</param>
/// <param name="firstIndex">Index of the first point in the list. It's always less than the second index.</param>
/// <param name="secondIndex">Index of the second point in the list. It's always greater than the first index.</param>
private static void FindClosestPoints(IList<Vector3> list, out int firstIndex, out int secondIndex)
{
float minDistance = float.MaxValue, maxDistance = float.MinValue;
firstIndex = 0;
secondIndex = 1;
for (int i = 0; i < list.Count - 1; i++)
{
for (int j = i + 1; j < list.Count; j++)
{
float distance = Vector3.Distance(list[i], list[j]);
if (distance < minDistance)
{
minDistance = distance;
firstIndex = i;
secondIndex = j;
}
if (distance > maxDistance)
{
maxDistance = distance;
}
}
}
}
/// <summary>
/// Welds closest points.
/// </summary>
/// <param name="list">List of points.</param>
/// <param name="targetCount">Target number of points in the list.</param>
private static void WeldPoints(IList<Vector3> list, int targetCount)
{
if (list.Count <= 2 || targetCount < 2)
{
return;
}
while (list.Count > targetCount)
{
int first, second;
FindClosestPoints(list, out first, out second);
var mixed = (list[first] + list[second]) * 0.5f;
list.RemoveAt(second); // the second index is always greater that the first => removing the second item first
list.RemoveAt(first);
list.Add(mixed);
}
}
/// <summary>
/// Returns the water level at given location.
/// </summary>
/// <param name="x">x-coordinate</param>
/// <param name="z">z-coordinate</param>
/// <returns>Water level</returns>
private float GetWaterLevel(float x, float z)
{
// return ocean == null ? 0.0f : ocean.GetWaterHeightAtLocation(x, z);
return 0.0f;
}
/// <summary>
/// Calculates physics.
/// </summary>
private void FixedUpdate()
{
forces.Clear(); // For drawing force gizmos
foreach (var point in voxels)
{
var wp = transform.TransformPoint(point);
float waterLevel = GetWaterLevel(wp.x, wp.z);
if (wp.y - voxelHalfHeight < waterLevel)
{
float k = (waterLevel - wp.y) / (2 * voxelHalfHeight) + 0.5f;
if (k > 1)
{
k = 1f;
}
else if (k < 0)
{
k = 0f;
}
var velocity = rigidbody.GetPointVelocity(wp);
var localDampingForce = -velocity * DAMPFER * rigidbody.mass;
var force = localDampingForce + Mathf.Sqrt(k) * localArchimedesForce;
rigidbody.AddForceAtPosition(force, wp);
forces.Add(new[] { wp, force }); // For drawing force gizmos
}
}
}
/// <summary>
/// Draws gizmos.
/// </summary>
private void OnDrawGizmos()
{
if (voxels == null || forces == null)
{
return;
}
const float gizmoSize = 0.05f;
Gizmos.color = Color.yellow;
foreach (var p in voxels)
{
Gizmos.DrawCube(transform.TransformPoint(p), new Vector3(gizmoSize, gizmoSize, gizmoSize));
}
Gizmos.color = Color.cyan;
foreach (var force in forces)
{
Gizmos.DrawCube(force[0], new Vector3(gizmoSize, gizmoSize, gizmoSize));
Gizmos.DrawLine(force[0], force[0] + force[1] / rigidbody.mass);
}
}
}
// by Alex Zhdankin
// Version 2.1
//
// http://forum.unity3d.com/threads/72974-Buoyancy-script
//
// Terms of use: do whatever you like
using System.Collections.Generic;
using UnityEngine;
public class Buoyancy : MonoBehaviour
{
// public Ocean ocean;
public float density = 500;
public int slicesPerAxis = 2;
public bool isConcave = false;
public int voxelsLimit = 16;
private const float DAMPFER = 0.1f;
private const float WATER_DENSITY = 1000;
private float voxelHalfHeight;
private Vector3 localArchimedesForce;
private List<Vector3> voxels;
private bool isMeshCollider;
private List<Vector3[]> forces; // For drawing force gizmos
/// <summary>
/// Provides initialization.
/// </summary>
private void Start()
{
forces = new List<Vector3[]>(); // For drawing force gizmos
// Store original rotation and position
var originalRotation = transform.rotation;
var originalPosition = transform.position;
transform.rotation = Quaternion.identity;
transform.position = Vector3.zero;
// The object must have a collider
if (collider == null)
{
gameObject.AddComponent<MeshCollider>();
Debug.LogWarning(string.Format("[Buoyancy.cs] Object \"{0}\" had no collider. MeshCollider has been added.", name));
}
isMeshCollider = GetComponent<MeshCollider>() != null;
var bounds = collider.bounds;
if (bounds.size.x < bounds.size.y)
{
voxelHalfHeight = bounds.size.x;
}
else
{
voxelHalfHeight = bounds.size.y;
}
if (bounds.size.z < voxelHalfHeight)
{
voxelHalfHeight = bounds.size.z;
}
voxelHalfHeight /= 2 * slicesPerAxis;
// The object must have a RidigBody
if (rigidbody == null)
{
gameObject.AddComponent<Rigidbody>();
Debug.LogWarning(string.Format("[Buoyancy.cs] Object \"{0}\" had no Rigidbody. Rigidbody has been added.", name));
}
rigidbody.centerOfMass = new Vector3(0, -bounds.extents.y * 0f, 0) + transform.InverseTransformPoint(bounds.center);
voxels = SliceIntoVoxels(isMeshCollider && isConcave);
// Restore original rotation and position
transform.rotation = originalRotation;
transform.position = originalPosition;
float volume = rigidbody.mass / density;
WeldPoints(voxels, voxelsLimit);
float archimedesForceMagnitude = WATER_DENSITY * Mathf.Abs(Physics.gravity.y) * volume;
localArchimedesForce = new Vector3(0, archimedesForceMagnitude, 0) / voxels.Count;
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));
}
/// <summary>
/// Slices the object into number of voxels represented by their center points.
/// <param name="concave">Whether the object have a concave shape.</param>
/// <returns>List of voxels represented by their center points.</returns>
/// </summary>
private List<Vector3> SliceIntoVoxels(bool concave)
{
var points = new List<Vector3>(slicesPerAxis * slicesPerAxis * slicesPerAxis);
if (concave)
{
var meshCol = GetComponent<MeshCollider>();
var convexValue = meshCol.convex;
meshCol.convex = false;
// Concave slicing
var bounds = collider.bounds;
for (int ix = 0; ix < slicesPerAxis; ix++)
{
for (int iy = 0; iy < slicesPerAxis; iy++)
{
for (int iz = 0; iz < slicesPerAxis; iz++)
{
float x = bounds.min.x + bounds.size.x / slicesPerAxis * (0.5f + ix);
float y = bounds.min.y + bounds.size.y / slicesPerAxis * (0.5f + iy);
float z = bounds.min.z + bounds.size.z / slicesPerAxis * (0.5f + iz);
var p = transform.InverseTransformPoint(new Vector3(x, y, z));
if (PointIsInsideMeshCollider(meshCol, p))
{
points.Add(p);
}
}
}
}
if (points.Count == 0)
{
points.Add(bounds.center);
}
meshCol.convex = convexValue;
}
else
{
// Convex slicing
var bounds = GetComponent<Collider>().bounds;
for (int ix = 0; ix < slicesPerAxis; ix++)
{
for (int iy = 0; iy < slicesPerAxis; iy++)
{
for (int iz = 0; iz < slicesPerAxis; iz++)
{
float x = bounds.min.x + bounds.size.x / slicesPerAxis * (0.5f + ix);
float y = bounds.min.y + bounds.size.y / slicesPerAxis * (0.5f + iy);
float z = bounds.min.z + bounds.size.z / slicesPerAxis * (0.5f + iz);
var p = transform.InverseTransformPoint(new Vector3(x, y, z));
points.Add(p);
}
}
}
}
return points;
}
/// <summary>
/// Returns whether the point is inside the mesh collider.
/// </summary>
/// <param name="c">Mesh collider.</param>
/// <param name="p">Point.</param>
/// <returns>True - the point is inside the mesh collider. False - the point is outside of the mesh collider. </returns>
private static bool PointIsInsideMeshCollider(Collider c, Vector3 p)
{
Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };
foreach (var ray in directions)
{
RaycastHit hit;
if (c.Raycast(new Ray(p - ray * 1000, ray), out hit, 1000f) == false)
{
return false;
}
}
return true;
}
/// <summary>
/// Returns two closest points in the list.
/// </summary>
/// <param name="list">List of points.</param>
/// <param name="firstIndex">Index of the first point in the list. It's always less than the second index.</param>
/// <param name="secondIndex">Index of the second point in the list. It's always greater than the first index.</param>
private static void FindClosestPoints(IList<Vector3> list, out int firstIndex, out int secondIndex)
{
float minDistance = float.MaxValue, maxDistance = float.MinValue;
firstIndex = 0;
secondIndex = 1;
for (int i = 0; i < list.Count - 1; i++)
{
for (int j = i + 1; j < list.Count; j++)
{
float distance = Vector3.Distance(list[i], list[j]);
if (distance < minDistance)
{
minDistance = distance;
firstIndex = i;
secondIndex = j;
}
if (distance > maxDistance)
{
maxDistance = distance;
}
}
}
}
/// <summary>
/// Welds closest points.
/// </summary>
/// <param name="list">List of points.</param>
/// <param name="targetCount">Target number of points in the list.</param>
private static void WeldPoints(IList<Vector3> list, int targetCount)
{
if (list.Count <= 2 || targetCount < 2)
{
return;
}
while (list.Count > targetCount)
{
int first, second;
FindClosestPoints(list, out first, out second);
var mixed = (list[first] + list[second]) * 0.5f;
list.RemoveAt(second); // the second index is always greater that the first => removing the second item first
list.RemoveAt(first);
list.Add(mixed);
}
}
/// <summary>
/// Returns the water level at given location.
/// </summary>
/// <param name="x">x-coordinate</param>
/// <param name="z">z-coordinate</param>
/// <returns>Water level</returns>
private float GetWaterLevel(float x, float z)
{
// return ocean == null ? 0.0f : ocean.GetWaterHeightAtLocation(x, z);
return 0.0f;
}
/// <summary>
/// Calculates physics.
/// </summary>
private void FixedUpdate()
{
forces.Clear(); // For drawing force gizmos
foreach (var point in voxels)
{
var wp = transform.TransformPoint(point);
float waterLevel = GetWaterLevel(wp.x, wp.z);
if (wp.y - voxelHalfHeight < waterLevel)
{
float k = (waterLevel - wp.y) / (2 * voxelHalfHeight) + 0.5f;
if (k > 1)
{
k = 1f;
}
else if (k < 0)
{
k = 0f;
}
var velocity = rigidbody.GetPointVelocity(wp);
var localDampingForce = -velocity * DAMPFER * rigidbody.mass;
var force = localDampingForce + Mathf.Sqrt(k) * localArchimedesForce;
rigidbody.AddForceAtPosition(force, wp);
forces.Add(new[] { wp, force }); // For drawing force gizmos
}
}
}
/// <summary>
/// Draws gizmos.
/// </summary>
private void OnDrawGizmos()
{
if (voxels == null || forces == null)
{
return;
}
const float gizmoSize = 0.05f;
Gizmos.color = Color.yellow;
foreach (var p in voxels)
{
Gizmos.DrawCube(transform.TransformPoint(p), new Vector3(gizmoSize, gizmoSize, gizmoSize));
}
Gizmos.color = Color.cyan;
foreach (var force in forces)
{
Gizmos.DrawCube(force[0], new Vector3(gizmoSize, gizmoSize, gizmoSize));
Gizmos.DrawLine(force[0], force[0] + force[1] / rigidbody.mass);
}
}
}