/// <summary> /// This method creates a hair strand with a given length and anchor position. /// </summary> /// <param name="length">The length of the hair (number of hair bodies)</param> /// <param name="anchorPosition">The anchor position that the hair will be attached to.</param> /// <returns>A new hair strand</returns> private unsafe HairStrand CreateHairStrand(int length, Vector3 anchorPosition) { HairStrand strand = new HairStrand(); for (int i = 0; i < length && i < kMaxHairLength; i++) { Vector3 newPos = anchorPosition + (-Vector3.up * (springLength * i)); strand.posX[i] = newPos.x; strand.posY[i] = newPos.y; strand.posZ[i] = newPos.z; } return(strand); }
public void Run(HairAsset asset) { uint[] movability = HairMovability.CreateData(asset.vertexCount); HairStrand[] strands = asset.GetStrandData(); for (int i = 0; i < strands.Length; i++) { HairStrand strand = strands[i]; HairMovability.SetMovable(strand.firstVertex, false, movability); int cnt = (strand.lastVertex - strand.firstVertex) + 1; for (int j = 1; j < cnt; j++) { HairMovability.SetMovable(strand.firstVertex + j, true, movability); } } asset.InitializeMovability(movability); }
public HairMesh[] Import(BinaryReader reader, string path, Hair hair, HairImportSettings importSettings) { reader.Close(); // Initialize the import // Load the ase file content // Create a List for all meshes // Create a list for the current-mesh strands string[] aseContent = File.ReadAllLines(path); List <HairMesh> hairMeshes = new List <HairMesh>(); List <HairStrand> currentStrands = new List <HairStrand>(); // Init "state"-variables int currentStrand = 0; int currentHairId = -1; float texcoordMultiplier = 0; // Now the hard part begins... for (int i = 0; i < aseContent.Length; i++) { string[] tokens = aseContent[i].Split('\t'); if (aseContent[i].Contains("*SHAPE_LINECOUNT")) { tokens = tokens[1].Split(' '); } else if (aseContent[i].Contains("SHAPE_LINE")) { tokens = tokens[1].Split(' '); } if (tokens.Length >= 2) { if (tokens[0] == "*SHAPE_LINECOUNT") { if (currentStrand > 0) { // Start parsing next mesh after flushing the current strands buffer currentHairId++; currentStrand = 0; // Add to mesh list / flush current strands buffer HairMesh hairMesh = new HairMesh(); foreach (HairStrand strand in currentStrands) { hairMesh.strands.Add(strand); } hairMeshes.Add(hairMesh); // Clear current strands currentStrands.Clear(); texcoordMultiplier = 1.0f / (float)int.Parse(tokens[1]); } } else if (tokens[0] == "*SHAPE_LINE") { HairStrand strand = new HairStrand(); strand.isGuidanceStrand = true; string[] vertexCountTokens = aseContent[i + 1].Split(' '); // Parse the current line int vertexCount = int.Parse(vertexCountTokens[1]); // Parse vertices for (int j = 0; j < vertexCount; j++) { string[] vertexTokens = aseContent[i + 2 + j].Replace('.', ',').Split('\t'); if (vertexTokens[2] == "*SHAPE_VERTEX_INTERP") { continue; } System.Globalization.NumberFormatInfo nf = new System.Globalization.NumberFormatInfo() { NumberGroupSeparator = "." }; vertexTokens[4] = vertexTokens[4].Replace(',', '.'); vertexTokens[5] = vertexTokens[5].Replace(',', '.'); vertexTokens[6] = vertexTokens[6].Replace(',', '.'); Vector3 position = new Vector3(float.Parse(vertexTokens[4], nf), float.Parse(vertexTokens[6], nf), float.Parse(vertexTokens[5], nf)); position = Vector3.Multiply(position, importSettings.scale); HairStrandVertex v = new HairStrandVertex(position, Vector3.Zero, Vector4.Zero); if (strand.vertices.Count == 0) { v.isMovable = false; } strand.vertices.Add(v); } currentStrands.Add(strand); // Increment file-line-pointer i = i + 1 + vertexCount; currentStrand++; } } } // Get last mesh HairMesh lastMesh = new HairMesh(); lastMesh.strands.AddRange(currentStrands); hairMeshes.Add(lastMesh); return(hairMeshes.ToArray()); }
public HairMesh[] Import(BinaryReader reader, string path, Hair hair, HairImportSettings importSettings) { reader.Close(); // Initialize the import // Load the ase file content // Create a List for all meshes // Create a list for the current-mesh strands string[] aseContent = File.ReadAllLines(path); List<HairMesh> hairMeshes = new List<HairMesh>(); List<HairStrand> currentStrands = new List<HairStrand>(); // Init "state"-variables int currentStrand = 0; int currentHairId = -1; float texcoordMultiplier = 0; // Now the hard part begins... for (int i = 0; i < aseContent.Length; i++) { string[] tokens = aseContent[i].Split('\t'); if (aseContent[i].Contains("*SHAPE_LINECOUNT")) { tokens = tokens[1].Split(' '); } else if (aseContent[i].Contains("SHAPE_LINE")) { tokens = tokens[1].Split(' '); } if (tokens.Length >= 2) { if (tokens[0] == "*SHAPE_LINECOUNT") { if (currentStrand > 0) { // Start parsing next mesh after flushing the current strands buffer currentHairId++; currentStrand = 0; // Add to mesh list / flush current strands buffer HairMesh hairMesh = new HairMesh(); foreach (HairStrand strand in currentStrands) { hairMesh.strands.Add(strand); } hairMeshes.Add(hairMesh); // Clear current strands currentStrands.Clear(); texcoordMultiplier = 1.0f / (float)int.Parse(tokens[1]); } } else if (tokens[0] == "*SHAPE_LINE") { HairStrand strand = new HairStrand(); strand.isGuidanceStrand = true; string[] vertexCountTokens = aseContent[i + 1].Split(' '); // Parse the current line int vertexCount = int.Parse(vertexCountTokens[1]); // Parse vertices for (int j = 0; j < vertexCount; j++) { string[] vertexTokens = aseContent[i + 2 + j].Replace('.', ',').Split('\t'); if (vertexTokens[2] == "*SHAPE_VERTEX_INTERP") continue; System.Globalization.NumberFormatInfo nf = new System.Globalization.NumberFormatInfo() { NumberGroupSeparator = "." }; vertexTokens[4] = vertexTokens[4].Replace(',', '.'); vertexTokens[5] = vertexTokens[5].Replace(',', '.'); vertexTokens[6] = vertexTokens[6].Replace(',', '.'); Vector3 position = new Vector3(float.Parse(vertexTokens[4], nf), float.Parse(vertexTokens[6], nf), float.Parse(vertexTokens[5], nf)); position = Vector3.Multiply(position, importSettings.scale); HairStrandVertex v = new HairStrandVertex(position, Vector3.Zero, Vector4.Zero); if (strand.vertices.Count == 0) v.isMovable = false; strand.vertices.Add(v); } currentStrands.Add(strand); // Increment file-line-pointer i = i + 1 + vertexCount; currentStrand++; } } } // Get last mesh HairMesh lastMesh = new HairMesh(); lastMesh.strands.AddRange(currentStrands); hairMeshes.Add(lastMesh); return hairMeshes.ToArray(); }
public HairMesh[] Import(BinaryReader reader, string path, Hair hair, HairImportSettings importSettings) { reader.Close(); // Initialize the importer string[] objContent = File.ReadAllLines(path); List<Vector3> vertices = new List<Vector3>(); List<Line> indices = new List<Line>(); List<HairStrand> currentStrands = new List<HairStrand>(); // Parse all vertices and indices for (int i = 0; i < objContent.Length; i++) { // Get all parts List<string> partsList = new List<string>(objContent[i].Split(' ')); partsList.RemoveAll(x => string.IsNullOrEmpty(x.Replace(" ", ""))); string[] parts = partsList.ToArray(); switch (parts[0]) { case "v": { vertices.Add(new Vector3(SafeParse(parts[1]), SafeParse(parts[2]), SafeParse(parts[3]))); } break; case "l": { indices.Add(new Line(int.Parse(parts[1])-1, int.Parse(parts[2])-1)); } break; } } // Parse all strands List<int> alreadyLoadedIndices = new List<int>(640000); List<HairStrandVertex> currentStrandVertices = new List<HairStrandVertex>(64); for (int i = 0; i < indices.Count; i++) { // In order to detect when a new hair starts, we detect if the first index in a line was not added to the mesh already. // If it was NOT, we are in a new strand if (i != 0 && !alreadyLoadedIndices.Contains(indices[i].index1)) { // We are in a new hair strand HairStrand hs = new HairStrand(); currentStrandVertices[0].isMovable = false; currentStrandVertices.ForEach((sv) => { hs.vertices.Add(sv); }); hs.isGuidanceStrand = true; currentStrands.Add(hs); // Cleanup currentStrandVertices.Clear(); } // Add if (!alreadyLoadedIndices.Contains(indices[i].index1)) { currentStrandVertices.Add(new HairStrandVertex(vertices[indices[i].index1], Vector3.Zero, Vector4.Zero)); alreadyLoadedIndices.Add(indices[i].index1); } if (!alreadyLoadedIndices.Contains(indices[i].index2)) { currentStrandVertices.Add(new HairStrandVertex(vertices[indices[i].index2], Vector3.Zero, Vector4.Zero)); alreadyLoadedIndices.Add(indices[i].index2); } } HairMesh hm = new HairMesh(); currentStrands.ForEach((item) => { hm.strands.Add(item); }); return new HairMesh[] { hm }; }
public virtual HairMesh[] Import(BinaryReader reader, string path, Hair hair, HairImportSettings importSettings) { // TODO: Implement import settings HairMesh[] returnData = null; // Load header information int numVertices = reader.ReadInt32(); int numStrands = reader.ReadInt32(); // maxVerticesPerStrand is what amd calls it // Actually this should be vertices per strand, as tressfx needs a uniform vertex count on every strand // We will assume that the input file is has a uniform vertex count. // If it doesnt, the import will fail. int maxVerticesPerStrand = reader.ReadInt32(); int numGuideHairVertices = reader.ReadInt32(); int numGuideHairStrands = reader.ReadInt32(); /*int numFollowHairsPerOneGuideHair = */reader.ReadInt32(); // Load hair data information int[] strandTypes = ReadIntegerArray(reader, numStrands); Vector4[] referenceVectors = ReadVector4Array(reader, numVertices); Quaternion[] globalRotations = ReadQuaternionArray(reader, numVertices); Quaternion[] localRotations = ReadQuaternionArray(reader, numVertices); Vector4[] vertices = ReadVector4Array(reader, numVertices); Vector4[] tangents = ReadVector4Array(reader, numVertices); // Read the triangle vertices // Actually those are __NEVER__ used anywhere. // So, we are going to skip / ignore them ReadVector3Array(reader, numVertices); ReadVector3Array(reader, numVertices); ReadVector4Array(reader, numVertices); float[] thicknessCoefficients = ReadFloatArray(reader, numVertices); Vector4[] followRootOffsets = ReadVector4Array(reader, numStrands); float[] restLengths = ReadFloatArray(reader, numVertices); // Get bounding sphere hair.InitializeBoundingSphere(ReadVector3(reader), reader.ReadSingle()); // Load indices int indexTmp = reader.ReadInt32(); int[] triangleIndices = ReadIntegerArray(reader, indexTmp); indexTmp = reader.ReadInt32(); int[] lineIndices = ReadIntegerArray(reader, indexTmp); // Init indices hair.InitializeIndices(lineIndices, triangleIndices); // Set simulation data HairSimulationData simulationData = new HairSimulationData(); simulationData.followRootOffsets = followRootOffsets.ToList(); simulationData.globalRotations = globalRotations.ToList(); simulationData.guideHairStrandCount = numGuideHairStrands; simulationData.guideHairVertexCount = numGuideHairVertices; simulationData.maxNumVerticesPerStrand = maxVerticesPerStrand; simulationData.localRotations = localRotations.ToList(); simulationData.referenceVectors = referenceVectors.ToList(); simulationData.strandTypes = strandTypes.ToList(); simulationData.tangents = tangents.ToList(); simulationData.thicknessCoefficients = thicknessCoefficients.ToList(); simulationData.vertices = vertices.ToList(); simulationData.restLength = restLengths.ToList(); hair.InitializeSimulationData(simulationData); // So, the data is read now. // Next, we are going to construct the hair meshes. HairMesh[] hairMeshes = new HairMesh[4]; // Current list "pointer" int vertexPointer = 0; int strandPointer = 0; List<HairStrand> strandReferences = new List<HairStrand>(); foreach (int meshIndex in strandTypes) { // Check if we got a valid strand types array if (meshIndex < 0 || meshIndex > 3) throw new FormatException("Mesh ids (strand types) < 0 or > 3 are not supported by TressFX!"); // Get mesh and create it if it doesnt exist if (hairMeshes[meshIndex] == null) hairMeshes[meshIndex] = new HairMesh(); HairMesh mesh = hairMeshes[meshIndex]; // Create new strand HairStrand strand = new HairStrand(); strandReferences.Add(strand); strand.followRootOffset = followRootOffsets[strandPointer]; if (strand.followRootOffset.x != 0 || strand.followRootOffset.y != 0 || strand.followRootOffset.z != 0) { // This is a follow hair strand.guidanceStrand = strandReferences[(int)strand.followRootOffset.w]; strand.isGuidanceStrand = false; } else { // This is a guidance hair strand.isGuidanceStrand = true; } // Read all vertices for (int i = 0; i < maxVerticesPerStrand; i++) { // Read vertex data // As texcoord we will create a coordinate which projects the hair along the x-axis based on the strand index // and the y-axis based on the vertex index inside of the current strand HairStrandVertex vertex = new HairStrandVertex(new Vector3(vertices[vertexPointer].X, vertices[vertexPointer].Y, vertices[vertexPointer].Z), new Vector3(tangents[vertexPointer].X, tangents[vertexPointer].Y, tangents[vertexPointer].Z), new Vector4(strandPointer / (float)numStrands, i / (float)maxVerticesPerStrand, 0, 0)); // Set movable flag vertex.isMovable = (vertices[vertexPointer].W > 0); // Set simulation data vertex.globalRotation = globalRotations[vertexPointer]; vertex.localRotation = localRotations[vertexPointer]; vertex.referenceVector = referenceVectors[vertexPointer]; vertex.restLength = restLengths[vertexPointer]; vertex.thicknessCoefficient = thicknessCoefficients[vertexPointer]; // Add to strand strand.vertices.Add(vertex); vertexPointer++; } // Add strand to mesh mesh.strands.Add(strand); strandPointer++; } // Construct return array List<HairMesh> meshes = new List<HairMesh>(); for (int i = 0; i < hairMeshes.Length; i++) if (hairMeshes[i] == null) { // We're done reading break; } else { // Read the mesh meshes.Add(hairMeshes[i]); } returnData = hairMeshes.ToArray(); return returnData; }
public HairMesh[] Import(BinaryReader reader, string path, Hair hair, HairImportSettings importSettings) { reader.Close(); // Initialize the importer string[] objContent = File.ReadAllLines(path); List <Vector3> vertices = new List <Vector3>(); List <Line> indices = new List <Line>(); List <HairStrand> currentStrands = new List <HairStrand>(); // Parse all vertices and indices for (int i = 0; i < objContent.Length; i++) { // Get all parts List <string> partsList = new List <string>(objContent[i].Split(' ')); partsList.RemoveAll(x => string.IsNullOrEmpty(x.Replace(" ", ""))); string[] parts = partsList.ToArray(); switch (parts[0]) { case "v": { vertices.Add(Vector3.Multiply(new Vector3(SafeParse(parts[1]), SafeParse(parts[2]), SafeParse(parts[3])), importSettings.scale)); } break; case "l": { indices.Add(new Line(int.Parse(parts[1]) - 1, int.Parse(parts[2]) - 1)); } break; } } // Parse all strands List <int> alreadyLoadedIndices = new List <int>(640000); List <HairStrandVertex> currentStrandVertices = new List <HairStrandVertex>(64); for (int i = 0; i < indices.Count; i++) { // In order to detect when a new hair starts, we detect if the first index in a line was not added to the mesh already. // If it was NOT, we are in a new strand if (i != 0 && !alreadyLoadedIndices.Contains(indices[i].index1)) { // We are in a new hair strand HairStrand hs = new HairStrand(); currentStrandVertices[0].isMovable = false; currentStrandVertices.ForEach((sv) => { hs.vertices.Add(sv); }); hs.isGuidanceStrand = true; currentStrands.Add(hs); // Cleanup currentStrandVertices.Clear(); } // Add if (!alreadyLoadedIndices.Contains(indices[i].index1)) { currentStrandVertices.Add(new HairStrandVertex(vertices[indices[i].index1], Vector3.Zero, Vector4.Zero)); alreadyLoadedIndices.Add(indices[i].index1); } if (!alreadyLoadedIndices.Contains(indices[i].index2)) { currentStrandVertices.Add(new HairStrandVertex(vertices[indices[i].index2], Vector3.Zero, Vector4.Zero)); alreadyLoadedIndices.Add(indices[i].index2); } } // Shuffle strands currentStrands = FormatHelper.Shuffle(currentStrands); HairMesh hm = new HairMesh(); currentStrands.ForEach((item) => { hm.strands.Add(item); }); return(new HairMesh[] { hm }); }
public virtual HairMesh[] Import(BinaryReader reader, string path, Hair hair, HairImportSettings importSettings) { // TODO: Implement import settings HairMesh[] returnData = null; // Load header information int numVertices = reader.ReadInt32(); int numStrands = reader.ReadInt32(); // maxVerticesPerStrand is what amd calls it // Actually this should be vertices per strand, as tressfx needs a uniform vertex count on every strand // We will assume that the input file is has a uniform vertex count. // If it doesnt, the import will fail. int maxVerticesPerStrand = reader.ReadInt32(); int numGuideHairVertices = reader.ReadInt32(); int numGuideHairStrands = reader.ReadInt32(); /*int numFollowHairsPerOneGuideHair = */ reader.ReadInt32(); // Load hair data information int[] strandTypes = ReadIntegerArray(reader, numStrands); Vector4[] referenceVectors = ReadVector4Array(reader, numVertices); Quaternion[] globalRotations = ReadQuaternionArray(reader, numVertices); Quaternion[] localRotations = ReadQuaternionArray(reader, numVertices); Vector4[] vertices = ReadVector4Array(reader, numVertices); Vector4[] tangents = ReadVector4Array(reader, numVertices); // Read the triangle vertices // Actually those are __NEVER__ used anywhere. // So, we are going to skip / ignore them ReadVector3Array(reader, numVertices); ReadVector3Array(reader, numVertices); ReadVector4Array(reader, numVertices); float[] thicknessCoefficients = ReadFloatArray(reader, numVertices); Vector4[] followRootOffsets = ReadVector4Array(reader, numStrands); float[] restLengths = ReadFloatArray(reader, numVertices); // Get bounding sphere hair.InitializeBoundingSphere(ReadVector3(reader), reader.ReadSingle()); // Load indices int indexTmp = reader.ReadInt32(); int[] triangleIndices = ReadIntegerArray(reader, indexTmp); indexTmp = reader.ReadInt32(); int[] lineIndices = ReadIntegerArray(reader, indexTmp); // Init indices hair.InitializeIndices(lineIndices, triangleIndices); // Set simulation data HairSimulationData simulationData = new HairSimulationData(); simulationData.followRootOffsets = followRootOffsets.ToList(); simulationData.globalRotations = globalRotations.ToList(); simulationData.guideHairStrandCount = numGuideHairStrands; simulationData.guideHairVertexCount = numGuideHairVertices; simulationData.maxNumVerticesPerStrand = maxVerticesPerStrand; simulationData.localRotations = localRotations.ToList(); simulationData.referenceVectors = referenceVectors.ToList(); simulationData.strandTypes = strandTypes.ToList(); simulationData.tangents = tangents.ToList(); simulationData.thicknessCoefficients = thicknessCoefficients.ToList(); simulationData.vertices = vertices.ToList(); simulationData.restLength = restLengths.ToList(); hair.InitializeSimulationData(simulationData); // So, the data is read now. // Next, we are going to construct the hair meshes. HairMesh[] hairMeshes = new HairMesh[4]; // Current list "pointer" int vertexPointer = 0; int strandPointer = 0; List <HairStrand> strandReferences = new List <HairStrand>(); foreach (int meshIndex in strandTypes) { // Check if we got a valid strand types array if (meshIndex < 0 || meshIndex > 3) { throw new FormatException("Mesh ids (strand types) < 0 or > 3 are not supported by TressFX!"); } // Get mesh and create it if it doesnt exist if (hairMeshes[meshIndex] == null) { hairMeshes[meshIndex] = new HairMesh(); } HairMesh mesh = hairMeshes[meshIndex]; // Create new strand HairStrand strand = new HairStrand(); strandReferences.Add(strand); strand.followRootOffset = followRootOffsets[strandPointer]; if (strand.followRootOffset.x != 0 || strand.followRootOffset.y != 0 || strand.followRootOffset.z != 0) { // This is a follow hair strand.guidanceStrand = strandReferences[(int)strand.followRootOffset.w]; strand.isGuidanceStrand = false; } else { // This is a guidance hair strand.isGuidanceStrand = true; } // Read all vertices for (int i = 0; i < maxVerticesPerStrand; i++) { // Read vertex data // As texcoord we will create a coordinate which projects the hair along the x-axis based on the strand index // and the y-axis based on the vertex index inside of the current strand HairStrandVertex vertex = new HairStrandVertex(new Vector3(vertices[vertexPointer].X, vertices[vertexPointer].Y, vertices[vertexPointer].Z), new Vector3(tangents[vertexPointer].X, tangents[vertexPointer].Y, tangents[vertexPointer].Z), new Vector4(strandPointer / (float)numStrands, i / (float)maxVerticesPerStrand, 0, 0)); // Set movable flag vertex.isMovable = (vertices[vertexPointer].W > 0); // Set simulation data vertex.globalRotation = globalRotations[vertexPointer]; vertex.localRotation = localRotations[vertexPointer]; vertex.referenceVector = referenceVectors[vertexPointer]; vertex.restLength = restLengths[vertexPointer]; vertex.thicknessCoefficient = thicknessCoefficients[vertexPointer]; // Add to strand strand.vertices.Add(vertex); vertexPointer++; } // Add strand to mesh mesh.strands.Add(strand); strandPointer++; } // Construct return array List <HairMesh> meshes = new List <HairMesh>(); for (int i = 0; i < hairMeshes.Length; i++) { if (hairMeshes[i] == null) { // We're done reading break; } else { // Read the mesh meshes.Add(hairMeshes[i]); } } returnData = hairMeshes.ToArray(); return(returnData); }
public void Import(out float3[] vertices, out HairStrand[] strands) { vertices = new float3[0]; strands = new HairStrand[0]; }
public override void OnInspectorGUI() { var target = (this.target as HairAsset); // Render importer var importers = HairImport.GetImporters(); int currentSelected = importers.IndexOf(this.currentImporter); int newSelected = EditorGUILayout.Popup("Importer: ", currentSelected == -1 ? 0 : currentSelected, importers.Select((importer) => importer.displayName).ToArray()); if (currentSelected != newSelected) { this.currentImporter = importers[newSelected]; } // Importer set? if (!ReferenceEquals(this.currentImporter, null)) { EditorGUI.BeginDisabledGroup(!this.currentImporter.OnInspectorGUI(target)); if (GUILayout.Button("Import")) { // Temporary variables Vector3[] vertices; HairStrand[] strands; // Import this.currentImporter.Import(out vertices, out strands); target.InitializeVertices(vertices); target.InitializeStrands(strands); target.InitializeMovability(); target.wasImported = true; // Mark dirty EditorUtility.SetDirty(target); } EditorGUI.EndDisabledGroup(); } // Render actual inspector if (target.wasImported) { EditorGUILayout.LabelField("Strand count: " + target.strandCount); EditorGUILayout.LabelField("Vertex count: " + target.vertexCount); // TODO: Implement post processing modules if (GUILayout.Button("Set standard Movability")) { uint[] movability = HairMovability.CreateData(target.vertexCount); HairStrand[] strands = target.GetStrandData(); for (int i = 0; i < strands.Length; i++) { HairStrand strand = strands[i]; HairMovability.SetMovable(strand.firstVertex, false, movability); int cnt = (strand.lastVertex - strand.firstVertex) + 1; for (int j = 1; j < cnt; j++) { HairMovability.SetMovable(strand.firstVertex + j, true, movability); } } target.InitializeMovability(movability); // Mark dirty EditorUtility.SetDirty(target); } } }