static void Main(string[] args) { // Check args length if (args.Length < 1) { LogToConsole("ERROR! Wrong syntax! asetotfx.exe [ASE FILE] [TFX PREFIX]", ConsoleColor.Red); return; } // Load args string asefile = args[0]; string hairFilePrefix = args[1]; // Start parsing the file LogToConsole("Loading ASE File...", ConsoleColor.Blue); string[] aseContent = File.ReadAllLines(asefile); List <TressFXStrand[]> currentStrands = new List <TressFXStrand[]>(); int currentStrand = 0; int currentHairId = -1; float texcoordMultiplier = 0; LogToConsole("Starting ASE parsing... This may take a LONG while..", ConsoleColor.Blue); // 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") { currentStrands.Add(new TressFXStrand[int.Parse(tokens[1])]); currentStrand = 0; currentHairId++; texcoordMultiplier = 1.0f / (float)int.Parse(tokens[1]); LogToConsole("Starting parse hair: " + currentHairId + ", lines count: " + int.Parse(tokens[1]), ConsoleColor.Yellow); } else if (tokens[0] == "*SHAPE_LINE") { // Parse the current line Vector3[] positions = null; string[] vertexCountTokens = aseContent[i + 1].Split(' '); positions = new Vector3[int.Parse(vertexCountTokens[1])]; // Parse vertices for (int j = 0; j < positions.Length; j++) { string[] vertexTokens = aseContent[i + 2 + j].Replace('.', ',').Split('\t'); positions[j] = new Vector3(float.Parse(vertexTokens[4]), float.Parse(vertexTokens[5]), float.Parse(vertexTokens[6])); } currentStrands[currentHairId][currentStrand] = new TressFXStrand(); currentStrands[currentHairId][currentStrand].vertices = positions; currentStrands[currentHairId][currentStrand].texcoordX = texcoordMultiplier * currentStrand; i = i + 1 + positions.Length; currentStrand++; } } } LogToConsole("Asefile Parsed! Hairs parsed: " + currentHairId + " starting TressFX Export...", ConsoleColor.Green); // Build TFX files for (int i = 0; i < currentStrands.Count; i++) { LogToConsole("Exporting hair " + i + "...", ConsoleColor.Yellow); StringBuilder currentHairBuilder = new StringBuilder(); currentHairBuilder.Append("numStrands " + currentStrands[i].Length + "\r\nis sorted 1\r\n"); // Write strands for (int j = 0; j < currentStrands[i].Length; j++) { currentHairBuilder.Append("strand " + j + " numVerts " + currentStrands[i][j].vertices.Length + " texcoord " + currentStrands[i][j].texcoordX + " 000000\r\n"); for (int k = 0; k < currentStrands[i][j].vertices.Length; k++) { currentHairBuilder.Append(currentStrands[i][j].vertices[k].x + " " + currentStrands[i][j].vertices[k].y + " " + currentStrands[i][j].vertices[k].z + "\r\n"); } } File.WriteAllText(hairFilePrefix + "_" + i + ".txt", currentHairBuilder.ToString().Replace(",", ".")); LogToConsole("Exported hair " + i + "!", ConsoleColor.Green); currentStrands[i] = null; } LogToConsole("Everything done :->", ConsoleColor.Green); Console.ReadLine(); }
/// <summary> /// Loads the hair tfx (text) file. /// This will generate the hair vertices and strandindices and store them in the passed lists. /// </summary> /// <param name="hairData">Hair mesh data (text from tfx file)</param> /// <param name="verticeList">The list where the vertices should go.</param> /// <param name="strandIndices">The list where the StrandIndices should go.</param> /// <param name="hairId">The HairID (starts at 0)</param> private int LoadHairTFX(string hairData, List <TressFXStrand> strandsList, int hairId) // string hairData, List<Vector4> verticeList, List<StrandIndex> strandIndices, List<int> verticesOffsets, int hairId) //, List<Vector3> normalList) { int vertexCount = 0; // Start parsing hair file string[] hairLines = hairData.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); // Wrong line endings? if (hairLines.Length < 2) { hairLines = hairData.Split(new string[] { "\n" }, StringSplitOptions.None); } // Properties int numStrands = 0; // Read every line of the data int i = 0; while (i < hairLines.Length) { string[] stringTokens = hairLines[i].Split(' '); // Hair definition if (stringTokens[0] == "numStrands") { // Strands definition numStrands = int.Parse(stringTokens[1]); } // Strand definition else if (stringTokens[0] == "strand") { // Parse informations about the strand // int strandNum = int.Parse(stringTokens[1]); int numStrandVertices = int.Parse(stringTokens[3]); float texcoordX = float.Parse(stringTokens[5]); TressFXStrand strand = new TressFXStrand(numStrandVertices); // Used for corruption check // If a strand or just one vertex of it is corrupted it will get ignored bool corrupted = false; int j; // Read all vertices for (j = 0; j < numStrandVertices; j++) { // String tokens string[] vertexData = hairLines[i + 1].Split(' '); // Strand corrupted? if (vertexData[0] == "-1.#INF" || vertexData[1] == "-1.#INF" || vertexData[2] == "-1.#INF") { corrupted = true; break; } // invMass = 1 means strand movable, 0 means not movable float invMass = 1.0f; if (j == 0 || j == 1 || (j == numStrandVertices - 1 && this.hairs[hairId].makeBothEndsImmovable)) { invMass = 0.0f; } // Set TressFX Strand data strand.vertices[j] = new TressFXVertex(); strand.vertices[j].pos = new Vector3(float.Parse(vertexData[0]), // X float.Parse(vertexData[1]), // Y float.Parse(vertexData[2])); // Z strand.vertices[j].pos.Scale(this.transform.lossyScale); // Load UVs strand.vertices[j].texcoords.x = texcoordX; strand.vertices[j].texcoords.y = (1.0f / (float)numStrandVertices) * (float)(j + 1); strand.vertices[j].invMass = invMass; strand.hairId = hairId; i++; } // Corrupted strands if (corrupted) { // Delete this strand numStrands--; } else { vertexCount += j; strandsList.Add(strand); } } i++; } return(vertexCount); }