Search code examples
c#unity-game-engineruntimefrustum

Conical Frustum in Unity3d


I`m helping my friend to make his study project, and for it we need a 3d frustum, with and ability to set the diameter of the upper round surface and the bottom one in runtime, could you please advice how can it be done? I am thinking of getting the array of edgex/vertices, that are connected with the center one on the top and on the bottom, and change their coords, maybe there is an easier way to do so?


Solution

  • For creating a cone frustum with dynamic sizes you can use the script from http://wiki.unity3d.com/index.php/ProceduralPrimitives#C.23_-_Cone

    Since you create the mesh once at beginning later you know exactly which vertices are the ones for the top and bottom plane so you can easily change them dynamically afterwards.

    Somewhat like e.g.

    public class DynamicConicalFrustum : MonoBehaviour
    {
        [SerializeField] private MeshFilter meshFilter;
        [SerializeField] private MeshCollider meshCollider;
    
        [Header("Settings")]
        [SerializeField] private float _height = 1f;
        [SerializeField] private float _bottomRadius = .25f;
        [SerializeField] private float _topRadius = .05f;
        [SerializeField] private int nbSides = 18;
    
        private Mesh mesh;
        const float _2pi = Mathf.PI * 2f;
        private Vector3[] vertices;
    
        private void Awake()
        {
            if (!meshFilter && !TryGetComponent<MeshFilter>(out meshFilter))
            {
                meshFilter = gameObject.AddComponent<MeshFilter>();
            }
    
            if(!GetComponent<MeshRenderer>())
            {
                var mr = gameObject.AddComponent<MeshRenderer>();
                mr.material = new Material(Shader.Find("Standard"));
            }
    
            if (!meshCollider)
                meshCollider = GetComponent<MeshCollider>();
    
            mesh = meshFilter.mesh;
    
            if (!mesh)
            {
                mesh = new Mesh();
            }
    
            meshFilter.mesh = mesh;
            if (meshCollider)
                meshCollider.sharedMesh = mesh;
    
            RecreateFrustum(_height,_bottomRadius,_topRadius);
        }
    
    #if UNITY_EDITOR
        private void OnValidate()
        {
            if (Application.isPlaying)
            {
                Awake();
            }
        }
    #endif
    
        public void RecreateFrustum(float height, float bottomRadius, float topRadius)
        {
            mesh.Clear();
    
            int nbVerticesCap = nbSides + 1;
            #region Vertices
    
            // bottom + top + sides
            vertices = new Vector3[nbVerticesCap + nbVerticesCap + nbSides  * 2 + 2];
    
            // Bottom cap
            vertices[0] = new Vector3(0f, 0f, 0f);
            for(var idx = 1; idx <= nbSides; idx++)
            {
                float rad = (float)(idx ) / nbSides * _2pi;
                vertices[idx ] = new Vector3(Mathf.Cos(rad) * bottomRadius, 0f, Mathf.Sin(rad) * bottomRadius);
            }
    
            // Top cap
            vertices[nbSides + 1] = new Vector3(0f, height, 0f);
            for(var idx = nbSides + 2; idx <= nbSides * 2 + 1; idx++)
            { 
                float rad = (float)(idx - nbSides - 1) / nbSides * _2pi;
                vertices[idx] = new Vector3(Mathf.Cos(rad) * topRadius, height, Mathf.Sin(rad) * topRadius);
            }
    
            // Sides
            int v = 0;
            for(var idx = nbSides * 2 + 2; idx <= vertices.Length - 4; idx+=2)
            { 
                float rad = (float)v / nbSides * _2pi;
                vertices[idx] = new Vector3(Mathf.Cos(rad) * topRadius, height, Mathf.Sin(rad) * topRadius);
                vertices[idx + 1] = new Vector3(Mathf.Cos(rad) * bottomRadius, 0, Mathf.Sin(rad) * bottomRadius);
                v++;
            }
            vertices[vertices.Length - 2] = vertices[nbSides * 2 + 2];
            vertices[vertices.Length - 1] = vertices[nbSides * 2 + 3];
            #endregion
    
            #region Triangles
            int nbTriangles = nbSides + nbSides + nbSides * 2;
            int[] triangles = new int[nbTriangles * 3 + 3];
    
            // Bottom cap
            int tri = 0;
            int i = 0;
            while (tri < nbSides - 1)
            {
                triangles[i] = 0;
                triangles[i + 1] = tri + 1;
                triangles[i + 2] = tri + 2;
                tri++;
                i += 3;
            }
            triangles[i] = 0;
            triangles[i + 1] = tri + 1;
            triangles[i + 2] = 1;
            tri++;
            i += 3;
    
            // Top cap
            //tri++;
            while (tri < nbSides * 2)
            {
                triangles[i] = tri + 2;
                triangles[i + 1] = tri + 1;
                triangles[i + 2] = nbVerticesCap;
                tri++;
                i += 3;
            }
    
            triangles[i] = nbVerticesCap + 1;
            triangles[i + 1] = tri + 1;
            triangles[i + 2] = nbVerticesCap;
            tri++;
            i += 3;
            tri++;
    
            // Sides
            while (tri <= nbTriangles)
            {
                triangles[i] = tri + 2;
                triangles[i + 1] = tri + 1;
                triangles[i + 2] = tri + 0;
                tri++;
                i += 3;
    
                triangles[i] = tri + 1;
                triangles[i + 1] = tri + 2;
                triangles[i + 2] = tri + 0;
                tri++;
                i += 3;
            }
            #endregion
    
            mesh.vertices = vertices;
            mesh.triangles = triangles;
    
            mesh.RecalculateBounds();
            mesh.RecalculateNormals();
            mesh.RecalculateTangents();
            mesh.Optimize();
        }
    }
    

    enter image description here

    It's of course not really optimized currently but I hope you can keep going from there ;) E.g. that method creates extra vertices for the sides .. one could re-use the ones from the top and bottom cap since they match positions anyway. Further you could of course also just update the according vertices when the height and radius change, you wouldn't need to recreate the entire mesh every time since the triangles stay the same, only the vertices change positions.