Mesh Octree and BSP Tree

Hello,

I’m looking to build a mesh BSP tree to allow easy exploration of a mesh at runtime (primary to find the nearest point on the surface of a mesh with respect to another point). I figured I’d start with an octree, since they’re a bit simpler (no need to define a partition plane). The biggest problem I’m encountering when trying to figure out the algorithm is how to properly partition a space filled with triangles. I’ve searched up and down the internet and can’t seem to find an answer to this question. When partitioning a mesh, there will inevitability be triangles that will be split down the partition plane (or in two different quadrants of an octree), as shown below.

Now obviously in this example you could select a slice plane that would not have any conflicts, but for more complex meshes that would not be practical. How is this resolved? Is there a best practice method? If you arbitrarily assign each conflicted triangle to one side or another, you’ll run into an issue where the nearest triangle to a point will be on the wrong side of the partition plane. I thought of maybe just having each triangle have membership in both sides of the plane, but that seems redundant, and I was hoping for a better solution.

Thanks for any help,
Erik

As far as I understand, using those methods for surfaces is impractical (actually, just working with surfaces as your smallest unit on anything is typically impractical).

It’s substantially easier to just search by points that reference the triangles they belong to. You just need to account for the fact that the nearest point doesn’t necessarily have to be a part of a triangle at a point (really depends on how the mesh is constructed). If anything, any graph that works with surfaces would be structured by dividing the points, but would reference what triangles intersect any given volume. You would end up with volumes that are likely to have several surfaces to them.

I liked the idea of using points since it makes subdividing the space up much easier. Here’s an issue I encountered with it:

Once you have your nearest vertex, your job is to now find the triangle on which the nearest point lays. However, some vertices are used by more than one triangle. This would mean you’d need to iterate through each triangle that has that vertex as a point to find which is the triangle on which the nearest point lays, as shown here.


Where we want to find the closest point on the mesh to the blue cross. The nearest vertex is the red dot, but since it’s connected to 8 triangles we need to iterate through each one (I assume, unless there’s a clever workaround) to tell which is nearest. This isn’t horrible since it can be done easily but it seems like an extra step that wouldn’t need to be done with a triangle tree.

With a triangle tree I figured that if a surface is intersected by a partitioning plane, I could store that surface on the node at the same level of that plane. So instead of having a tree full of empty nodes and then the triangles exclusively on the leaves, both non-leaves and leaves would have triangles and then as you traverse the tree you’d keep track of the triangles encountered at each node.

So for the mesh in my first post, the tree would have the three blue dotted triangles in a set at the root, with a set consisting of the green triangles as one child and a set of the pink triangles as the other child.

This seems like a good idea, but I haven’t thought it through enough to notice if there’s any hitches.

I’m not fully sure what you’re saying here. Are you saying that the nearest point isn’t necessarily going to be an actual vertex on the mesh, but can also lay on the actual plane of a triangle? Sorry if I’m misunderstanding that part but it sounded important!

Thanks for the reply!

At the very least, it’s not hard to go through a number of triangles and make bounding boxes to cheaply filter out ones that are obviously no good. Another option is, instead of grabbing just the nearest point, grab a few of the nearest points and figure triangles from that.

Take two triangles that share an edge. One of them is small and obtuse, while the other is large and acute. If you’re point is near the shared edge, it’s quite possible for the nearest point to be the on the smaller triangle when the point you are testing is in the larger one. At this point I am now realizing you pretty much have to grab several points if you really want to limit the number of checks you make. At the same time, it’s not smart to be too reliant on the tree alone. If the topology of the mesh is weird and/or you end up with triangles that are proportionally huge, there is almost no use for a tree.

Ok. On that note, I drew up a diagram of the process of finding the nearest point with a tree partitioned by points (which can reference the triangles they belong to). We have a mesh partitioned by a single hyperplane (a line in this case since it’s 2D) dividing the vertices into two sets of children, blue and yellow. We input point P into our method to query what the closest point on the surface of the mesh is to P.


Step 1 just shows the initial setup. By checking which side of the partitioning plane P lies, we explore into the tree node with the blue vertices. In Step 2 we query against every blue vertex in the set to see which is the closest. In Step 3 we find the closest, and retrieve all of the triangles that use this vertex. We then check to see which triangle is closest (I’m not yet sure how to do this or how demanding this computation is). The darker orange triangle is shown to be the nearest of the two, so we select it, and in step 4 we calculate where on the surface of this triangle the nearest point (drawn in the cyan cross) actually lies.

I like using the points method since it seems more optimal compared to surfaces. Using surfaces we’d have to query against 7 triangles in step 2 to see which is closest to P, but it would allow us to skip step 3 (since we’d already know the closest triangle). I guess it depends on how demanding it is to compute the nearest triangle versus the nearest point (a distance squared check). But back to your reply…

What would be the purpose of this? Wouldn’t the nearest triangle always be attached to the nearest vertex?

I couldn’t think of an example case of this, so I drew up a diagram with what I think you’re explaining.


I have the querying point P inside the large acute triangle…but it seems the nearest point on the surface (which would be the same as P since P is within the triangle’s bounds) would be inside the acute as well. In what case would it not be?

Thanks very much for the detailed reply, I hope you’re enjoying the increasing complexity of the diagrams.

Use P as the center of a circle that touches both points on the shared edge. If the outer point in the smaller triangle is inside the circle, it’s now the closest vertex to P. This way if you don’t grab additional vertexes, you won’t be able to find the triangle P is actually on.

Yeah, this is pretty much a recipe for disaster. If you don’t test a point that is on a surface, it’s quite possible you might end up checking ever surface for a match.

Okay, thanks for the clarification! I’ll build a rough implementation and bump this post with any results when they come.

Interesting thread.
I have some questions regarding the context of the problem. Is your tree single-use, or do you reuse it? Do you build many trees? If so, do you build them often? What’s your target-platform? (PC, console, mobile)

What @RockoDyne said is important: How good (or bad) it performs depends on how many vertices you query. Since the runtime-complexity of the linear search is O(dn) (in this case O(2n), therefore O(n)), this can really kick your ass with high-poly meshes.
You also need to consider what kind of meshes this algorithm can be run against. If there are big variations in polycount, the performance will vary greatly too.
Since you’re developing a game, one of your top-priorities must be constant performance and those kinds of algorithms can result in huuuuuuuuge problems.

Multiple uses per frame. I’m using it for a custom character controller, specifically to resolve collisions with mesh colliders. Since when you contact a collider, in order to resolve collision you move the controller to the nearest point on the surface of this collider. Since I’d be making the assumption (at least initially) that the user would not be modifying their mesh collider’s meshes at runtime, the tree only needs to be built once on initialization for each mesh collider in the scene.

And yeah, I’m aware that this is something I’d have to test very thoroughly for it to be practically useful, since it would be run many times per frame (and since the character controller can be set to have a fixed timestep, sometimes more than once per controller). Luckily I know that it is feasible to do this, since user fholm made an octree based implementation for his RPG Controller. However, I want to do this try to improve performance on what he has (using a BSP tree instead), as well as to learn how to build an octree/BSP tree myself, and document it to hopefully help others.

I see.
I’ve asked because in that case, you should build your tree once (maybe even offline in the editor) and just load it, if you can. Reusing trees is something you can’t always do depending on your use-case.
For a uni project we (some friends and me) wrote a boid system. We used a kd-tree for the all-k-nearest-neighbor-search. Given n boids, we needed the 3 nearest neighbors of every single one of them.
It ended up becoming the bottleneck for performance. Having to rebuild it after each iteration certainly didn’t help.

On a side-note:
Ohhhhhhh boy. (Good) player controllers are a real PITA, aren’t they? It took me months before I even found the most fitting approach for mine. It seemed like there was always some plausible border-condition where it completely failed.
Luckily it’s over now. :smile:

Yeah, I figured if the trees ended up taking too long to build then it would definitely be a good idea to build it offline. I’m guessing building a tree in an optimal fashion can be just as complex as searching/traversing it.

I had no experience with any kind of collision detection when I started this so it was a bit of an effort to get into it. I really like games that have extremely tight and well designed controls though, so it seemed like a necessity to learn how to build my own from scratch. Unity’s limited API to access the Physics doesn’t help too much either, since all you really get is OverlapSphere and the different casts.

In other news…I finished a really basic script that finds the nearest point on a triangle with respect to another point.

It isn’t yet searching through the triangles to find the nearest but that can be easily done through brute force at this point. This step was tougher than I thought what with all those Barycentric coordinates. I’ll post a cleaned up version of the code later in case anyone is interested.

First draft. Lots of incredibly obvious optimizations to do. Going to build a tree and see how it improves. Currently I get 9.5 FPS with 100 checks per frame (I get 2000 regularly in the scene).

using UnityEngine;
using System.Collections;
using System;

public class BSPTree : MonoBehaviour {

    [SerializeField]
    int tests;

    [SerializeField]
    MeshFilter testMesh;

    private Mesh mesh;

    // Use this for initialization
    void Start () {
        mesh = testMesh.mesh;

        triangles = new int[mesh.triangles.Length];
        normals = new Vector3[mesh.normals.Length];
        vertices = new Vector3[mesh.vertices.Length];

        triangleLength = triangles.Length;

        Array.Copy(mesh.triangles, triangles, mesh.triangles.Length);
        Array.Copy(mesh.normals, normals, mesh.normals.Length);
        Array.Copy(mesh.vertices, vertices, mesh.vertices.Length);

        // DrawMesh(mesh, testMesh.transform);
    }
   
    // Update is called once per frame
    void Update () {
        for (int i = 0; i < tests; i++)
        {
            FindNearestPoint();
        }
    }

    void FindNearestPoint()
    {
        Vector3 testPosition = transform.position;

        Vector3 closestPoint = ClosestPointOn(testMesh, testPosition);

        //DebugDraw.DrawMarker(closestPoint, 3.0f, Color.red, 0, false);
    }

    int[] triangles;
    Vector3[] vertices;
    Vector3[] normals;

    int triangleLength;

    Vector3 ClosestPointOn(MeshFilter filter, Vector3 to)
    {
        float shortestDistance = float.MaxValue;
        int shortestTriangle = 0;
        Vector3 shortestPoint = Vector3.zero;

        // Iterate through all triangles
        for (int i = 0; i < triangleLength; i += 3)
        {
            Vector3 p1 = TransformPoint(vertices[triangles[i]], filter.transform);
            Vector3 p2 = TransformPoint(vertices[triangles[i + 1]], filter.transform);
            Vector3 p3 = TransformPoint(vertices[triangles[i + 2]], filter.transform);

            Vector3 normal = Vector3.Cross((p2 - p1).normalized, (p3 - p1).normalized).normalized;

            Vector3 centroid = (p1 + p2 + p3) * 0.3333f;

            Vector3 projected = Math3d.ProjectPointOnPlane(normal, centroid, to);

            Vector3 uvw = Barycentric(projected, p1, p2, p3);

            Vector3 nearest = ProjectPointOntoBarycentric(uvw, p1, p2, p3, projected);

            float distance = (to - nearest).sqrMagnitude;

            if (distance < shortestDistance)
            {
                shortestDistance = distance;
                shortestTriangle = i;
                shortestPoint = nearest;
            }
        }

       // DrawTriangle(mesh.vertices[mesh.triangles[shortestTriangle]], mesh.vertices[mesh.triangles[shortestTriangle + 1]], mesh.vertices[mesh.triangles[shortestTriangle + 2]], filter.transform, Color.cyan);

        return shortestPoint;
    }

    Vector3 Barycentric(Vector3 p, Vector3 a, Vector3 b, Vector3 c)
    {
        Vector3 v0 = b - a, v1 = c - a, v2 = p - a;

        float d00 = Vector3.Dot(v0, v0);
        float d01 = Vector3.Dot(v0, v1);
        float d11 = Vector3.Dot(v1, v1);
        float d20 = Vector3.Dot(v2, v0);
        float d21 = Vector3.Dot(v2, v1);

        float denom = d00 * d11 - d01 * d01;
        float v = (d11 * d20 - d01 * d21) / denom;
        float w = (d00 * d21 - d01 * d20) / denom;
        float u = 1.0f - v - w;

        return new Vector3(u, v, w);
    }

    Vector3 ProjectPointOntoBarycentric(Vector3 barycentric, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p)
    {
        float u = barycentric.x;
        float v = barycentric.y;
        float w = barycentric.z;

        Vector3 projected = Vector3.zero;

        if (u > 0 && v > 0 && w < 0)
        {
            projected = Math3d.ProjectPointOnLineSegment(p1, p2, p);
        }
        else if (u < 0 && v > 0 && w > 0)
        {
            projected = Math3d.ProjectPointOnLineSegment(p2, p3, p);
        }
        else if (u > 0 && v < 0 && w > 0)
        {
            projected = Math3d.ProjectPointOnLineSegment(p3, p1, p);
        }
        else if (u > 0 && v < 0 && w < 0)
        {
            projected = p1;
        }
        else if (u < 0 && v > 0 && w < 0)
        {
            projected = p2;
        }
        else if (u < 0 && v < 0 && w > 0)
        {
            projected = p3;
        }
        else
        {
            projected = p;
        }

        return projected;
    }

    Vector3 TransformPoint(Vector3 p, Transform t)
    {
        return t.rotation * p + t.position;
    }

    void DrawMesh(Mesh mesh, Transform t)
    {
        for (int i = 0; i < mesh.triangles.Length; i += 3)
        {
            DrawTriangle(mesh.vertices[mesh.triangles[i]], mesh.vertices[mesh.triangles[i + 1]], mesh.vertices[mesh.triangles[i + 2]], t);
        }
    }

    void DrawTriangle(Vector3 a, Vector3 b, Vector3 c)
    {
        Debug.DrawLine(a, b, Color.cyan);
        Debug.DrawLine(b, c, Color.cyan);
        Debug.DrawLine(c, a, Color.cyan);
    }

    void DrawTriangle(Vector3 a, Vector3 b, Vector3 c, Color color)
    {
        Debug.DrawLine(a, b, color);
        Debug.DrawLine(b, c, color);
        Debug.DrawLine(c, a, color);
    }

    void DrawTriangle(Vector3 a, Vector3 b, Vector3 c, Transform t)
    {
        a = t.rotation * a + t.position;
        b = t.rotation * b + t.position;
        c = t.rotation * c + t.position;

        Debug.DrawLine(a, b, Color.black);
        Debug.DrawLine(b, c, Color.black);
        Debug.DrawLine(c, a, Color.black);
    }

    void DrawTriangle(Vector3 a, Vector3 b, Vector3 c, Transform t, Color color)
    {
        a = t.rotation * a + t.position;
        b = t.rotation * b + t.position;
        c = t.rotation * c + t.position;

        Debug.DrawLine(a, b, color);
        Debug.DrawLine(b, c, color);
        Debug.DrawLine(c, a, color);
    }

    void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawSphere(transform.position, 0.25f);
    }
}
1 Like

Interesting concept, the point snaps towards the mesh constantly.
I wonder if the method can be applied to find the nearest point on walls?

Yes! This is what Im trying to implement now as well… I may write here if I do any progress and dont get banned for posting in a wrong thread or sth.

That’s what I use it for. You can see the current script I use here. The actual partitioning steps could probably be improved, since they’re pretty simple right now, and I’ve had some suggestions about using a KD-tree instead.

1 Like

Almost forgotten about this post…
This is neat, I look forward to testing it and showing you my result!

Random comment on that code:

Doing this:

Will make 2 copies of vertices and 2 copies of triangles. Every call to mesh.vertices etc makes a new array. Try to do vertexCount = vertices.Length instead.