Thanks for the replies
None of the parts have rigidbodies, I have different prefabs for each part, and the built prefabs don’t have any rigidbodies attached.
tjmaul:
Check if the rotational inertia tensor is recalculated when you add colliders. As your compound object grows, so should the inertia tensor. If it stays the same, buoyancy forces added far away from the center of mass lead to large angular accelerations. I assume your also adding mass to the rigidbody as your raft grows?
Thanks I never knew about inertia tensor, I have now added ResetInertiaTensor(); each time.
This seems to help a little but it seems to still do it now and then, I think its doing it when the floating script increases the drag and angular drag. This works to give the individual pieces a more realistic buoyancy. I’m not sure why this would make it freak out but i’ll not increase those on the raft rigidbody and see.
I will add my buoyancy script here, in case there is something funky i’m doing in it. If you have the time to read through it and see what you think it would be appreciated.
From my building script:
private void BuildIt()//Place the piece
{
GameObject builtObject = Instantiate(hitItemBuilding.builtPiecePrefab, previewGameObjectChild.position, previewGameObjectChild.rotation, raft.transform);
//previewScript.Place(hitGameObject);
Destroy(previewGameObject);
previewGameObject = null;
//previewScript = null;
previewGameObjectChild = null;
//isBuilding = false;
canSnap = false;
andRotating = false;
selected = false;
selectable = false;
previewing = false;
shifting = false;
if (carryingAmount == 1)
{
print("remove " + carryingAmount);
Destroy(carryingGO[0]);
carryingGO.RemoveAt(0);
carryingAmount--;
amCarrying = false;
carryingItem = null;
carryingGO.Clear();
amCarryingName = null;
canBuild = false;
}
if (carryingAmount > 1)
{
print("remove " + carryingAmount);
//int tmporary = carryingAmount - 1;
Destroy(carryingGO[carryingAmount - 1]);
carryingGO.RemoveAt(carryingAmount - 1);
carryingAmount--;
}
//isRotZ = false;
//isRotY = false;
//translateOffset = 0f;
//AddMeshCollider(raft);
raft.GetComponent<Rigidbody>().ResetInertiaTensor();
raft.GetComponent<FloatStuff>().Recalculate();
//raft.GetComponent<FloatStuff>().enabled = true;
}
My buoyancy script:
// Buoyancy.cs
// by Alex Zhdankin, modified for my purpouse.
// Version 2.1.1?
//
// https://forum.unity3d.com/threads/buoyancy-script.72974/
//
// Terms of use: do whatever you like
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Collider))]
public class FloatStuff : MonoBehaviour
{
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 Bounds bounds;
private List<Vector3[]> forces; // For drawing force gizmos
private Rigidbody rigidBody;
private Collider thecollider;
public List<Collider> allColliders = new List<Collider>();
private List<Bounds> allBounds = new List<Bounds>();
private List<float> allVoxelHalfHeight = new List<float>();
[SerializeField]
public List<List<Vector3>> allVoxels = new List<List<Vector3>>();
private Ocean ocean;
public float waterLevel;
public bool children;
private float volume;
private float allMass;
public int allVoxelsCount = 0;
public List<Vector3> allCofM;
private int subVoxelsCount;
//private int weights;
/// <summary>
/// Provides initialization.
/// </summary>
private void Start()
{
forces = new List<Vector3[]>(); // For drawing force gizmos
rigidBody = GetComponent<Rigidbody>();
thecollider = GetComponent<Collider>();
allColliders.Clear();
foreach (Transform child in transform)
{
if (child.CompareTag("BuildingMaterial"))
{
allColliders.Add(child.GetComponent<Collider>());
allMass += child.GetComponent<MyWeight>().myWeight;
}
}
// Store original rotation and position
var originalRotation = transform.rotation;
var originalPosition = transform.position;
transform.rotation = Quaternion.identity;
transform.position = Vector3.zero;
ocean = Ocean.Singleton;
if (!children)
{
isMeshCollider = GetComponent<MeshCollider>() != null;
bounds = thecollider.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;
rigidBody.centerOfMass = new Vector3(0, -bounds.extents.y * 0f, 0) + transform.InverseTransformPoint(bounds.center);
voxels = SliceIntoVoxels(isMeshCollider && isConcave);
transform.rotation = originalRotation;
transform.position = originalPosition;
WeldPoints(voxels, voxelsLimit);
volume = rigidBody.mass / density;
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));
}
//allVoxelsCount = 0;
else if (children)
{
for (int i = 0; i < allColliders.Count; i++)
{
allBounds.Insert(i, allColliders[i].bounds);
if (allBounds[i].size.x < allBounds[i].size.y)
{
allVoxelHalfHeight.Insert(i, allBounds[i].size.x);
}
else
{
allVoxelHalfHeight.Insert(i, allBounds[i].size.y);
}
if (allBounds[i].size.z < allVoxelHalfHeight[i])
{
allVoxelHalfHeight.RemoveAt(i);
allVoxelHalfHeight.Insert(i, allBounds[i].size.z);
}
float tmpHalfHeight = allVoxelHalfHeight[i];
allVoxelHalfHeight.RemoveAt(i);
allVoxelHalfHeight.Insert(i, tmpHalfHeight /= 2 * slicesPerAxis);
//print(i);
Vector3 tmp = new Vector3(allColliders[i].transform.localPosition.x, allColliders[i].transform.localPosition.y, allColliders[i].transform.localPosition.z);
print(tmp);
allCofM.Add(tmp);
//allMass = weights;
//rigidBody.mass = 80;
allVoxels.Insert(i, SliceIntoVoxels(isMeshCollider && isConcave, allColliders[i], i));
subVoxelsCount = allVoxels[i].Count;
allVoxelsCount += subVoxelsCount;
}
//allVoxelsCount += allVoxels.Count;
print(allVoxelsCount);
var average = allCofM.Aggregate(new Vector3(0, 0, 0), (s, v) => s + v) / allCofM.Count;
print(average);
rigidBody.centerOfMass = average; //new Vector3(0, -average.y * 0f, 0); /// + transform.InverseTransformPoint(bounds.center);
//transform.rotation = originalRotation;
//transform.position = originalPosition;
for (int j = 0; j < allVoxels.Count; j++)
{
foreach (Vector3 tmpVoxels in allVoxels[j])
{
voxels = SliceIntoVoxels(isMeshCollider && isConcave);
transform.rotation = originalRotation;
transform.position = originalPosition;
WeldPoints(voxels, voxelsLimit);
WeldPoints(allVoxels[j], voxelsLimit);
}
}
rigidBody.mass = allMass;
volume = rigidBody.mass / density;
float archimedesForceMagnitude = WATER_DENSITY * Mathf.Abs(Physics.gravity.y) * volume;
localArchimedesForce = new Vector3(0, archimedesForceMagnitude, 0) / allVoxelsCount;
Debug.Log(string.Format("[Buoyancy.cs] Name=\"{0}\" volume={1:0.0}, mass={2:0.0}, density={3:0.0}", name, volume, allMass, density));
}
}
public void Recalculate()
{
allColliders.Clear();
allMass = 0;
foreach (Transform child in transform)
{
if (child.CompareTag("BuildingMaterial"))
{
allColliders.Add(child.GetComponent<Collider>());
allMass += child.GetComponent<MyWeight>().myWeight;
}
}
// Store original rotation and position
var originalRotation = transform.rotation;
var originalPosition = transform.position;
transform.rotation = Quaternion.identity;
transform.position = Vector3.zero;
//ocean = Ocean.Singleton;
if (!children)
{
isMeshCollider = GetComponent<MeshCollider>() != null;
bounds = thecollider.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;
rigidBody.centerOfMass = new Vector3(0, -bounds.extents.y * 0f, 0) + transform.InverseTransformPoint(bounds.center);
voxels = SliceIntoVoxels(isMeshCollider && isConcave);
transform.rotation = originalRotation;
transform.position = originalPosition;
WeldPoints(voxels, voxelsLimit);
volume = rigidBody.mass / density;
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));
}
//allVoxelsCount = 0;
else if (children)
{
for (int i = 0; i < allColliders.Count; i++)
{
allBounds.Insert(i, allColliders[i].bounds);
if (allBounds[i].size.x < allBounds[i].size.y)
{
allVoxelHalfHeight.Insert(i, allBounds[i].size.x);
}
else
{
allVoxelHalfHeight.Insert(i, allBounds[i].size.y);
}
if (allBounds[i].size.z < allVoxelHalfHeight[i])
{
allVoxelHalfHeight.RemoveAt(i);
allVoxelHalfHeight.Insert(i, allBounds[i].size.z);
}
float tmpHalfHeight = allVoxelHalfHeight[i];
allVoxelHalfHeight.RemoveAt(i);
allVoxelHalfHeight.Insert(i, tmpHalfHeight /= 2 * slicesPerAxis);
//print(i);
Vector3 tmp = new Vector3(allColliders[i].transform.localPosition.x, allColliders[i].transform.localPosition.y, allColliders[i].transform.localPosition.z);
print(tmp);
allCofM.Add(tmp);
//allMass = weights;
//rigidBody.mass = 80;
allVoxels.Insert(i, SliceIntoVoxels(isMeshCollider && isConcave, allColliders[i], i));
subVoxelsCount = allVoxels[i].Count;
allVoxelsCount += subVoxelsCount;
}
//allVoxelsCount += allVoxels.Count;
print(allVoxelsCount);
var average = allCofM.Aggregate(new Vector3(0, 0, 0), (s, v) => s + v) / allCofM.Count;
print(average);
rigidBody.centerOfMass = average; //new Vector3(0, -average.y * 0f, 0); /// + transform.InverseTransformPoint(bounds.center);
//transform.rotation = originalRotation;
//transform.position = originalPosition;
for (int j = 0; j < allVoxels.Count; j++)
{
foreach (Vector3 tmpVoxels in allVoxels[j])
{
voxels = SliceIntoVoxels(isMeshCollider && isConcave);
transform.rotation = originalRotation;
transform.position = originalPosition;
WeldPoints(voxels, voxelsLimit);
WeldPoints(allVoxels[j], voxelsLimit);
}
}
rigidBody.mass = allMass;
volume = rigidBody.mass / density;
float archimedesForceMagnitude = WATER_DENSITY * Mathf.Abs(Physics.gravity.y) * volume;
localArchimedesForce = new Vector3(0, archimedesForceMagnitude, 0) / allVoxelsCount;
Debug.Log(string.Format("[Buoyancy.cs] Name=\"{0}\" volume={1:0.0}, mass={2:0.0}, density={3:0.0}", name, volume, allMass, 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 = thecollider.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>
/// 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, Collider coll, int index)
{
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 = coll.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 = coll.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.GetHeightChoppyAtLocation2(x, z);
//return 5.0f;
}
/// <summary>
/// Calculates physics.
/// </summary>
private void FixedUpdate()
{
forces.Clear(); // For drawing force gizmos
if (!children)
{
foreach (var point in voxels)
{
var wp = transform.TransformPoint(point);
waterLevel = GetWaterLevel(wp.x, wp.z);
if ((transform.position.y - 0.01f) <= waterLevel)
{
rigidBody.drag = 1f;
rigidBody.angularDrag = 1f;
}
else
{
rigidBody.drag = 0f;
rigidBody.angularDrag = 0.05f;
}
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
}
}
}
else if (children)
{
foreach (var pointlist in allVoxels)
{
foreach (var point in pointlist)
{
var wp = transform.TransformPoint(point);
waterLevel = GetWaterLevel(wp.x, wp.z);
if ((transform.position.y - 0.01f) <= waterLevel)
{
rigidBody.drag = 1f;
rigidBody.angularDrag = 1f;
}
else
{
rigidBody.drag = 0f;
rigidBody.angularDrag = 0.05f;
}
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 (!children)
{
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);
}
}
else if (children)
{
foreach (var voxellist in allVoxels)
{
if (voxellist == null || forces == null)
{
return;
}
const float gizmoSize = 0.05f;
Gizmos.color = Color.yellow;
foreach (var p in voxellist)
{
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);
}
}
//Gizmos.color = Color.red;
//Gizmos.DrawSphere( rigidBody.centerOfMass, 0.2f);
}
}
}