Beispiel #1
0
        /// <summary>
        /// Gets the vector3 for the given distance to root for the given strand.
        ///
        /// If the dist to root is > than the hair length, the last vertex is returned, if its shorter then the first hair vertex is returned.
        /// </summary>
        /// <param name="strand"></param>
        /// <param name="distToRoot"></param>
        /// <returns></returns>
        public static Vector3 GetPositionOnStrand(HairStrand strand, float distToRoot)
        {
            // Get strand root
            Vector3 strandRoot   = strand.vertices[0].position;
            float   strandLength = strand.length;

            if (distToRoot <= 0)
            {
                return(strandRoot);
            }
            else if (distToRoot >= strandLength)
            {
                return(strand.vertices[strand.vertices.Count - 1].position);
            }

            // Loop variables
            float   rootDist    = 0;
            Vector3 lastPos     = strandRoot;
            int     lineSegment = -1;

            float[] rootDists = new float[strand.vertices.Count];

            // Find the line segment
            for (int i = 1; i < strand.vertices.Count; i++)
            {
                Vector3 curPos = strand.vertices[i].position;

                // Calculate root dists
                rootDist    += (lastPos - curPos).Length;
                rootDists[i] = rootDist;

                if (rootDist > distToRoot)
                {
                    // We found the segment
                    lineSegment = i;
                    break;
                }

                lastPos = curPos;
            }

            if (lineSegment == -1 || lineSegment == 0)
            {
                throw new KeyNotFoundException("Could not find the line segment for dist to root = " + distToRoot + "!");
            }

            // Sample position
            Vector3 segmentStart = strand.vertices[lineSegment - 1].position;
            Vector3 segmentEnd   = strand.vertices[lineSegment].position;

            // Build lerp t factor
            float segmentStartDist = rootDists[lineSegment - 1];
            float segmentEndDist   = rootDists[lineSegment];
            float segmentLength    = segmentEndDist - segmentStartDist;

            float lerpT = (distToRoot - segmentStartDist) / segmentLength;

            return(Vector3.Lerp(segmentStart, segmentEnd, lerpT));
        }
Beispiel #2
0
        /// <summary>
        /// Performs follow hair generation.
        /// If a hair file gets imported from a "not-tressfx" format like ase every hair will be set to be a guidance hair.
        /// With this functions its possible to generate hairs which will not get simulated, but moved along their guidance hair.
        /// This can get used to make hair more dense without simulation overhead.
        /// </summary>
        /// <param name="followHairsPerGuidanceHair"></param>
        /// <param name="maxRadiusAroundGuideHair"></param>
        public void GenerateFollowHairs(int followHairsPerGuidanceHair, float maxRadiusAroundGuideHair)
        {
            // Generate follow hairs
            if (followHairsPerGuidanceHair <= 0)
            {
                return;
            }

            foreach (HairMesh mesh in this.meshes)
            {
                if (mesh != null)                                                   // Check if mesh is not null to prevent nullpointer exceptions
                {
                    List <HairStrand> resultStrandList = new List <HairStrand>();
                    foreach (HairStrand guideStrand in mesh.strands)                     // Get every strand in every mesh
                    {
                        // Add the current strand
                        resultStrandList.Add(guideStrand);

                        if (guideStrand.isGuidanceStrand)                           // Check if the current strand is a guidance strand and needs follow hairs
                        {
                            for (int i = 0; i < followHairsPerGuidanceHair; i++)    // Generate the strands.
                            {
                                // Init the new strand
                                HairStrand newStrand = new HairStrand();
                                newStrand.isGuidanceStrand = false;
                                newStrand.guidanceStrand   = guideStrand;

                                Vector3 v01 = (guideStrand.vertices[1].position - guideStrand.vertices[0].position);
                                v01.Normalize();

                                // Find two orthogonal unit tangent vectors to v01
                                Vector3 t0, t1;
                                Utilities.GetTangentVectors(v01, out t0, out t1);
                                Vector3 offset = Utilities.GetRandom(-maxRadiusAroundGuideHair, maxRadiusAroundGuideHair) * t0 + Utilities.GetRandom(-maxRadiusAroundGuideHair, maxRadiusAroundGuideHair) * t1;

                                // Generate the vertices
                                for (int k = 0; k < guideStrand.vertices.Count; k++)
                                {
                                    float            factor   = 5.0f * (float)k / (float)guideStrand.vertices.Count + 1.0f;
                                    Vector3          position = guideStrand.vertices[k].position + (offset * factor);
                                    HairStrandVertex vertex   = new HairStrandVertex(position, Vector3.Zero, guideStrand.vertices[k].texcoord);
                                    vertex.isMovable = guideStrand.vertices[k].isMovable;
                                    newStrand.vertices.Add(vertex);
                                }

                                resultStrandList.Add(newStrand);
                            }
                        }
                    }

                    mesh.strands.Clear();
                    mesh.strands.AddRange(resultStrandList);
                }
            }
        }
Beispiel #3
0
        /// <summary>
        /// Implicitly initializes the indices of this hair.
        /// This calculates the indices automatically
        /// </summary>
        /// <param name="lineIndices"></param>
        /// <param name="triangleIndices"></param>
        public void CreateIndices()
        {
            List <int> lineIndices     = new List <int>();
            List <int> triangleIndices = new List <int>();

            int id = 0;

            // Iterate through every mesh and every strand and every vertex and generate the indices for them.
            for (int i = 0; i < this.meshes.Length; i++)
            {
                HairMesh currentMesh = this.meshes[i];

                if (currentMesh == null)
                {
                    continue;
                }

                for (int j = 0; j < currentMesh.strandCount; j++)
                {
                    HairStrand currentStrand = currentMesh.strands[j];

                    for (int k = 0; k < currentStrand.vertices.Count - 1; k++)
                    {
                        HairStrandVertex currentVertex = currentStrand.vertices[k];

                        // Append line indices
                        lineIndices.Add(id);
                        lineIndices.Add(id + 1);

                        // Append triangle indices
                        triangleIndices.Add(2 * id);
                        triangleIndices.Add(2 * id + 1);
                        triangleIndices.Add(2 * id + 2);
                        triangleIndices.Add(2 * id + 2);
                        triangleIndices.Add(2 * id + 1);
                        triangleIndices.Add(2 * id + 3);

                        id++;
                    }

                    id++;
                }
            }

            // Set indices
            this.lineIndices     = lineIndices.ToArray();
            this.triangleIndices = triangleIndices.ToArray();
        }
Beispiel #4
0
        /// <summary>
        /// Performs follow hair generation.
        /// If a hair file gets imported from a "not-tressfx" format like ase every hair will be set to be a guidance hair.
        /// With this functions its possible to generate hairs which will not get simulated, but moved along their guidance hair.
        /// This can get used to make hair more dense without simulation overhead.
        /// </summary>
        /// <param name="followHairsPerGuidanceHair"></param>
        /// <param name="maxRadiusAroundGuideHair"></param>
        public void GenerateFollowHairs(int followHairsPerGuidanceHair, float maxRadiusAroundGuideHair)
        {
            // Generate follow hairs
            if (followHairsPerGuidanceHair <= 0)
                return;

            foreach (HairMesh mesh in this.meshes)
            {
                if (mesh != null)                                                   // Check if mesh is not null to prevent nullpointer exceptions
                {
                    List<HairStrand> resultStrandList = new List<HairStrand>();
                    foreach (HairStrand guideStrand in mesh.strands)                     // Get every strand in every mesh
                    {
                        // Add the current strand
                        resultStrandList.Add(guideStrand);

                        if (guideStrand.isGuidanceStrand)                                // Check if the current strand is a guidance strand and needs follow hairs
                            for (int i = 0; i < followHairsPerGuidanceHair; i++)    // Generate the strands.
                            {
                                // Init the new strand
                                HairStrand newStrand = new HairStrand();
                                newStrand.isGuidanceStrand = false;
                                newStrand.guidanceStrand = guideStrand;

                                Vector3 v01 = (guideStrand.vertices[1].position - guideStrand.vertices[0].position);
                                v01.Normalize();

                                // Find two orthogonal unit tangent vectors to v01
                                Vector3 t0, t1;
                                Utilities.GetTangentVectors(v01, out t0, out t1);
                                Vector3 offset = Utilities.GetRandom(-maxRadiusAroundGuideHair, maxRadiusAroundGuideHair) * t0 + Utilities.GetRandom(-maxRadiusAroundGuideHair, maxRadiusAroundGuideHair) * t1;

                                // Generate the vertices
                                for (int k = 0; k < guideStrand.vertices.Count; k++)
                                {
                                    float factor = 5.0f * (float)k / (float)guideStrand.vertices.Count + 1.0f;
                                    Vector3 position = guideStrand.vertices[k].position + (offset * factor);
                                    HairStrandVertex vertex = new HairStrandVertex(position, Vector3.Zero, guideStrand.vertices[k].texcoord);
                                    vertex.isMovable = guideStrand.vertices[k].isMovable;
                                    newStrand.vertices.Add(vertex);
                                }

                                resultStrandList.Add(newStrand);
                            }
                    }

                    mesh.strands.Clear();
                    mesh.strands.AddRange(resultStrandList);
                }
            }
        }
Beispiel #5
0
        /// <summary>
        /// This function will get used by importers for example if there were only strand vertex positions available.
        /// It will calculate everything inside of the HairStrand and HairStrandVertex based off the vertex positions.
        ///
        /// This function assumes that hair strands always have atleast 2 vertices!
        /// and atleast 2 strands.
        ///
        /// NOTE: This is currently non-functional, it seems like somewhere some rounding issues happen that will make the output of this unusable.
        /// The logic was taken from AMD's AssetConverter. I already spent some days of debugging on this but there is something really weird going on here.
        /// My best guess is that C++ floats are more precise than C# floats for this calculations.
        /// </summary>
        public void PrepareSimulationParameters(ref int hairIndexCounter)
        {
            // TODO: Validity checks
            int counterOffset = hairIndexCounter;

            for (int i = 0; i < this.strandCount; i++)
            {
                // Get current strand
                HairStrand strand = this.strands[i];

                // Set the root follow hair offset
                if (strand.isGuidanceStrand)
                {
                    strand.followRootOffset = new Vector4(0, 0, 0, hairIndexCounter);
                }
                else
                {
                    if (strand.guidanceStrand == null)
                    {
                        throw new FormatException("Tried to process a follow strand without guidance strand reference!");
                    }

                    Vector3 offset = strand.vertices[0].position - strand.guidanceStrand.vertices[0].position;
                    strand.followRootOffset = new Vector4(offset, counterOffset + this.strands.FindIndex(0, this.strands.Count, s => s == strand.guidanceStrand));
                }

                // First vertex
                HairStrandVertex firstVertex  = this.strands[i].vertices[0];
                HairStrandVertex secondVertex = this.strands[i].vertices[1];
                {
                    Vector3 vec  = (secondVertex.position - firstVertex.position);
                    Vector3 vecX = Vector3.Normalize(vec);

                    Vector3 vecZ = Vector3.Cross(vecX, new Vector3(1, 0, 0));

                    if (vecZ.LengthSquared < 0.0001f)
                    {
                        vecZ = Vector3.Cross(vecX, new Vector3(0, 1, 0));
                    }

                    vecZ.Normalize();

                    Vector3 vecY = Vector3.Normalize(Vector3.Cross(vecZ, vecX));

                    // Construct rotation matrix
                    Matrix3 rotL2W = new Matrix3();
                    rotL2W.R0C0 = vecX.x;
                    rotL2W.R1C0 = vecX.y;
                    rotL2W.R2C0 = vecX.z;

                    rotL2W.R0C1 = vecY.x;
                    rotL2W.R1C1 = vecY.y;
                    rotL2W.R2C1 = vecY.z;

                    rotL2W.R0C2 = vecZ.x;
                    rotL2W.R1C2 = vecZ.y;
                    rotL2W.R2C2 = vecZ.z;

                    firstVertex.localTranslation = firstVertex.position;
                    firstVertex.localRotation    = rotL2W.ToQuaternion();
                    firstVertex.globalTransform  = firstVertex.localTransform;
                }

                // The rest n-1 vertices
                for (int j = 1; j < strand.vertices.Count; j++)
                {
                    Vector3          currentPosition  = strand.vertices[j].position;
                    Vector3          previousPosition = strand.vertices[j - 1].position;
                    HairStrandVertex previousVertex   = strand.vertices[j - 1];
                    HairStrandVertex currentVertex    = strand.vertices[j];

                    Vector3 vec = Vector3.TressFXTransform((currentPosition - previousPosition), Quaternion.Invert(previousVertex.globalRotation));

                    Vector3 vecX = Vector3.Normalize(vec);

                    Vector3 X       = new Vector3(1.0f, 0, 0);
                    Vector3 rotAxis = Vector3.Cross(X, vecX);
                    float   angle   = Mathf.Acos(Vector3.Dot(X, vecX));


                    if (Mathf.Abs(angle) < 0.001f || rotAxis.LengthSquared < 0.001f)
                    {
                        currentVertex.localRotation = Quaternion.Identity;
                    }
                    else
                    {
                        rotAxis.Normalize();
                        Quaternion rot = Quaternion.FromAxisAngle(rotAxis, angle);
                        currentVertex.localRotation = rot;
                    }

                    currentVertex.localTransform.translation = vec;
                    currentVertex.globalTransform            = previousVertex.globalTransform.Multiply(currentVertex.localTransform);
                    // Set the reference vector
                    currentVertex.referenceVector = new Vector4(currentVertex.localTransform.translation, 0);
                }

                hairIndexCounter++;
            }

            for (int i = 0; i < this.strandCount; i++)
            {
                // Get current strand
                HairStrand strand      = this.strands[i];
                int        vertexCount = strand.vertices.Count;

                // Compute strand tangent
                {
                    strand.vertices[0].tangent = Vector3.Normalize((strand.vertices[1].position - strand.vertices[0].position));
                    strand.vertices[0].tangent.Normalize();

                    // 1 - (n-1)
                    for (int j = 1; j < vertexCount - 1; j++)
                    {
                        Vector3 tangentPre  = strand.vertices[j].position - strand.vertices[j - 1].position;
                        Vector3 tangentNext = strand.vertices[j + 1].position - strand.vertices[j].position;
                        strand.vertices[j].tangent = (tangentPre + tangentNext);
                        strand.vertices[j].tangent.Normalize();
                    }

                    // Last vertex
                    strand.vertices[vertexCount - 1].tangent = Vector3.Normalize((strand.vertices[vertexCount - 1].position - strand.vertices[vertexCount - 2].position));
                    strand.vertices[vertexCount - 1].tangent.Normalize();
                }

                // The current distance to root
                float distanceToRoot = 0;

                // Compute distances to root
                for (int j = 1; j < vertexCount; j++)
                {
                    Vector3 vec    = strand.vertices[j].position - strand.vertices[j - 1].position;
                    float   disSeg = vec.Length;
                    distanceToRoot += disSeg;
                    strand.vertices[j].distanceToRoot = distanceToRoot;
                }

                // Distance to root normalization
                for (int j = 0; j < vertexCount; j++)
                {
                    strand.vertices[j].distanceToRoot /= distanceToRoot;
                }

                // Rest length
                for (int j = 0; j < vertexCount - 1; j++)
                {
                    strand.vertices[j].restLength = (strand.vertices[j].position - strand.vertices[j + 1].position).Length;
                }

                // Calculate texcoord
                for (int j = 0; j < vertexCount - 1; j++)
                {
                    strand.vertices[j].texcoord = new Vector4(i / (float)this.strandCount, j / (float)strand.vertices.Count, 0, 0);
                }

                // Calculate thickness coefficient
                for (int j = 0; j < vertexCount; j++)
                {
                    float tVal = strand.vertices[j].distanceToRoot;
                    strand.vertices[j].thicknessCoefficient = Mathf.Sqrt(1.0f - tVal * tVal);
                }
            }
        }
Beispiel #6
0
        /// <summary>
        /// Gets the vector3 for the given distance to root for the given strand.
        /// 
        /// If the dist to root is > than the hair length, the last vertex is returned, if its shorter then the first hair vertex is returned.
        /// </summary>
        /// <param name="strand"></param>
        /// <param name="distToRoot"></param>
        /// <returns></returns>
        public static Vector3 GetPositionOnStrand(HairStrand strand, float distToRoot)
        {
            // Get strand root
            Vector3 strandRoot = strand.vertices[0].position;
            float strandLength = strand.length;

            if (distToRoot <= 0)
                return strandRoot;
            else if (distToRoot >= strandLength)
                return strand.vertices[strand.vertices.Count-1].position;

            // Loop variables
            float rootDist = 0;
            Vector3 lastPos = strandRoot;
            int lineSegment = -1;
            float[] rootDists = new float[strand.vertices.Count];

            // Find the line segment
            for (int i = 1; i < strand.vertices.Count; i++)
            {
                Vector3 curPos = strand.vertices[i].position;

                // Calculate root dists
                rootDist += (lastPos - curPos).Length;
                rootDists[i] = rootDist;

                if (rootDist > distToRoot)
                {
                    // We found the segment
                    lineSegment = i;
                    break;
                }

                lastPos = curPos;
            }

            if (lineSegment == -1 || lineSegment == 0)
                throw new KeyNotFoundException("Could not find the line segment for dist to root = " + distToRoot + "!");

            // Sample position
            Vector3 segmentStart = strand.vertices[lineSegment-1].position;
            Vector3 segmentEnd = strand.vertices[lineSegment].position;
            
            // Build lerp t factor
            float segmentStartDist = rootDists[lineSegment-1];
            float segmentEndDist = rootDists[lineSegment];
            float segmentLength = segmentEndDist - segmentStartDist;

            float lerpT = (distToRoot - segmentStartDist) / segmentLength;

            return Vector3.Lerp(segmentStart, segmentEnd, lerpT);
        }
Beispiel #7
0
        /// <summary>
        /// This function will get used by importers for example if there were only strand vertex positions available.
        /// It will calculate everything inside of the HairStrand and HairStrandVertex based off the vertex positions.
        ///
        /// This function assumes that hair strands always have atleast 2 vertices!
        /// and atleast 2 strands.
        /// </summary>
        public void PrepareSimulationParameters(ref int hairIndexCounter)
        {
            // TODO: Validity checks
            int counterOffset = hairIndexCounter;

            for (int i = 0; i < this.strandCount; i++)
            {
                // Get current strand
                HairStrand strand = this.strands[i];

                // Set the root follow hair offset
                if (strand.isGuidanceStrand)
                {
                    strand.followRootOffset = new Vector4(0, 0, 0, hairIndexCounter);
                }
                else
                {
                    if (strand.guidanceStrand == null)
                    {
                        throw new FormatException("Tried to process a follow strand without guidance strand reference!");
                    }

                    Vector3 offset = strand.vertices[0].position - strand.guidanceStrand.vertices[0].position;
                    strand.followRootOffset = new Vector4(offset, counterOffset + this.strands.FindIndex(0, this.strands.Count, s => s == strand.guidanceStrand));
                }

                // The current strand length.
                float strandLength = strand.length;

                // The current distance to root
                float distanceToRoot = 0;

                for (int j = 0; j < strand.vertices.Count; j++)
                {
                    // Get current vertex
                    HairStrandVertex vertex        = strand.vertices[j];
                    HairStrandVertex lastVertex    = strand.vertices[strand.vertices.Count - 1];
                    HairStrandVertex firstVertex   = this.strands[j].vertices[0];
                    HairStrandVertex secondVertex  = this.strands[j].vertices[1];
                    HairStrandVertex preLastVertex = strand.vertices[strand.vertices.Count - 2];

                    // Get next vertex
                    HairStrandVertex nextVertex = null;
                    if (j < strand.vertices.Count - 1)
                    {
                        nextVertex = strand.vertices[j + 1];
                    }
                    else
                    {
                        if (i < this.strandCount - 1)
                        {
                            nextVertex = this.strands[i + 1].vertices[0];
                        }
                        else
                        {
                            nextVertex = new HairStrandVertex(Vector3.Zero, new Vector3(0, 0, 1), Vector4.Zero);
                        }
                    }

                    // Get previous vertex
                    HairStrandVertex previousVertex = null;
                    if (j > 0)
                    {
                        previousVertex = strand.vertices[j - 1];
                    }
                    else
                    {
                        if (i > 0)
                        {
                            HairStrand prevStrand = this.strands[i - 1];
                            previousVertex = prevStrand.vertices[prevStrand.vertices.Count - 1];
                        }
                        else
                        {
                            previousVertex = new HairStrandVertex(Vector3.Zero, new Vector3(0, 0, 1), Vector4.Zero);
                        }
                    }

                    // Get current and next position vectors
                    Vector3 currentPosition  = vertex.position;
                    Vector3 nextPosition     = nextVertex.position;
                    Vector3 previousPosition = previousVertex.position;

                    // Calculate vertex level information

                    // Initialize first vertex
                    // First vertex global and local frame initialization
                    // Gets the direction from the current position to the next position
                    if (j == 0)
                    {
                        if (Mathf.Abs(currentPosition.x - (-20.3614f)) <= 0.0001f && Mathf.Abs(currentPosition.y - (81.1211f)) <= 0.0001f && Mathf.Abs(currentPosition.z - (-18.1023f)) <= 0.0001f)
                        {
                            int jk = 0;
                        }

                        Vector3 vec  = (secondVertex.position - firstVertex.position);
                        Vector3 vecX = Vector3.Normalize(vec);

                        Vector3 vecZ = Vector3.Cross(vecX, new Vector3(1, 0, 0));

                        if (vecZ.LengthSquared < 0.0001f)
                        {
                            vecZ = Vector3.Cross(vecX, new Vector3(0, 1, 0));
                        }

                        vecZ.Normalize();

                        Vector3 vecY = Vector3.Normalize(Vector3.Cross(vecZ, vecX));

                        // Construct rotation matrix
                        Matrix3 rotL2W = new Matrix3();
                        rotL2W.R0C0 = vecX.x;
                        rotL2W.R1C0 = vecX.y;
                        rotL2W.R2C0 = vecX.z;

                        rotL2W.R0C1 = vecY.x;
                        rotL2W.R1C1 = vecY.y;
                        rotL2W.R2C1 = vecY.z;

                        rotL2W.R0C2 = vecZ.x;
                        rotL2W.R1C2 = vecZ.y;
                        rotL2W.R2C2 = vecZ.z;

                        firstVertex.localTranslation = firstVertex.position;
                        firstVertex.localRotation    = rotL2W.ToQuaternion();
                        firstVertex.globalTransform  = firstVertex.localTransform;
                    }
                    else
                    {
                        // -6.54806995f 71.0205994f 44.4449997f

                        // if (vertex.position.x == -4.19069004f && vertex.position.y == 73.1125031f && vertex.position.z == 42.9104004f)
                        if (i == 10 && j == 9) // vertex.position.x == -33.5211029f && vertex.position.y == -21.6556110f && vertex.position.z == -11.5229073f)
                        {
                            int elite = 123;
                        }

                        Vector3 v         = Vector3.Cross(new Vector3(1, 0, 0), new Vector3(1, 0, 0.010100123f));
                        float   d         = Vector3.Dot(new Vector3(1, 0, 0), new Vector3(1, 0, 0.010100123f));
                        float   angleTest = Mathf.Acos(d);

                        Vector3 vec = Vector3.TressFXTransform((currentPosition - previousPosition), Quaternion.Invert(previousVertex.globalRotation));

                        Vector3 vecX = Vector3.Normalize(vec);

                        Vector3 X       = new Vector3(1.0f, 0, 0);
                        Vector3 rotAxis = Vector3.Cross(X, vecX);
                        float   angle   = Mathf.Acos(Vector3.Dot(X, vecX));


                        if (Mathf.Abs(angle) < 0.0011f || rotAxis.LengthSquared < 0.0011f)
                        {
                            vertex.localRotation = Quaternion.Identity;
                        }
                        else
                        {
                            rotAxis.Normalize();
                            Quaternion rot = Quaternion.FromAxisAngle(rotAxis, angle);
                            vertex.localRotation = rot;
                        }

                        vertex.localTransform.translation = vec;
                        vertex.globalTransform            = previousVertex.globalTransform.Multiply(vertex.localTransform);
                        // Set the reference vector
                        vertex.referenceVector = new Vector4(vertex.localTransform.translation, 0);
                    }

                    // Rest length
                    if (j < strand.vertices.Count - 1)
                    {
                        vertex.restLength = (strand.vertices[j].position - strand.vertices[j + 1].position).Length;
                    }
                    else
                    {
                        vertex.restLength = 0;
                    }

                    // Compute strand tangent

                    // Calculate tangents for every vertex except the first and the last
                    if (j == 0)
                    {
                        strand.vertices[0].tangent = Vector3.Normalize((strand.vertices[1].position - strand.vertices[0].position));
                    }
                    else if (j == strand.vertices.Count - 1)
                    {
                        // Calculate the last vertex tangent
                        lastVertex.tangent = Vector3.Normalize((lastVertex.position - preLastVertex.position));
                    }
                    else
                    {
                        Vector3 tangent_pre  = Vector3.Normalize(vertex.position - previousVertex.position);
                        Vector3 tangent_next = Vector3.Normalize(nextVertex.position - vertex.position);
                        vertex.tangent = Vector3.Normalize(tangent_pre + tangent_next);
                    }

                    // Calculate texcoord
                    vertex.texcoord = new Vector4(i / (float)this.strandCount, j / (float)strand.vertices.Count, 0, 0);

                    // Calculate thickness coefficient
                    if (j > 0)
                    {
                        float segmentLength = (vertex.position - previousVertex.position).Length;
                        distanceToRoot += segmentLength;
                    }

                    float tVal = distanceToRoot / strandLength;
                    vertex.thicknessCoefficient = Mathf.Sqrt(1.0f - tVal * tVal);
                }

                hairIndexCounter++;
            }
        }