/// <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)); }
/// <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); } } }
/// <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(); }
/// <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); } } }
/// <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); } } }
/// <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); }
/// <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++; } }