Bezier Curve Mesh Filter Placement

Hi Everyone,

This is my first post here so please let me know if there is anything I can provide to help answer my question. I am working on creating a script to place a conveyor belt object along a Bezier curve. I am able to create the curve and update it as the end point is moved around. However, when I have started to try and introduce the mesh deformation to the equation, I am running into issues. I followed a tutorial on updating the vertices and triangles for the mesh. However, I am getting wonky results (see image below). Are there any resources that someone could point me to? Or any suggestion on the code below. I am also self taught and fairly new to coding so any suggestions/tips on keeping my code cleaner/more efficient would be greatly appreciated!

Thanks,

using UnityEngine;

public class ConveyorPlacement : MonoBehaviour
{
    //Prefab for pole object
    [SerializeField] private GameObject conveyorPole;

    //Prefab for belt object 
    [SerializeField] private GameObject beltMesh;

    //Width of belt
    [SerializeField] private float beltWidth = 1;

    //Number of points between starting pole and ending pole
    private int numberOfPoints = 20;

    //bools to determine logic based on step in process
    private bool isPlacing = false;
    private bool startPlaced = false;
    private bool endPlaced = false;


    private GameObject startingPole;
    private GameObject endingPole;
    private GameObject belt;
    private Mesh mesh;

    //parent object to hold each pole and the belt object
    private GameObject conveyorBelt;

    //Speed to rotate the object when placing
    private readonly float rotationSpeed = 45f;

    //points between starting and ending pole along Bezier curve
    private Vector3[] points;

    private float placementDistance = 5f;

    private readonly float minDistance = 2f;
    private readonly float maxDistance = 10f;
    private readonly float scrollSensitivity = 1f;

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (!isPlacing)
            {
                StartPlacing();
            }
            else if (!startPlaced)
            {
                PlaceStart();
                StartPlacingEnd();
            }
            else
            {
                PlaceEnd();
            }
        }

        if (isPlacing && !startPlaced)
        {
            UpdateStart();
        }
        else if (isPlacing && !endPlaced)
        {
            UpdateEnd();
        }

        if (endPlaced)
        {
            endPlaced = false;
        }
    }

    private void StartPlacing()
    {
        //Logic to start placing the starting pole
        isPlacing = true;

        //Create empty game object to hold pieces of conveyor belt
        conveyorBelt = new GameObject("Conveyor Belt");
        startingPole = Instantiate(conveyorPole, conveyorBelt.transform);
        placementDistance = 5f;
        startingPole.transform.position = DetermineLocation();
    }

    private void StartPlacingEnd()
    {
        //Logic to start placing the ending pole
        endingPole = Instantiate(conveyorPole, conveyorBelt.transform);
        placementDistance = 5f;
        endingPole.transform.position = DetermineLocation();


        //Create the belt object and set its position to the starting pole
        belt = Instantiate(beltMesh, conveyorBelt.transform);
        belt.transform.position = startingPole.transform.position;
        mesh = belt.GetComponent<MeshFilter>().mesh;

        //Update the points between the starting and ending pole once ending pole is created
        points = FindPoints(startingPole.transform, endingPole.transform, numberOfPoints);

        //Start udpating the mesh once ending pole is created
        CreateMesh(points, mesh);
    }

    private void UpdateStart()
    {
        startingPole.transform.position = DetermineLocation();
        RotateObject(startingPole.transform);
    }

    private void UpdateEnd()
    {
        endingPole.transform.position = DetermineLocation();
        RotateObject(endingPole.transform);

        //update the points between the starting and ending pole as the ending pole is moved around
        points = FindPoints(startingPole.transform, endingPole.transform, numberOfPoints);

        //update the mesh based on the updated points 
        CreateMesh(points, mesh);
    }

    private void PlaceStart()
    {
        startPlaced = true;
    }

    private void PlaceEnd()
    {
        //Clear all of the variabels that should be reset after ending point is placed 
        endPlaced = true;
        isPlacing = false;
        startingPole = null;
        points = null;
        endingPole = null;
        belt = null;
        mesh = null;
        conveyorBelt = null;
        startPlaced = false;
    }

    //Update the location of the object that is currently being placed 
    private Vector3 DetermineLocation()
    {
        Vector3 screenCenter = new(Screen.width / 2, Screen.height / 2, 0f);

        UpdateDistanceFromPlayer();

        return Camera.main.ScreenToWorldPoint(new Vector3(screenCenter.x, screenCenter.y, placementDistance));
    }

    //Allow player to use scroll wheel to place object closer or further away
    private void UpdateDistanceFromPlayer()
    {
        float scrollInput = Input.GetAxis("Mouse ScrollWheel");

        placementDistance += scrollInput * scrollSensitivity;

        placementDistance = Mathf.Clamp(placementDistance, minDistance, maxDistance);
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;

        if (startingPole)
        {
            Vector3 objectPosition = startingPole.transform.position;
            Vector3 objectForward = startingPole.transform.forward;

            Gizmos.DrawLine(objectPosition, objectPosition + objectForward * 2f);

            Gizmos.color = Color.green;
            Gizmos.DrawSphere(startingPole.transform.position, .1f);
        }

        if (endingPole)
        {
            Vector3 objectPosition = endingPole.transform.position;
            Vector3 objectForward = endingPole.transform.forward;

            Gizmos.DrawLine(objectPosition, objectPosition + objectForward * 2f);

            Gizmos.color = Color.green;
            Gizmos.DrawSphere(endingPole.transform.position, 0.1f);
        }

        if (startingPole && endingPole)
        {
            Gizmos.color = Color.blue;

            foreach (Vector3 point in points)
            {
                Gizmos.DrawSphere(point, 0.05f);
            }
        }
    }

    private void RotateObject(Transform item)
    {
        if (Input.GetKey(KeyCode.Q))
        {
            item.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
        }
        if (Input.GetKey(KeyCode.E))
        {
            item.Rotate(Vector3.up, -rotationSpeed * Time.deltaTime);
        }
    }

    private Vector3[] FindPoints(Transform start, Transform end, int numberOfPoints)
    {
        float distance = Vector3.Distance(start.position, end.position) / 2;

        Vector3 p0 = start.position;
        Vector3 p1 = start.position + start.forward * distance;
        Vector3 p2 = end.position - end.forward * distance;
        Vector3 p3 = end.position;

        Vector3[] newPoints = new Vector3[numberOfPoints];

        Debug.Log(newPoints);

        newPoints[0] = p0;
        newPoints[newPoints.Length - 1] = p3;

        for (int i = 1; i < newPoints.Length - 2; i++)
        {
            newPoints[i] = Bezier.CubicCurve(p0, p1, p2, p3, (float)i / (numberOfPoints - 2));
        }

        return newPoints;
    }

    private void CreateMesh(Vector3[] points, Mesh mesh)
    {
        //Create Vector3[] with length twice that of the number of points
        // to hold the vertices (Vertices = 2(numberOfPoints))
        Vector3[] verts = new Vector3[points.Length * 2];

        //Create int[] with length 2n-1 to hold triangles
        int[] tris = new int[2 * (points.Length - 1) * 3];
        int vertexIndex = 0;
        int triIndex = 0;

        for (int i = 0; i < points.Length; i++)
        {
            //Set the intial value of forward to 0
            Vector3 forward = Vector3.zero;
            if (i < points.Length - 1)
            {
                //If it is not the last point, add the distance between the current point
                //and the following point
                forward += points[i + 1] - points[i];
            }
            if (i > 0)
            {
                //if it is not the first point, add the distance between the current point
                //and the previous point
                forward += points[i] - points[i - 1];
            }

            //Get the average of the forward; 
            forward.Normalize();

            //calculate the position to place the vertices
            Vector3 left = new Vector3(-forward.y, points[i].y, forward.x);

            verts[vertexIndex] = points[i] + 0.5f * beltWidth * left;
            verts[vertexIndex + 1] = points[i] - 0.5f * beltWidth * left;

            //Update the triangles array to include appropriate vertices
            if (i < points.Length - 1)
            {
                tris[triIndex] = vertexIndex;
                tris[triIndex + 1] = vertexIndex + 2;
                tris[triIndex + 2] = vertexIndex + 1;

                tris[triIndex + 3] = vertexIndex + 1;
                tris[triIndex + 4] = vertexIndex + 2;
                tris[triIndex + 5] = vertexIndex + 3;
            }

            vertexIndex += 2;
            triIndex += 6;
        }

        //assign the vertices and triangles to the mesh and recalculate bounds/normals
        mesh.vertices = verts;
        mesh.triangles = tris;

        mesh.RecalculateBounds();
        mesh.RecalculateNormals();
    }
}


Just glancing quickly through your code the one thing that pops out is that I think you are taking world positions out of the Bezier curves, then using them to produce mesh vertices, which are always in local object space.

Another possibility is if your source mesh came in as an FBX, it might have a built-in rotation of (-90,0,0) or something, including axis swaps, so that might confound what you think the code above should do.

Also check that all transforms are identity rotation if possible, or if not then consider how it impacts all children below the rotated Transform. It gets complicated quickly. :slight_smile:

Thank you! I updated the vertices to use local space with the Transform.InverseTransformPoint() and then updated some of my math that was incorrect and I think I have it working now! I appreciate your help!

Update code below for anyone interested

using UnityEngine;

public class ConveyorPlacement : MonoBehaviour
{
    //Prefab for pole object
    [SerializeField] private GameObject conveyorPole;

    //Prefab for belt object 
    [SerializeField] private GameObject beltMesh;

    //Width of belt
    [SerializeField] private float beltWidth = 1;

    //Number of points between starting pole and ending pole
    private int numberOfPoints = 20;

    //bools to determine logic based on step in process
    private bool isPlacing = false;
    private bool startPlaced = false;
    private bool endPlaced = false;


    private GameObject startingPole;
    private GameObject endingPole;
    private GameObject belt;
    private Mesh mesh;

    //parent object to hold each pole and the belt object
    private GameObject conveyorBelt;

    //Speed to rotate the object when placing
    private readonly float rotationSpeed = 45f;

    //points between starting and ending pole along Bezier curve
    private Vector3[] points;

    private float placementDistance = 5f;

    private readonly float minDistance = 2f;
    private readonly float maxDistance = 10f;
    private readonly float scrollSensitivity = 1f;

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (!isPlacing)
            {
                StartPlacing();
            }
            else if (!startPlaced)
            {
                PlaceStart();
                StartPlacingEnd();
            }
            else
            {
                PlaceEnd();
            }
        }

        if (isPlacing && !startPlaced)
        {
            UpdateStart();
        }
        else if (isPlacing && !endPlaced)
        {
            UpdateEnd();
        }

        if (endPlaced)
        {
            endPlaced = false;
        }
    }

    private void StartPlacing()
    {
        //Logic to start placing the starting pole
        isPlacing = true;

        //Create empty game object to hold pieces of conveyor belt
        conveyorBelt = new GameObject("Conveyor Belt");
        startingPole = Instantiate(conveyorPole, conveyorBelt.transform);
        placementDistance = 5f;
        startingPole.transform.position = DetermineLocation();
    }

    private void StartPlacingEnd()
    {
        //Logic to start placing the ending pole
        endingPole = Instantiate(conveyorPole, conveyorBelt.transform);
        placementDistance = 5f;
        endingPole.transform.position = DetermineLocation();


        //Create the belt object and set its position to the starting pole
        belt = Instantiate(beltMesh, conveyorBelt.transform);
        belt.transform.position = startingPole.transform.position;
        mesh = belt.GetComponent<MeshFilter>().mesh;

        //Update the points between the starting and ending pole once ending pole is created
        points = FindPoints(startingPole.transform, endingPole.transform, numberOfPoints);

        //Start udpating the mesh once ending pole is created
        //CreateMesh(points, mesh);
    }

    private void UpdateStart()
    {
        startingPole.transform.position = DetermineLocation();
        RotateObject(startingPole.transform);
    }

    private void UpdateEnd()
    {
        endingPole.transform.position = DetermineLocation();
        RotateObject(endingPole.transform);

        //update the points between the starting and ending pole as the ending pole is moved around
        points = FindPoints(startingPole.transform, endingPole.transform, numberOfPoints);

        //update the mesh based on the updated points 
        //CreateMesh(points, mesh);
    }

    private void PlaceStart()
    {
        startPlaced = true;
    }

    private void PlaceEnd()
    {
        //Clear all of the variabels that should be reset after ending point is placed 
        endPlaced = true;
        isPlacing = false;
        startingPole = null;
        points = null;
        endingPole = null;
        belt = null;
        mesh = null;
        conveyorBelt = null;
        startPlaced = false;
    }

    //Update the location of the object that is currently being placed 
    private Vector3 DetermineLocation()
    {
        Vector3 screenCenter = new(Screen.width / 2, Screen.height / 2, 0f);

        UpdateDistanceFromPlayer();

        return Camera.main.ScreenToWorldPoint(new Vector3(screenCenter.x, screenCenter.y, placementDistance));
    }

    //Allow player to use scroll wheel to place object closer or further away
    private void UpdateDistanceFromPlayer()
    {
        float scrollInput = Input.GetAxis("Mouse ScrollWheel");

        placementDistance += scrollInput * scrollSensitivity;

        placementDistance = Mathf.Clamp(placementDistance, minDistance, maxDistance);
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;

        if (startingPole)
        {
            Vector3 objectPosition = startingPole.transform.position;
            Vector3 objectForward = startingPole.transform.forward;

            Gizmos.DrawLine(objectPosition, objectPosition + objectForward * 2f);

            Gizmos.color = Color.green;
            Gizmos.DrawSphere(startingPole.transform.position, .1f);
        }

        if (endingPole)
        {
            Vector3 objectPosition = endingPole.transform.position;
            Vector3 objectForward = endingPole.transform.forward;

            Gizmos.DrawLine(objectPosition, objectPosition + objectForward * 2f);

            Gizmos.color = Color.green;
            Gizmos.DrawSphere(endingPole.transform.position, 0.1f);
        }

        if (startingPole && endingPole)
        {
            Gizmos.color = Color.blue;

            foreach (Vector3 point in points)
            {
                Gizmos.DrawSphere(point, 0.05f);
            }

            CreateMesh(points, mesh);
        }
    }

    private void RotateObject(Transform item)
    {
        if (Input.GetKey(KeyCode.Q))
        {
            item.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
        }
        if (Input.GetKey(KeyCode.E))
        {
            item.Rotate(Vector3.up, -rotationSpeed * Time.deltaTime);
        }
    }

    private Vector3[] FindPoints(Transform start, Transform end, int numberOfPoints)
    {
        float distance = Vector3.Distance(start.position, end.position) / 2;

        Vector3 p0 = start.position;
        Vector3 p1 = start.position + start.forward * distance;
        Vector3 p2 = end.position - end.forward * distance;
        Vector3 p3 = end.position;

        Vector3[] newPoints = new Vector3[numberOfPoints];

        Debug.Log(newPoints);

        newPoints[0] = p0;
        newPoints[newPoints.Length - 1] = p3;

        for (int i = 1; i < newPoints.Length - 1; i++)
        {
            newPoints[i] = Bezier.CubicCurve(p0, p1, p2, p3, (float)i / (numberOfPoints - 2));
        }

        return newPoints;
    }

    private void CreateMesh(Vector3[] points, Mesh mesh)
    {
        //Create Vector3[] with length twice that of the number of points
        // to hold the vertices (Vertices = 2(numberOfPoints))
        Vector3[] verts = new Vector3[points.Length * 2];

        //Create int[] with length 2n-1 to hold triangles
        int[] tris = new int[2 * (points.Length - 1) * 3];
        int vertexIndex = 0;
        int triIndex = 0;

        for (int i = 0; i < points.Length; i++)
        {
            //Set the intial value of forward to 0
            Vector3 forward = Vector3.zero;
            if (i < points.Length - 1)
            {
                //If it is not the last point, add the distance between the current point
                //and the following point
                forward += points[i + 1] - points[i];
            }
            if (i > 0)
            {
                //if it is not the first point, add the distance between the current point
                //and the previous point
                forward += points[i] - points[i - 1];
            }

            //Get the average of the forward; 
            forward.Normalize();

            //calculate the position to place the vertices
            Vector3 left = new Vector3(-forward.z, 0, forward.x);

            verts[vertexIndex] = points[i] + 0.5f * beltWidth * left;
            verts[vertexIndex + 1] = points[i] - 0.5f * beltWidth * left;

            //Update the triangles array to include appropriate vertices
            if (i < points.Length - 1)
            {
                tris[triIndex] = vertexIndex;
                tris[triIndex + 1] = vertexIndex + 2;
                tris[triIndex + 2] = vertexIndex + 1;

                tris[triIndex + 3] = vertexIndex + 1;
                tris[triIndex + 4] = vertexIndex + 2;
                tris[triIndex + 5] = vertexIndex + 3;
            }

            vertexIndex += 2;
            triIndex += 6;
        }

        for (int i = 0; i < verts.Length; i++)
        {
            verts[i] = belt.transform.InverseTransformPoint(verts[i]);
            DrawVertex(verts[i]);
        }

        //assign the vertices and triangles to the mesh and recalculate bounds/normals
        mesh.vertices = verts;
        mesh.triangles = tris;

        mesh.RecalculateBounds();
        mesh.RecalculateNormals();
    }

    private void DrawVertex(Vector3 vert)
    {
        Gizmos.color = Color.black;

        Gizmos.DrawSphere(vert, 0.02f);
    }
}
1 Like