How do I get the cross sectional surface area of my geometry mesh in meters?

I need the Cross section (geometry) of my GameObject mesh for calculating the drag force. Assuming 1 unit = 1 meter, I need the surface area in meters. I can obtain the wind direction. But I can’t imagine how to obtain and calculate the surface projected area. Is there a known way?

I’m reading: https://forum.unity3d.com/threads/projecting-shadow-locally-per-mesh-how-to-do-it.283056/?_ga=1.141900081.2090915164.1475000191

Probably a difficult question for an expert.

I am using this code for an airplane. It assumes that your models are convex. If you want concave models to work, you will have to break up your model (and modify my algorithm to stop including all of the child objects when finding vertices).

The logic goes like this:

Get an array of Vector3s containing all of the vertices (that are different). So that we do not have to find this every time, I store it in a dictionary.

It finds two perpendicular vectors (perp1 and perp2) that are perpendicular to the normal vector. These will be used as basis vectors.

It squishes all of the vertices on the plane perpendicular to normal using the Dot method to find these points in terms of the basis vectors in the plane.

It uses a hull mapping algorithm from Code Complete Convex Hull c# for Unity · GitHub to find the hull of the shape.

It finds the area of the hull and returns it.

public static Dictionary<GameObject, Vector3[]> verticesDict = new Dictionary<GameObject, Vector3[]>();

    public static float CrossSectionArea(GameObject g, Vector3 normal)
    {
        if(normal.x == 0f && normal.y == 0f && normal.z == 0f)
        {
            return 0f;
        }

        Vector3[] vertices;
       
        PutVerticesInDict(g, out vertices);
       

        normal = normal.normalized;

        Vector3 perp1 = Perp(normal).normalized;
        Vector3 perp2 = Vector3.Cross(normal, perp1);


        List<Vector2> inThePlane = new List<Vector2>(vertices.Length);
       

        for (int i = 0; i < vertices.Length; i++)
        {
           
            inThePlane.Add(new Vector2(Vector3.Dot(perp1, vertices[i]), Vector3.Dot(perp2, vertices[i])));
        }
        return HullArea(ComputeConvexHull(inThePlane));
    }
    public static void PutVerticesInDict(GameObject g, out Vector3[] vertices)
    {
        if (!verticesDict.TryGetValue(g, out vertices))
        {
            MeshFilter[] meshes = g.GetComponentsInChildren<MeshFilter>();


            HashSet<Vector3> svertices = new HashSet<Vector3>();
            foreach (MeshFilter m in meshes)
            {

                foreach (Vector3 v3 in m.mesh.vertices)
                {

                    svertices.Add(m.transform.TransformPoint(v3));
                }
            }

            vertices = new Vector3[svertices.Count];
            svertices.CopyTo(vertices);
            verticesDict.Add(g, vertices);
        }
    }
    public static Vector3 Perp(Vector3 v3)
    {
        return v3.z < v3.x ? new Vector3(v3.y, -v3.x, 0) : new Vector3(0, -v3.z, v3.y);
    }


    public static float HullArea(IList<Vector2> hull)
    {
       
        float sum = 0f;
        for (int i = 1; i < hull.Count - 1; i++)
        {
            sum += TriangleArea(hull[0], hull[i], hull[i + 1]);
        }
        return sum;
    }
    public static float TriangleArea(Vector2 v0, Vector2 v1, Vector2 v2)
    {

        return Mathf.Abs((v1.x - v0.x) * (v2.y - v0.y) - (v2.x - v0.x) * (v1.y - v0.y)) * .5f;
    }
    //From https://gist.github.com/dLopreiato/7fd142d0b9728518552188794b8a750c
    public static IList<Vector2> ComputeConvexHull(List<Vector2> points, bool sortInPlace = false)
    {
        if (!sortInPlace)
            points = new List<Vector2>(points);
        points.Sort((a, b) =>
            a.x == b.x ? a.y.CompareTo(b.y) : (a.x > b.x ? 1 : -1));

        // Importantly, DList provides O(1) insertion at beginning and end
        CircularList<Vector2> hull = new CircularList<Vector2>();
        int L = 0, U = 0; // size of lower and upper hulls

        // Builds a hull such that the output polygon starts at the leftmost Vector2.
        for (int i = points.Count - 1; i >= 0; i--)
        {
            Vector2 p = points[i], p1;

            // build lower hull (at end of output list)
            while (L >= 2 && ((p1 = hull.Last) - (hull[hull.Count - 2])).Cross(p - p1) >= 0)
            {
                hull.PopLast();
                L--;
            }
            hull.PushLast(p);
            L++;

            // build upper hull (at beginning of output list)
            while (U >= 2 && ((p1 = hull.First) - (hull[1])).Cross(p - p1) <= 0)
            {
                hull.PopFirst();
                U--;
            }
            if (U != 0) // when U=0, share the Vector2 added above
                hull.PushFirst(p);
            U++;
            Debug.Assert(U + L == hull.Count + 1);
        }
        hull.PopLast();
        return hull;
    }

    private static Vector2 Sub(this Vector2 a, Vector2 b)
    {
        return a - b;
    }

    private static float Cross(this Vector2 a, Vector2 b)
    {
        return a.x * b.y - a.y * b.x;
    }

    private class CircularList<T> : List<T>
    {
        public T Last
        {
            get
            {
                return this[this.Count - 1];
            }
            set
            {
                this[this.Count - 1] = value;
            }
        }

        public T First
        {
            get
            {
                return this[0];
            }
            set
            {
                this[0] = value;
            }
        }

        public void PushLast(T obj)
        {
            this.Add(obj);
        }

        public T PopLast()
        {
            T retVal = this[this.Count - 1];
            this.RemoveAt(this.Count - 1);
            return retVal;
        }

        public void PushFirst(T obj)
        {
            this.Insert(0, obj);
        }

        public T PopFirst()
        {
            T retVal = this[0];
            this.RemoveAt(0);
            return retVal;
        }
    }
3 Likes

Hello Matthew,
Your first answer is amazing. And by the way, very well come to this forum!

I read your code and I did not implement it yet.
But since I’m relatively new to C#, I did not understand Cross(p - p1) at lines 93 and 102 because Cross needs two inputs.

I tried rewriting but I came across this line

((p1 = hull.Last) - (hull[hull.Count - 2])).Cross(p - p1)

Can it be then instead of a - is a “,”? for exmple

// build lower hull (at end of output list)
            while ( L >= 2 )
            {
                p1 = hull.Last - (hull[hull.Count - 2]);

                if ( Cross(p , p1) >= 0)
                {
                    hull.PopLast();
                    L--;
                }
            }

I have an idea that would work pretty decently as an approximation possibly. Maybe not something for seriously realistic game. If the cross sectional area is really just a 2D area looking from the front of the object, then why not just place some transforms into the world around each component or mesh, and then at runtime compute the area of the shape you generated from 2D vectors.

This of course relies on (my limited) understanding of this formula. If in fact the surface area of the front of the mesh is needed, then certainly you would need to iterate over the area and compute this. I would recommend performing this once for your parts, store it as a const, and use it in an AirResistanceModifier (or something named like that) MonoBehaviour that updates the drag property of the rigidbody component.

I’m needing to implement this right now, and I figured, hey why not just place down some vectors to define the general shape of the object. If you’re looking for simulation, this is not what you want of course. Anyways, thought I’d mention my idea, since I’m working on the same thing.

Since we are talking about drag we do not want a cross section, what we need is a projection of a mesh in direction of fluid movement.

An approximate solution could be obtained by rendering mesh on a texture and than counting number of pixels. Can be calculated by montecarlo method for faster computations.

1 Like

Gotcha! Thank you so much for clarifying this confusing bit for me. Examples of cross sectional area always resulted in images of 2D planes intersecting 3D objects. Your solution makes perfect sense to me, and I can definitely implement that. Thank you very much for helping me find a simple way to easily compute this.

I have two ideas here after studying the Monte Carlo method.

  1. Place a camera in the scene in orthographic mode which only renders the flying craft. Then, the camera would be programmed to move in the direction that the fluid is moving, and use the Monte Carlo method to compute the cross sectional area for a given frame that was captured. I know I’d need some way to scale the measurement coming from the camera, essentially I’d need to know the number of units my viewport is measuring.

  2. This is just me taking a stab in the dark, as I am not well versed in this subject. But I wonder if I’d be possible to create a heuristic model to determine drag given the direction the fluid would be moving over the craft. I was watching a video about Monte Carlo tree search, and thought maybe it could quickly calculate the drag. The applications where for long task decision making, but I feel like the application could be much broader.

That’s an interesting topic and the idea to count pixels of a mesh rendered to a texture is a great method to get the frontal area.
But to properly simulate drag you also have to include the drag coefficient of the mesh;

Maybe you can render the camera’s depth to a texture and analyse the meshes shape to somehow approximate the drag coefficient.
Like building up a curvature map from it to get convex/concave info, or even create a flowmap based on that.