[color=#333333][font=Gudea]
[font=arial]Hi Floatlands fans! 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.[/font][/font][/color]
[font=arial]PLACEMENT TEST FUNCTION[/font]
[font=arial]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:[/font]
[code=js:1]public static bool CanPlaceMesh(MeshCollider testCollider, LayerMask mask, Plane ignorePointsPlane)
[font=arial][color=#333333]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.[/color][/font]
[code=js:1]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); _tempList.Add(point); } else { DebugDraw.DrawMarker(point, 0.1f, Color.red, 0f); }}
[font=arial][color=#333333]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.
[/color]
[color=#444444]Triangle center points[/color]
[color=#333333]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):
[/color]
[color=#444444]Splitting plane animation[/color]
[color=#333333]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:[/color][/font]
[code=js:1]var wB = testCollider.bounds; //get testCollider world boundsCollider[] colliders = Physics.OverlapBox(wB.center, wB.extents + Vector3.up * 10f, Quaternion.identity, mask, QueryTriggerInteraction.Ignore); //query for all nearby colliders.
[font=arial][color=#333333]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:
[/color]
[color=#333333]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.[/color][/font]
[code=js:1]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; 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;
[font=arial]THE END RESULT
Vili Vol?ini
I like it and I think I follow your logic. Immediately a few questions come to mind. You say that every 23rd triangle centre is referenced.
Does that mean the geometries are created in a uniform fashion that guarantee a good sampling of the geometry by only testing every 23rd face, Or does this mean there could be certain geometries which would generate is such a fashion that it could by chance, break the function because every 23rd triangle exists on one edge?
Upon geometry creation do you analyse the geometry and identify certain faces to be used as your test subjects for the function?
Also, what is a scenario that you could imagine where the function would not work properly?
Regardless, I like it a lot.