static int ComputeVertexCountForProperties(int pointCount, int edgeCount, TubeRenderer.NormalMode normalMode, TubeRenderer.CapMode capMode)
    {
        int targetVertexCount = 0;

        switch (normalMode)
        {
        case TubeRenderer.NormalMode.Smooth: targetVertexCount = pointCount * (edgeCount + 1); break;

        case TubeRenderer.NormalMode.Hard: targetVertexCount = (pointCount - 1) * edgeCount * 4; break;

        case TubeRenderer.NormalMode.HardEdges: targetVertexCount = pointCount * 2 * edgeCount; break;
        }
        if (capMode == TubeRenderer.CapMode.Both)
        {
            targetVertexCount += ((edgeCount + 1) + 1) * 2;
        }
        else if (capMode == TubeRenderer.CapMode.Begin || capMode == TubeRenderer.CapMode.End)
        {
            targetVertexCount += (edgeCount + 1) + 1;
        }

        return(targetVertexCount);
    }
    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        isDirty = false;

        // Stats
        EditorGUILayout.LabelField("Stats: ", tube.points.Length.ToString() + " points, " + tube.mesh.vertexCount.ToString() + " verts.");

        // Points
        _pointsFoldout.boolValue = EditorGUILayout.Foldout(_pointsFoldout.boolValue, "Points");
        if (_pointsFoldout.boolValue)
        {
            EditorGUI.indentLevel++;

            EditorGUI.BeginChangeCheck();
            int newCount = EditorGUILayout.IntField("Count", _points.arraySize);
            if (EditorGUI.EndChangeCheck() && _points.arraySize != newCount)
            {
                // Check against Unity mesh limit.
                int targetVertexCount = ComputeVertexCountForProperties(newCount, _edgeCount.intValue, (TubeRenderer.NormalMode)_normalMode.enumValueIndex, (TubeRenderer.CapMode)_caps.enumValueIndex);
                if (targetVertexCount > meshVertexCountLimit)
                {
                    Debug.LogWarning(messageHeader + "Points change for " + tube.name + " was ignored. You are exceeding Unity's 65000 vertex limit.\n");
                }
                else
                {
                    // Update point count.
                    int lastArraySize = _points.arraySize;
                    _points.arraySize = newCount;
                    if (_points.arraySize > lastArraySize && lastArraySize > 0)
                    {
                        // Super smart extension
                        Vector3 position = _points.GetArrayElementAtIndex(lastArraySize - 1).vector3Value;
                        Vector3 step     = position - _points.GetArrayElementAtIndex(lastArraySize - 2).vector3Value;
                        for (int p = lastArraySize; p < _points.arraySize; p++)
                        {
                            position += step;
                            _points.GetArrayElementAtIndex(p).vector3Value = position;
                        }
                    }
                    if (_radiuses.arraySize != 0)
                    {
                        _radiuses.arraySize = newCount;
                    }
                    if (_pointColors.arraySize != 0)
                    {
                        _pointColors.arraySize = newCount;
                    }
                    isDirty = true;
                }
            }

            EditorGUI.indentLevel++;

            if (newCount < 100)
            {
                EditorGUI.BeginChangeCheck();
                for (int i = 0; i < _points.arraySize; i++)
                {
                    EditorGUILayout.PropertyField(_points.GetArrayElementAtIndex(i));
                }
                if (EditorGUI.EndChangeCheck())
                {
                    isDirty = true;
                }
            }
            else
            {
                EditorGUILayout.HelpBox("Too many points to display in inspector.", MessageType.Warning);
            }

            EditorGUI.indentLevel--;

            EditorGUI.indentLevel--;
        }

        // Radius
        if (_radiuses.arraySize != 0)
        {
            GUI.color = disabledColor;
        }
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.Slider(_radius, 0.01f, 10);
        if (EditorGUI.EndChangeCheck())
        {
            isDirty             = true;
            _radiuses.arraySize = 0;
        }
        GUI.color = Color.white;

        // Radiuses
        if (_radiuses.arraySize == 0)
        {
            GUI.color = disabledColor;
        }
        _radiusesFoldout.boolValue = EditorGUILayout.Foldout(_radiusesFoldout.boolValue, "Radiuses");
        if (_radiusesFoldout.boolValue)
        {
            EditorGUI.indentLevel++;

            EditorGUI.BeginChangeCheck();
            bool usingArray  = _radiuses.arraySize != 0;
            bool newUseArray = EditorGUILayout.Toggle("Use", usingArray);
            if (EditorGUI.EndChangeCheck() && newUseArray != usingArray)
            {
                _radiuses.arraySize = newUseArray ? _points.arraySize : 0;
                for (int i = 0; i < _radiuses.arraySize; i++)
                {
                    _radiuses.GetArrayElementAtIndex(i).floatValue = _radius.floatValue;
                }
                isDirty = true;
            }

            EditorGUI.indentLevel++;

            if (_points.arraySize < 100)
            {
                EditorGUI.BeginChangeCheck();
                for (int i = 0; i < _radiuses.arraySize; i++)
                {
                    EditorGUILayout.PropertyField(_radiuses.GetArrayElementAtIndex(i));
                }
                if (EditorGUI.EndChangeCheck())
                {
                    isDirty = true;
                }
            }
            else
            {
                EditorGUILayout.HelpBox("Too many points to display in inspector.", MessageType.Warning);
            }
            EditorGUI.indentLevel--;

            EditorGUI.indentLevel--;
        }
        GUI.color = Color.white;


        // Colors.
        if (_pointColors.arraySize == 0)
        {
            GUI.color = disabledColor;
        }
        _colorsFoldout.boolValue = EditorGUILayout.Foldout(_colorsFoldout.boolValue, "Colors (vertex colors)");
        if (_colorsFoldout.boolValue)
        {
            EditorGUI.indentLevel++;

            EditorGUI.BeginChangeCheck();
            bool usingArray  = _pointColors.arraySize != 0;
            bool newUseArray = EditorGUILayout.Toggle("Use", usingArray);
            if (EditorGUI.EndChangeCheck() && newUseArray != usingArray)
            {
                _pointColors.arraySize = newUseArray ? _points.arraySize : 0;
                for (int i = 0; i < _pointColors.arraySize; i++)
                {
                    _pointColors.GetArrayElementAtIndex(i).colorValue = Color.white;
                }
                isDirty = true;
            }

            EditorGUI.indentLevel++;
            EditorGUI.BeginChangeCheck();
            for (int i = 0; i < _pointColors.arraySize; i++)
            {
                EditorGUILayout.PropertyField(_pointColors.GetArrayElementAtIndex(i));
            }
            if (EditorGUI.EndChangeCheck())
            {
                isDirty = true;
            }
            EditorGUI.indentLevel--;

            EditorGUI.indentLevel--;
        }
        GUI.color = Color.white;

        // Tangents.
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.PropertyField(_calculateTangents);
        if (EditorGUI.EndChangeCheck())
        {
            isDirty = true;
        }

        // Invert mesh.
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.PropertyField(_invertMesh);
        if (EditorGUI.EndChangeCheck())
        {
            isDirty = true;
        }

        // Normal mode.
        EditorGUILayout.BeginHorizontal();
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.PrefixLabel("Normal Mode");
        TubeRenderer.NormalMode newNormalMode = (TubeRenderer.NormalMode)EditorGUILayout.EnumPopup((TubeRenderer.NormalMode)_normalMode.enumValueIndex);
        if (EditorGUI.EndChangeCheck())
        {
            // Check against Unity mesh limit.
            int targetVertexCount = ComputeVertexCountForProperties(_points.arraySize, _edgeCount.intValue, newNormalMode, (TubeRenderer.CapMode)_caps.enumValueIndex);
            if (targetVertexCount > meshVertexCountLimit)
            {
                Debug.LogWarning(messageHeader + "Normal mode change for " + tube.name + " was ignored. You are exceeding Unity's 65000 vertex limit.\n");
            }
            else
            {
                // Update.
                _normalMode.enumValueIndex = (int)newNormalMode;
                isDirty = true;
            }
        }
        EditorGUILayout.EndHorizontal();

        // Caps.
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Caps");
        TubeRenderer.CapMode newCapMode = (TubeRenderer.CapMode)EditorGUILayout.EnumPopup((TubeRenderer.CapMode)_caps.enumValueIndex);
        if (EditorGUI.EndChangeCheck())
        {
            // Check against Unity mesh limit.
            int targetVertexCount = ComputeVertexCountForProperties(_points.arraySize, _edgeCount.intValue, (TubeRenderer.NormalMode)_normalMode.intValue, newCapMode);
            if (targetVertexCount > meshVertexCountLimit)
            {
                Debug.LogWarning(messageHeader + "Caps change for " + tube.name + " was ignored. You are exceeding Unity's 65000 vertex limit.\n");
            }
            else
            {
                // Update.
                _caps.enumValueIndex = (int)newCapMode;
                isDirty = true;
            }
        }
        EditorGUILayout.EndHorizontal();

        // Edge count.
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Edge Count");
        int newEdgeCount = EditorGUILayout.IntSlider(_edgeCount.intValue, 3, 64);

        if (EditorGUI.EndChangeCheck())
        {
            // Check against Unity mesh limit.
            int targetVertexCount = ComputeVertexCountForProperties(_points.arraySize, newEdgeCount, (TubeRenderer.NormalMode)_normalMode.enumValueIndex, (TubeRenderer.CapMode)_caps.enumValueIndex);
            if (targetVertexCount > meshVertexCountLimit)
            {
                Debug.LogWarning(messageHeader + "Edge count change for " + tube.name + " was ignored. You are exceeding Unity's 65000 vertex limit.\n");
            }
            else
            {
                // Update.
                _edgeCount.intValue = newEdgeCount;
                isDirty             = true;
            }
        }
        ;
        EditorGUILayout.EndHorizontal();

        // Forward angle offset.
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.Slider(_forwardAngleOffset, -180f, 180);
        if (EditorGUI.EndChangeCheck())
        {
            isDirty = true;
        }

        // UV mapping.
        _uvFoldout.boolValue = EditorGUILayout.Foldout(_uvFoldout.boolValue, "UV Mapping");
        if (_uvFoldout.boolValue)
        {
            EditorGUI.indentLevel++;

            // UV rect
            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(_uvRect);
            if (EditorGUI.EndChangeCheck())
            {
                isDirty = true;
            }

            if (_caps.enumValueIndex != (int)TubeRenderer.CapMode.None)
            {
                // UV rect
                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(_uvRectCap);
                if (EditorGUI.EndChangeCheck())
                {
                    isDirty = true;
                }

                // UV rect cap mirrored
                if (_caps.enumValueIndex == (int)TubeRenderer.CapMode.Both || _caps.enumValueIndex == (int)TubeRenderer.CapMode.End)
                {
                    EditorGUI.BeginChangeCheck();
                    EditorGUILayout.PropertyField(_uvRectCapEndMirrored);
                    if (EditorGUI.EndChangeCheck())
                    {
                        isDirty = true;
                    }
                }
            }

            EditorGUI.indentLevel--;
        }

        // Mesh gizmos.
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.PropertyField(_showMeshGizmos);
        if (EditorGUI.EndChangeCheck())
        {
            isDirty = true;
        }

        // Mesh gizmos length.
        if (_showMeshGizmos.boolValue)
        {
            EditorGUI.BeginChangeCheck();
            EditorGUILayout.Slider(_meshGizmoLength, 0.01f, 1f);
            if (EditorGUI.EndChangeCheck())
            {
                isDirty = true;
            }
        }

        // Mesh gizmos.
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.PropertyField(_showRotationGizmos);
        if (EditorGUI.EndChangeCheck())
        {
            isDirty = true;
        }

        // Mesh gizmos length.
        if (_showRotationGizmos.boolValue)
        {
            EditorGUI.BeginChangeCheck();
            EditorGUILayout.Slider(_rotationGizmoLength, 0.01f, 1f);
            if (EditorGUI.EndChangeCheck())
            {
                isDirty = true;
            }
        }

        // Update and serialize the tube.
        serializedObject.ApplyModifiedProperties();

        // Request that the tube gets an update call.
        if (isDirty)
        {
            EditorUtility.SetDirty(tube);
        }
    }