- Home /
Find intersecting volume of two cubes -or- submerged volume of object
Hey Unity users!
I am trying to find out how much of an object is below a certain surface. I basically have a (fake) bath and want to use the displaced volume by the controlled object as a measure for the disturbance of the fluid.
This bath is static and has a fixed height. The controlled object can have any orientation/position. What I've tried:
Simplify the controlled object to a box & find out how far the box is below the surface. I managed to get the positions of all the vertices and the positions where the edges cross the surface of the bath. However, I found out that calculating this volume for an arbitrary orientation is rather difficult. I basically need a formula that calculates the volume of an arbitrary (irregular) polyhedron, but haven't been able to find something that works here.
I could also draw a voxel grid on the controlled object, and found out how much of it is underneath the surface, and use that to estimate the volume. However, I have no idea how to do this and haven't been able to find much guidance either.
I have found this thread: https://answers.unity.com/questions/1178637/finding-volume-of-two-3d-meshes-intersection.html but was not able to use it to solve my problem unfortunately.
Does anyone have an idea on how I could approach this problem? Any help would be greatly appreciated!
looks like you developed an approach.
as i recall there was a demo on https://shadertoy.com about two months ago which had an analytical solution to the problem of what volume of a box is 'submerged'. unfortunately i haven't been able to find it.
Answer by avandenberg · Dec 18, 2019 at 03:12 PM
Managed to find out a way to do this. I created a voxel grid using the same mesh with NVidia Flex (https://assetstore.unity.com/packages/tools/physics/nvidia-flex-for-unity-1-0-beta-120425). Got the position of these particles in the first frame (this is in the update loop, since for whatever reason the particle positions are all 0,0,0 when the startfunction runs):
if (m_particles == null) // For some reason the positions only get loaded in at the first update (not at start)
{
NVIDIA.Flex.FlexSolidActor skim_actor = skimmerObject.GetComponent<NVIDIA.Flex.FlexSolidActor>();
m_particles = AddPos(ToVector3(skim_actor.particles), -skimmerObject.transform.position); // Get the position relative to the skimmer position
skim_actor.enabled = false; // Turn off the flex object so that it doesn't interfere with anything
Nparticles = m_particles.Length;
}
Where ToVector3 is a custom function:
Vector3[] ToVector3(Vector4[] parent)
{
// For some reason the particle positions come in Vector4 in which the w is always 1, remove this component here.
Vector3[] Vec3Arr = new Vector3[parent.Length];
for (int i = 0; i < parent.Length; i++)
{
Vec3Arr[i] = new Vector3(parent[i].x, parent[i].y, parent[i].z);
}
return Vec3Arr;
}
I check if the colliders are intersecting and then scan how many particles are below the surface:
MeshCollider skimColl = skimmerObject.GetComponent<MeshCollider>(); // Use the meshcollider in this case? a bit more intensive i guess but as long as its convex & trigger is turned on it should be fine.
Collider bathColl = zincObject.GetComponent<Collider>(); // Best if this is a BoxCollider but could be any collider.
if (bathColl.bounds.Intersects(skimColl.bounds)) // Check if they are colliding at all to prevent unneccessary calculations
{
colliding = true; // Set a bool to display in Editor
float surfaceY = zincObject.transform.position.y + zincObject.transform.localScale.y/2; // The height of the bath
Vector3[] currPos = TransformArr(m_particles, skimmerObject.transform.localToWorldMatrix); // Get the actual position of each voxel
int detected = scanBathForSkimmer(currPos, surfaceY); // Find out how many voxels are in the bath
fractionSubmerged = detected / Nparticles; // The fraction of the object that is submerged.
The positions are found by using the controlled objects transformation matrix:
int scanBathForSkimmer(Vector3[] pos, float ylevel)
{
// since we already checked if the colliders are intersecting, and the bath can only be acessed from above
// we only need to check the yposition for every voxel and see if it is below the surface or not.
int detected = 0;
for (int i = 0; i < pos.Length; i++)
{
if (pos[i].y > ylevel) detected++;
}
return detected;
}
Vector3[] TransformArr(Vector3[] parent, Matrix4x4 matrix)
{
Vector3[] transformed = new Vector3[parent.Length];
for (int i=0; i< m_particles.Length; i++)
{
transformed[i] = matrix.MultiplyPoint3x4(m_particles[i]);
}
return transformed;
}
And the bath is scanned using:
int scanBathForSkimmer(Vector3[] pos, float ylevel)
{
// since we already checked if the colliders are intersecting, and the bath can only be acessed from above
// we only need to check the yposition for every voxel and see if it is below the surface or not.
int detected = 0;
for (int i = 0; i < pos.Length; i++)
{
if (pos[i].y > ylevel) detected++;
}
return detected;
}
Owh and one more important part to this is that I customized the NVidia flex asset to output the particle positions, as this doesn't do so automatically. This is done by changing FlexContainer.cs to contain:
private int m_numParticles = 0;
List<FlexActor> m_actors = new List<FlexActor>();
public int maxParticles { get { return m_maxParticles; } set { m_maxParticles = value; } }
public int numParticles { get { return m_numParticles; } }
public List<FlexActor> actors { get { return m_actors; } }
Modify its AddActor and RemoveActor function:
public void AddActor(FlexActor actor)
{
if (m_actorCount == 0) CreateContainer();
actor.id = m_actorCount;
++m_actorCount;
m_actors.Add(actor);
m_numParticles += actor.asset.particles.Length;
}
public void RemoveActor(FlexActor actor)
{
m_numParticles -= actor.asset.particles.Length;
m_actors.Remove(actor);
--m_actorCount;
if (m_actorCount == 0) DestroyContainer();
}
Additionally you need to change the FlexActor.cs to contain:
[SerializeField]
Vector4[] m_particles;
[SerializeField]
Vector3[] m_velocities;
public Vector4[] particles
{
get { return m_particles; }
}
public Vector3[] velocities
{
get { return m_velocities; }
}
And change the AqcuireAsset and ReleaseAsset functions:
void AcquireAsset()
{
if (subclassAsset)
{
m_currentAsset = subclassAsset;
m_currentAsset.onBeforeRebuild += OnBeforeRecreate;
m_currentAsset.onAfterRebuild += OnAfterRecreate;
m_particles = new Vector4[m_currentAsset.particles.Length];
m_velocities = new Vector3[m_currentAsset.particles.Length];
}
}
void ReleaseAsset()
{
if (m_currentAsset)
{
m_currentAsset.onBeforeRebuild -= OnBeforeRecreate;
m_currentAsset.onAfterRebuild -= OnAfterRecreate;
m_currentAsset = null;
m_particles = null;
m_velocities = null;
}
}
Add the following line to the OnFlexUpdate function (after the UpdateBounds call):
UpdateParticles(_particleData);
A new function, defined by:
void UpdateParticles(FlexContainer.ParticleData _particleData)
{
if (m_container != null && m_indices != null && m_indices.Length > 0)
{
_particleData.GetParticles(m_indices[0], m_indices.Length, m_particles);
_particleData.GetVelocities(m_indices[0], m_indices.Length, m_velocities);
}
}
This function is the most important part and is what enables the updating of the particle positions. I found these changes by investigating the following git repo: https://github.com/neuroailab/flex-ml-agents/
A bit of a big workaround but I had most of the functionality implemented already so extending it like this wasn't that much of a problem.
Your answer
![](https://koobas.hobune.stream/wayback/20220612220313im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
What is the best way to render a volume from a stack of 2D slices? 0 Answers
How to fill a volume with a procedurally generated 3d voxel grid? 0 Answers
Volume detection. 0 Answers
Check if GameObject is within the volume of a cone. 4 Answers
Rounding edges of cubes (Voxels and Marching Cubes) 1 Answer