The Points On Unity’s 3D Primitive Objects And Finding Random Points

Using the mesh information of primitive objects on Unity, we can acquire the information of local coordinates of the vertices in the mesh. This information is very useful for area limitation and filling. Moreover, with the guidance of defined vertices, we can come up with new ones.

I am in an increasing curiosity of finding points on objects. The journey is fun but It wasn’t planned. The more I look into things the more I come up with. Here is the first post on finding corner points of a plane to apply restrictions and the second one, using corner points to find a random point on a plane.

Object Points

ObjectPoints class is the parent class for SpherePoints, CubePoints, CylinderPoints and PlanePoints classes. It contains base variables and functions for vertice calculations.

objects

Through the development, inheritance is used to prevent code duplication for different types of classes and the possibility of adding new types of classes. If you are not familiar with inheritance, we can say ObjectPoints class is the skeleton for other points classes. These classes decide on what to do with the functionality ObjectPoints class provides.

    protected List<Vector3> ObjectVertices;       //Global vertex points on the object mesh
    protected List<Vector3> ObjectUniqueVertices; //Global distinct vertex points on the object mesh
    protected List<Vector3> ObjectLocalVertices;  //Local vertex points on the object mesh
    protected List<Vector3> CornerPoints;
    protected List<Vector3> EdgeVectors;
    protected Vector3 RandomPoint;

Vertice information taken from the meshes fills vertice variables. If the child class needs corner points or edge vectors, they are calculated by that class.

    public List<Vector3> GetCornerPoints() //corner points calculated and returned to outer objects
    {
        CalculateCornerPoints();
        return CornerPoints;
    }

    public List<Vector3> GetObjectGlobalVertices() //global vertices calculated and returned to outer objects
    {
        CalculateObjectVertices();
        return ObjectVertices;
    }

    public List<Vector3> GetObjectLocalVertices()  //local vertices calculated and returned to outer objects
    {
        CalculateObjectVertices();
        return ObjectLocalVertices;
    }

    public List<Vector3> GetObjectUniqueVertices() //unique global vertices calculated and returned to outer objects
    {
        CalculateObjectVertices();
        return ObjectUniqueVertices;
    }

    public Vector3 GetRandomPoint() //random point calculated and returned to outer objects
    {
        CalculateRandomPoint();
        return RandomPoint;
    }

These are common get functions of the class to share these values with other classes by not causing a security problem on the variables’ value.

There are 4 calculation functions, calculation of vertice points, corner points, edge vectors, and random point. Vertice points are calculated by the same way for each class as parent class implements this function. Each child class implements a function for random point. Some do not use corner points or edge vectors, so they do not implement those.

CubeVerts.PNG
objects' points.PNG
    private void CalculateObjectVertices() //local, global and unique global vertices calculated for the object
    {
        ObjectLocalVertices.Clear();
        ObjectVertices.Clear();
        ObjectUniqueVertices.Clear();
        ObjectLocalVertices = new List<Vector3>(GetComponent<MeshFilter>().sharedMesh.vertices); //get vertice points with local coordinates from the mesh of the object
        foreach (Vector3 point in ObjectLocalVertices) //all the points are transformed into global points
        {
            ObjectVertices.Add(transform.TransformPoint(point));
        }
        ObjectUniqueVertices = ObjectVertices.Distinct().ToList();
    }

Local vertices coordinates are taken from the mesh of the object. These are translated to global coordinates by using transform.TransformPoint(Vector3) and lastly all the unique coordinates among the coordinates of the mesh taken with using LINQ’s Distinct() function.

    protected virtual void CalculateEdgeVectors(int VectorCornerIdx) 
    {
        CalculateCornerPoints();
        EdgeVectors.Clear(); 
        //It is up to child to fill the vectors
    }

    protected virtual void CalculateEdgeVectors(int VectorCornerIdx, int vectorfaceidx)
    {
        CalculateCornerPoints();
        EdgeVectors.Clear();
        //It is up to child to fill the vectors
    }

    protected virtual void CalculateRandomPoint(){} //It is up to child how to calcullate random point

    protected virtual void CalculateCornerPoints()
    {
        CalculateObjectVertices();
        CornerPoints.Clear(); //in case of transform changes corner points are reset
        //It is up to child to how to calculate corner points
    }

Whenever a value to be calculated their previous values are cleared as the game object’s transform may be changed. These calculation functions will be defined by child objects.

Cube Points

Even though the calculation of the random point had the most work on cubes, After finding which face will have a random point, it is the same calculations for planes.

cube random + all.PNG

There are 24 vertices on cube mesh, 8 of them being unique. Actually, each 4 represents a face of the cube.

    readonly int FaceCount = 6;
    readonly int FaceVertexCount = 4;
    List<List<Vector3>> Faces = new List<List<Vector3>>();

Using the first 8 vertices would require some logic to map to faces so, there is List<List<Vector3>> Faces and other information on cubes.

    protected  override void CalculateCornerPoints()   //Random point on cube calculated with faces, so faces are defined as well
    {
        base.CalculateCornerPoints();
        List<Vector3> OneFace = new List<Vector3>();

        for (int i = 0; i< ObjectVertices.Count;i++)
        {
            OneFace.Add(ObjectVertices[i]);
            if ((i + 1) % FaceVertexCount == 0)
            {
                Faces.Add(OneFace);
                OneFace = new List<Vector3>();
            }          
        }
        CornerPoints = ObjectUniqueVertices; 
    }

To find a random point on a face using barycentric coordinates as it was done for planes, we will need corner points. While calculating corner points each face is defined with the corner points on them. Every 4 points represent a face in order.

    private int GetRandomFaceIndex()
    {
        return Random.Range(0, FaceCount - 1);
    }
    protected override void CalculateEdgeVectors(int VectorCornerIdx,int faceIndex)
    {
        base.CalculateEdgeVectors(VectorCornerIdx,faceIndex);
        EdgeVectors.Add(Faces[faceIndex][3] - Faces[faceIndex][VectorCornerIdx]);
        EdgeVectors.Add(Faces[faceIndex][1] - Faces[faceIndex][VectorCornerIdx]);
    }

    protected override void CalculateRandomPoint()
    {
        int randomCornerIdx = Random.Range(0, 2) == 0 ? 0 : 2;
        int randomFaceIdx = GetRandomFaceIndex();
        CalculateEdgeVectors(randomCornerIdx, randomFaceIdx);

        float u = Random.Range(0.0f, 1.0f);
        float v = Random.Range(0.0f, 1.0f);

        if (v + u > 1) //sum of coordinates should be smaller than 1 for the point be inside the triangle
        {
            v = 1 - v;
            u = 1 - u;
        }

        RandomPoint = Faces[randomFaceIdx][randomCornerIdx] + u * EdgeVectors[0] + v * EdgeVectors[1]; 
    }
Asset 5.png

These calculations are the same as the calculations done for planes. The only difference is first we need to decide on a random face to find the points on. Then use that face for the calculations.

Sphere Points

For finding a random point on the sphere only the implementation of CalculateRandomPoint function is sufficient. No need to calculate corner points or any edge vectors.

sphere random + all.PNG

There are 515 vertices on the sphere mesh, 386 of them are unique.

    protected override void CalculateRandomPoint()
    {
        RandomPoint = (Random.Range(0, 2) == 0) ? WholeCoverageRandomPoint() : QuickRandomPoint();
    }

There are two approaches for finding a random point on the sphere. Which one to use is left to %50-%50.

    private Vector3 QuickRandomPoint() //random point chosen from the points on the sphere mesh
    {
        return ObjectUniqueVertices[Random.Range(0, ObjectUniqueVertices.Count)]; 
    }

One is the easy way, a random point is chosen among the vertices of the mesh.

    private Vector3 WholeCoverageRandomPoint()  //Random point chosen from any direction from the center of the sphere
    {
        Vector3 DirectionVector = new Vector3(  Random.Range(-1.0f, 1.0f),  //random direction vector
                                                Random.Range(-1.0f, 1.0f),
                                                Random.Range(-1.0f, 1.0f));
        DirectionVector.Normalize();
        float HalfRadius = GetComponent<Renderer>().bounds.extents.magnitude / 2; 
        return transform.position + DirectionVector * ( HalfRadius + SphereSize ); //center moved along the direction vector by radius
    }
bigger font sphereAsset 3.png

This approach has more coverage on the sphere. We get a random direction to move the center point towards by the half-length of the radius of the sphere.

Cylinder Points

Same for the sphere, for finding a random point on it, only the implementation of CalculateRandomPoint function is sufficient. No need to calculate corner points or any edge vectors. This one is the most cheated calculation among others. Because only the points on the side face of the cylinder are calculated and these points can be only between the opposing upper and down circles’ defined points. No random points on the circle faces.

cyclinder random + all.PNG

There is 42 unique, 88 total vertice on cylinder mesh. First 20 are the vertices on the bound of the lower circle, next 20 are on the upper circle and last 2 are the centers of circle faces.

    protected override void CalculateRandomPoint() //random point is chosen from the side plane of the cylinder
    {                                                                               
        base.CalculateRandomPoint();                
        int idx = Random.Range(0, (ObjectUniqueVertices.Count - 2)/2  ); //index of the first circle's vertex
        float DistanceWeight = Random.Range(0.0f, 1.0f); //distance weighted from the first circle's vertex

        Vector3 Direction = ObjectUniqueVertices[idx + (ObjectUniqueVertices.Count / 2) - 1] - ObjectUniqueVertices[idx];//direction of the side of the cylinder on chosen vertex
        RandomPoint = Direction * DistanceWeight + ObjectUniqueVertices[idx];
    }
cylinderAsset 4.png

First, we chose a random index of vertices on the border of the lower circle, which represents the first 20 points (ie. (ObjectUniqueVertices.Count – 2)/2 ). Since next 20 belongs to upper circle, idx + (ObjectUniqueVertices.Count / 2) – 1 gives us the opposing vertice’s index. Using them we calculate the direction vector starting from the point of the lower index to its opposing neighbor. Instead of normalizing the vector this time we divide it into smaller direction vector using a random weight between 0 and 1. Random point is calculated by moving the lower point in the weighted direction of the upper point.

Plane Points

Calculation of points and random point on the planes are explained in previous posts (points on a plane, random point on a plane). As the calculation for planes was the starting point of this study, it was very easy to convert that implementation to this style.

plane random + all.PNG
    protected override void CalculateEdgeVectors(int VectorCornerIdx)
    {
        base.CalculateEdgeVectors(VectorCornerIdx);
        EdgeVectors.Add(CornerPoints[3] - CornerPoints[VectorCornerIdx]);
        EdgeVectors.Add(CornerPoints[1] - CornerPoints[VectorCornerIdx]);
    }

    protected override void CalculateRandomPoint()
    {
        int randomCornerIdx = Random.Range(0, 2) == 0 ? 0 : 2; //there is two triangles in a plane, which tirangle contains the random point is chosen
                                                               //corner point is chosen for triangles as the variable
        CalculateEdgeVectors(randomCornerIdx);
        float u = Random.Range(0.0f, 1.0f);
        float v = Random.Range(0.0f, 1.0f);
        if (v + u > 1) //sum of coordinates should be smaller than 1 for the point be inside the triangle
        {
            v = 1 - v;
            u = 1 - u;
        }
        RandomPoint = CornerPoints[randomCornerIdx] + u * EdgeVectors[0] + v * EdgeVectors[1];
    }

    protected override void CalculateCornerPoints()
    {
        base.CalculateCornerPoints();
        CornerPoints.Add(ObjectVertices[0]); //corner points are added to show  on the editor
        CornerPoints.Add(ObjectVertices[10]);
        CornerPoints.Add(ObjectVertices[110]);
        CornerPoints.Add(ObjectVertices[120]);
    }

Showing The Points

To run the demo, I needed a simple UI controller with 2 toggles and a button. The decision of showing all points or the random point is left to the user and controlled by the toggles. The button is for requesting a new random point.

demo scene.PNG

UI of the demo
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) //the target object to show points is chosen by mouse click on it
        {
            RaycastHit hit; ;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out hit))
            {
                if (hit.collider.gameObject.GetComponent<ObjectPoints>() != null ) //FIX everytime the selected object clicked on, new random point is calculated
                {
                        SelectedPointSource = hit.collider.gameObject;
                        GetRandomPoint();                   
                }
            }
        }
    }

The selected object is selected by a mouse click on them. Since there is no control over if the selected object is already selected. Every time the object is clicked on, a new random point is requested.

    public void ToggleShowAllPoints()
    {
        ShowRandomPoint = !ShowRandomPoint;
    }

    public void ToggleShowRandomPoints()
    {
        ShowAllPoints = !ShowAllPoints;
    }

I am not sure toggling the boolean with no controls like this is the best practice but I like it a lot. These go to toggles OnChange event.

    public void GetRandomPoint()
    {
        RandomPoint = SelectedPointSource.GetComponent<ObjectPoints>().GetRandomPoint();
    }

Button is here to ask for a new random point from the SelectedPointSource. I would like to emphasize using ObjectPoints as a component, not looking for the specific class type. Using inheritance provides this functionality to have the child class be callable under parent class.

void OnDrawGizmos()
    {
        if (SelectedPointSource != null)
        {
            if (ShowAllPoints)
            {
                if (SelectedPointSource.GetComponent<ObjectPoints>().GetObjectUniqueVertices().Count > 0)
                {
                    for (int a = 0; a < SelectedPointSource.GetComponent<ObjectPoints>().GetObjectUniqueVertices().Count; a++) //unique points on the object mesh is shown
                    {

                        Gizmos.color = Color.red;
                        Gizmos.DrawSphere(SelectedPointSource.GetComponent<ObjectPoints>().GetObjectUniqueVertices()[a], 0.5f);
                    }

                }
            }
            if (ShowRandomPoint) //random point on the object is shown
            {
                Gizmos.color = Color.blue;
                Gizmos.DrawSphere(RandomPoint, 0.75f);
            }
        }
    }

Points are shown as gizmos on the screen.

You can find the project here in Assets/Points in 3D Objects folder. I am planning the next step of these posts on creating a maze using the points on the plane.

2 thoughts on “The Points On Unity’s 3D Primitive Objects And Finding Random Points

Leave a comment