Floatlands devblog #27 - placement test function

Vili, our lead programmer will present to you a solution he worked on the previous week – placement test function. It checks if a certain object is colliding with the other objects in the world.

Posted by on

This blog post will be a bit more technical, so just a heads up before you start reading. Vili, our lead programmer will present to you a solution he worked on the previous week – placement test function. It checks if a certain object is colliding with the other objects in the world.

### PLACEMENT TEST FUNCTION

I’ve created a function that checks if 3D mesh object is colliding with the world – other 3D meshes and primitives. The hardest part here is to check if your 3D mesh is inside another 3D mesh and their walls/triangles are not colliding, because Unitys Physics.OverlapBox doesn’t return other 3D meshes. The reason is that 3D meshes are not usually topologically closed.

This is my test 3D mesh. It has 1700 vertices and 580 triangles.

The function is called ‘CanPlaceMesh’ and returns true or false and looks like this:

``public static bool CanPlaceMesh(MeshCollider testCollider, LayerMask mask, Plane ignorePointsPlane)``

I did an approximative calculation, because we don’t need 100% accuracy, but 90% or so suffices. The more important thing is that the function is fast. As you can see, my test mesh has 1700 vertices and that’s a lot of triangles. So I just took a small amount of triangle centers. The reason I choose triangle centers is because they are better distributed than just edges.

``````for(int i = 0;i < tris.Length; i+=8*3) { //Skip every 23 triangle
Vector3 a = verts[tris[i + 0]];
Vector3 b = verts[tris[i + 1]];
Vector3 c = verts[tris[i + 2]];
Vector3 triCenter = (a + b + c) / 3f; //calculate center
Vector3 point = testCollider.transform.TransformPoint(triCenter); //transform into world position
// if points on positive side of the plane
if(ignorePointsPlane.GetSide(point)) {
DebugDraw.DrawMarker(point, 0.1f, Color.green, 0f);
}
else
{
DebugDraw.DrawMarker(point, 0.1f, Color.red, 0f);
}
}``````

So out of 1700 points I get to check only around 1700/(8*3) = 70 points. Still quite too much, but remember, if any point is found colliding with the world, the algorithm stops and returns false – meaning it can’t place. So we check averagely 70/2 = 35 points.

Triangle center points

If you look closely, points are located in triangle centers. Let me first show you this .gif and then explain the red and green points (and blue splitting plane):

Splitting plane animation

Explanation is simple: we dip 3D mesh into the ground, so we really need to ignore the bottom points of 3D mesh or else it will collide with ground. This is exactly what this plane is meant for and why it’s called ignorePointsPlane. Now you may wonder what I do with these points? Before I start testing if points collide with the world, I need to do a query for nearby colliders:

``````var wB = testCollider.bounds; //get testCollider world bounds
Collider[] colliders = Physics.OverlapBox(wB.center, wB.extents + Vector3.up * 10f, Quaternion.identity, mask, QueryTriggerInteraction.Ignore); //query for all nearby colliders.``````

You may have noticed ‘Vector3.up * 10f’ – this extends query box up so it hits mesh colliders. If our mesh testCollider is inside another mesh collider, normal query box (testCollider.bounds) won’t detect it.

This is why I extend it upwards so that query box looks like this:

And you see it hits a big island mesh. Now that I have collected all ‘nearby’ colliders, I can test if points are inside them or not. I made special functions for concave mesh colliders and convex+primitive colliders. Those functions work only on closed meshes so this is why we use only closed 3D colliders.

``````Collider[] colliders = Physics.OverlapBox(wB.center, wB.extents + Vector3.up * 10f, Quaternion.identity, mask, QueryTriggerInteraction.Ignore);
for (int i = 0; i < colliders.Length; i++) {
Collider c = colliders[i];

if (testCollider == c)
continue;

if (c is MeshCollider && !(c as MeshCollider).convex)  {
for (int k = 0; k < _tempList.Count; k++) {
if (c.IsPointInside(_tempList[k])) {
DebugDraw.DrawBounds(wB, Color.red, 0f);
return false;
}
}
}
else {
for (int k = 0; k < _tempList.Count; k++)
if (c.IsPointInsideConvex(_tempList[k])) {
DebugDraw.DrawBounds(wB, Color.red, 0f);
return false;
}
}
}

DebugDraw.DrawBounds(wB, Color.green, 0f);
return true;``````

### THE END RESULT:

Finished product acts like this

This is a really great idea!!
Much Love.