private bool AddOuterUpBlendShape(string targetShapeName, float scaleAsFractionOfFace, bool keepLeft, bool keepRight, int onlyIncludeThisSubmesh)
    {
        if (mesh.GetBlendShapeIndex(targetShapeName) >= 0)
        {
            // Blend shape already exists. Don't try to add it again.
            return(false);
        }

        // The mesh extent is from center of face to edge, so multiply by two for total face size.
        float maxYOffset = scaleAsFractionOfFace * 2.0f * mesh.bounds.extents.magnitude;

        Vector3[] deltaVertices = new Vector3[mesh.vertexCount];
        Vector3[] deltaNormals  = new Vector3[mesh.vertexCount];
        Vector3[] deltaTangents = new Vector3[mesh.vertexCount];

        Vector3[] verteces = mesh.vertices;
        Bounds    bounds   = mesh.bounds;
        float     minX     = bounds.center.x - bounds.extents.x;
        float     maxX     = bounds.center.x + bounds.extents.x;

        UnityEngine.Rendering.SubMeshDescriptor smd = mesh.GetSubMesh(onlyIncludeThisSubmesh);

        for (int i = 0; i < mesh.vertexCount; i++)
        {
            Vector3 v       = verteces[i];
            float   yOffset = 0.0f;

            if (i >= smd.firstVertex && i < smd.firstVertex + smd.vertexCount)
            {
                if ((v.x <= 0.0f && keepLeft) || (v.x >= 0.0f && keepRight))
                {
                    float localScale = v.x / (maxX / 2.0f);
                    if (localScale < 0.0f)
                    {
                        localScale = -localScale;
                    }
                    if (localScale > 1.0f)
                    {
                        localScale = 1.0f;
                    }

                    yOffset = localScale * maxYOffset;
                }
            }

            deltaVertices[i] = new Vector3(0, yOffset, 0);
            deltaNormals[i]  = Vector3.zero;
            deltaTangents[i] = Vector3.zero;
        }

        float frameWeight = 100.0f;

        mesh.AddBlendShapeFrame(targetShapeName, frameWeight, deltaVertices, deltaNormals, deltaTangents);

        return(true);
    }
    // Find some key points on the face (center of eyes etc)
    // These don't have to be exact - they just help normalize the source mesh to the target mesh a bit as eyes, nose, mouth can be different sizes.
    private KeyPoints FindKeyPoints(Mesh mesh)
    {
        // TODO: This assumes sub meshes are at certain places - if VRoid changes, these assumptions may become incorrect.

        KeyPoints kp = new KeyPoints();

        // Just pick any point in mesh as a relative value for Z so Z does not dominate below because the mouth is further forward in the head.
        float baseZ = mesh.vertices[0].z;

        // Assume subMesh 0 is the mouth. Find forward left and forward right most points as edges of mouth.
        UnityEngine.Rendering.SubMeshDescriptor mouthSmd = mesh.GetSubMesh(SM_MOUTH);
        for (int i = mouthSmd.firstVertex; i < mouthSmd.firstVertex + mouthSmd.vertexCount; i++)
        {
            Vector3 v = mesh.vertices[i];

            // Left is +ve X. Forward is +ve Z. We want front left, so add the -X and Z offsets.
            if ((v.x + (v.z - baseZ) / 2.0f) > (kp.leftEdgeOfMouth.x + (kp.leftEdgeOfMouth.z - baseZ) / 2.0f))
            {
                kp.leftEdgeOfMouth = v;
            }

            // Right is -ve X. Forward is +ve Z. We want front right, so add the X and Z offsets.
            if ((-v.x + (v.z - baseZ) / 2.0f) > (-kp.rightEdgeOfMouth.x + (kp.rightEdgeOfMouth.z - baseZ) / 2.0f))
            {
                kp.rightEdgeOfMouth = v;
            }
        }

        // Just approximate the eye positions.
        // TODO: Could do a more accurate estimation by perhaps taking average of all vertices with +ve and -ve X values.
        UnityEngine.Rendering.SubMeshDescriptor eyeSmd = mesh.GetSubMesh(SM_EYEWHITES);
        kp.centerOfLeftEye  = eyeSmd.bounds.center + new Vector3(eyeSmd.bounds.extents.x * 0.75f, 0, 0);
        kp.centerOfRightEye = eyeSmd.bounds.center - new Vector3(eyeSmd.bounds.extents.x * 0.75f, 0, 0);

        return(kp);
    }
    private static SubMeshInfo[] LocateSubMeshes(Mesh mesh)
    {
        var subMeshes = new SubMeshInfo[9];

        // For now this is a bit hacky - can improve over time if needed.
        // If VRoid character has 9 meshes, then the ears have been merged into the face already.
        // If 10 meshes, index 7 and 8 are face and ears - lets merge them together.
        // Some tools merge them, so let's work with those tools as well.
        int j = 0;

        for (int i = 0; i < mesh.subMeshCount; i++)
        {
            UnityEngine.Rendering.SubMeshDescriptor smd = mesh.GetSubMesh(i);

            SubMeshInfo smi = new SubMeshInfo();
            smi.name       = m_subMeshNames[j];
            smi.startIndex = smd.firstVertex; // Or is it indexStart?
            smi.endIndex   = smd.firstVertex + smd.vertexCount;
            smi.bounds     = smd.bounds;

            // HACK: If size 10, do some magic at index 7 (face) to include following submesh (ears)
            // They share the same material and some tools merge them.
            // (If VRoid changes the order of subMeshes this will stuff things up big time!)
            if (i == 7 && mesh.subMeshCount == 10)
            {
                i++;
                smd = mesh.GetSubMesh(i);
                smi.bounds.Encapsulate(smd.bounds);
                smi.endIndex = smd.firstVertex + smd.vertexCount;
            }

            subMeshes[j++] = smi;
        }

        return(subMeshes);
    }
    private void AdjustBlendShape(
        Vector3[] deltaVertices,
        Vector3[] deltaNormals,
        Vector3[] deltaTangents,
        float magnitudeScale,
        float yScale,
        bool keepLeft,
        bool keepRight,
        bool keepUpward,
        bool keepDownward,
        ScaleNormalization scaleNormalization,
        int onlyIncludeThisSubmesh,
        int onlyExcludeThisSubmesh)
    {
        Vector3[] vertices = mesh.vertices;
        Bounds    bounds   = mesh.bounds;
        float     minX     = bounds.center.x - bounds.extents.x;
        float     maxX     = bounds.center.x + bounds.extents.x;

        for (int i = 0; i < mesh.vertexCount; i++)
        {
            Vector3 v = vertices[i];
            Debug.Log("v=" + V3ToString(v) + "  dv=" + V3ToString(deltaVertices[i]));

            bool zeroIt = false;

            if (v.x > 0.0f && !keepRight)
            {
                zeroIt = true;
                Debug.Log("ZA");
            }

            if (v.x < 0.0f && !keepLeft)
            {
                zeroIt = true;
                Debug.Log("ZB");
            }

            if (deltaVertices[i].y > 0.0f && !keepUpward)
            {
                zeroIt = true;
                Debug.Log("ZC");
            }

            if (deltaVertices[i].y < 0.0f && !keepDownward)
            {
                zeroIt = true;
                Debug.Log("ZD");
            }

            if (zeroIt)
            {
                deltaVertices[i] = Vector3.zero;
                deltaNormals[i]  = Vector3.zero;
                deltaTangents[i] = Vector3.zero;
            }
            else
            {
                Debug.Log("KEEP");

                // Adjust the scaling factor based on position on face.
                float scaleFactor = magnitudeScale;
                switch (scaleNormalization)
                {
                case ScaleNormalization.LeftToRight:
                {
                    float localScale = (v.x - minX) / (maxX - minX);
                    Debug.Log("1.v.x=" + v.x + ", minX=" + minX + ", maxX=" + maxX + ", lscale=" + localScale);
                    if (localScale < 0.0f)
                    {
                        localScale = 0.0f;
                    }
                    if (localScale > 1.0f)
                    {
                        localScale = 1.0f;
                    }
                    scaleFactor *= localScale;
                    break;
                }

                case ScaleNormalization.RightToLeft:
                {
                    float localScale = (v.x - minX) / (maxX - minX);
                    Debug.Log("2.v.x=" + v.x + ", minX=" + minX + ", maxX=" + maxX + ", lscale=" + localScale);
                    if (localScale < 0.0f)
                    {
                        localScale = 0.0f;
                    }
                    if (localScale > 1.0f)
                    {
                        localScale = 1.0f;
                    }
                    scaleFactor *= (1.0f - localScale);
                    break;
                }

                case ScaleNormalization.ZeroAtCenter:
                {
                    float localScale = v.x / (maxX / 4.0f);
                    Debug.Log("localScale = " + localScale);
                    Debug.Log("2.v.x=" + v.x + ", minX=" + minX + ", maxX=" + maxX + ", lscale=" + localScale);
                    if (localScale < 0.0f)
                    {
                        localScale = -localScale;
                    }
                    if (localScale > 1.0f)
                    {
                        localScale = 1.0f;
                    }
                    Debug.Log("final localScale = " + localScale);
                    scaleFactor *= localScale;
                    Debug.Log("scaleFactor= " + scaleFactor);
                    break;
                }
                }

                // Multiply in magnitude scale.
                deltaVertices[i] *= scaleFactor;
                deltaNormals[i]  *= scaleFactor;
                deltaTangents[i] *= scaleFactor;

                // Multiple in Y scale (e.g. scale = -1 to invert)
                deltaVertices[i].y *= yScale;
                deltaNormals[i].y  *= yScale;
                deltaTangents[i].y *= yScale;
            }
        }

        if (onlyIncludeThisSubmesh >= 0)
        {
            // Set everything to zero not in requested submesh
            UnityEngine.Rendering.SubMeshDescriptor smd = mesh.GetSubMesh(onlyIncludeThisSubmesh);
            for (int i = 0; i < smd.firstVertex; i++)
            {
                deltaVertices[i] = Vector3.zero;
                deltaNormals[i]  = Vector3.zero;
                deltaTangents[i] = Vector3.zero;
            }
            for (int i = smd.firstVertex + smd.vertexCount; i < mesh.vertexCount; i++)
            {
                deltaVertices[i] = Vector3.zero;
                deltaNormals[i]  = Vector3.zero;
                deltaTangents[i] = Vector3.zero;
            }
        }

        if (onlyExcludeThisSubmesh >= 0)
        {
            // Set everything to zero in requested submesh
            UnityEngine.Rendering.SubMeshDescriptor smd = mesh.GetSubMesh(onlyExcludeThisSubmesh);
            for (int i = smd.firstVertex; i < smd.firstVertex + smd.vertexCount; i++)
            {
                deltaVertices[i] = Vector3.zero;
                deltaNormals[i]  = Vector3.zero;
                deltaTangents[i] = Vector3.zero;
            }
        }
    }