/// <summary> /// Updates all UV coordinates (Texture Vertex) based on /// the rectangular transforms which may be included in /// slicing options if texture partitioning has occurred. /// </summary> public void TransformUVs(SlicingOptions options) { Trace.TraceInformation("Transforming {0} UV points across {1} extents", TextureList.Count, options.UVTransforms.Keys.Count); foreach (var uvTransform in options.UVTransforms) { TransformUVsForTextureTile(options, uvTransform.Key, uvTransform.Value, new CancellationToken()); } }
public void ProcessTextureTile(string outputPath, Vector2 textureTile, SlicingOptions options, CancellationToken cancellationToken) { Trace.TraceInformation("Processing texture tile {0}", textureTile); string fileOutPath = Path.Combine(outputPath, string.Format("{0}_{1}.jpg", textureTile.X, textureTile.Y)); // Generate new texture var transform = options.TextureInstance.GenerateTextureTile(fileOutPath, textureTile, options, cancellationToken); // Transform associated UV's ObjInstance.TransformUVsForTextureTile(options, textureTile, transform, cancellationToken); }
public CubeManager(SlicingOptions options) { size = options.CubeGrid; // Parse and load the object Trace.TraceInformation("Loading {0}", options.Obj); ObjInstance = new Obj(); ObjInstance.LoadObj(options.Obj, ShowLinesLoaded, size, options); // Write out a bit of info about the object Trace.TraceInformation("Loaded {0} vertices and {1} faces", ObjInstance.VertexList.Count(), ObjInstance.FaceList.Count()); Trace.TraceInformation("Size: X {0} Y {1} Z {2}", ObjInstance.Size.XSize, ObjInstance.Size.YSize, ObjInstance.Size.ZSize); Trace.TraceInformation("Memory Used: " + GC.GetTotalMemory(true) / 1024 / 1024 + "mb"); }
public int WriteSpecificCube(string path, Vector3 cube, SlicingOptions options) { string objPath = path + ".obj"; string eboPath = path + ".ebo"; string openCtmPath = path + ".ctm"; // Delete files or handle resume if the required ones already exist CleanOldFiles(options, objPath, eboPath, openCtmPath); // Revert all vertices in case we previously changed their indexes if (_verticesRequireReset) { FaceList.AsParallel().ForAll(f => f.RevertVertices()); _verticesRequireReset = false; } // Get all faces in this cube List <Face> chunkFaceList; chunkFaceList = FaceMatrix[cube.X, cube.Y, cube.Z]; if (!chunkFaceList.Any()) { return(0); } Trace.TraceInformation("{0} faces", chunkFaceList.Count); if (options.GenerateEbo) { WriteEboFormattedFile(eboPath, options.OverrideMtl, chunkFaceList); } var tile = Texture.GetTextureCoordFromCube(options.TextureSliceY, options.TextureSliceX, cube.X, cube.Y, this); if (options.GenerateObj) { string comment = string.Format("Texture Tile {0},{1}", tile.X, tile.Y); WriteObjFormattedFile(objPath, options.OverrideMtl, chunkFaceList, tile, options, comment); chunkFaceList.AsParallel().ForAll(f => f.RevertVertices()); } if (options.GenerateOpenCtm) { WriteOpenCtmFormattedFile(openCtmPath, chunkFaceList, tile); } return(chunkFaceList.Count); }
private static void CleanOldFiles(SlicingOptions options, string objPath, string eboPath, string ctmPath) { if (!Directory.Exists(Path.GetDirectoryName(objPath))) { Directory.CreateDirectory(Path.GetDirectoryName(objPath)); } if (options.GenerateObj) { File.Delete(objPath); } if (options.GenerateEbo) { File.Delete(eboPath); File.Delete(GetEbo2Path(eboPath)); } if (options.GenerateOpenCtm) { File.Delete(ctmPath); } }
public void GenerateCubes(string outputPath, SlicingOptions options) { CubeMetadata metadata = new CubeMetadata(size) { WorldBounds = ObjInstance.Size, VirtualWorldBounds = options.ForceCubicalCubes ? ObjInstance.CubicalSize : ObjInstance.Size, VertexCount = ObjInstance.VertexList.Count }; // Configure texture slicing metadata if (options.RequiresTextureProcessing()) { metadata.TextureSetSize = new Vector2(options.TextureSliceX, options.TextureSliceY); } else { metadata.TextureSetSize = new Vector2(1, 1); } // Create texture if there is one if (!string.IsNullOrEmpty(options.Texture)) { Texture t = new Texture(this.ObjInstance, options.Texture); options.TextureInstance = t; } // Generate the data if (options.Debug) { SpatialUtilities.EnumerateSpace(metadata.TextureSetSize, (x, y) => { var vertexCounts = GenerateCubesForTextureTileAsync(outputPath, new Vector2(x, y), options, new CancellationToken()).Result; foreach (var cube in vertexCounts.Keys) { metadata.CubeExists[cube.X, cube.Y, cube.Z] = vertexCounts[cube] > 0; } }); } else { SpatialUtilities.EnumerateSpaceParallel(metadata.TextureSetSize, (x, y) => { var vertexCounts = GenerateCubesForTextureTileAsync(outputPath, new Vector2(x, y), options, new CancellationToken()).Result; foreach (var cube in vertexCounts.Keys) { metadata.CubeExists[cube.X, cube.Y, cube.Z] = vertexCounts[cube] > 0; } }); } // Write out some json metadata string metadataPath = Path.Combine(outputPath, "metadata.json"); if (File.Exists(metadataPath)) { File.Delete(metadataPath); } string metadataString = JsonConvert.SerializeObject(metadata); File.WriteAllText(metadataPath, metadataString); }
public async Task <Dictionary <Vector3, int> > GenerateCubesForTextureTileAsync(string outputPath, Vector2 textureTile, SlicingOptions options, CancellationToken cancellationToken) { // If appropriate, generate textures and save transforms first if (options.Texture != null) { await Task.Run(() => ProcessTextureTile(Path.Combine(outputPath, TextureSubDirectory), textureTile, options, cancellationToken), cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); Dictionary <Vector3, int> vertexCounts = new Dictionary <Vector3, int>(); // Generate some cubes var cubes = Texture.GetCubeListFromTextureTile(options.TextureSliceY, options.TextureSliceX, textureTile.X, textureTile.Y, ObjInstance).ToList(); cubes.ForEach(v => { cancellationToken.ThrowIfCancellationRequested(); Trace.TraceInformation("Processing cube {0}", v); string fileOutPath = Path.Combine(outputPath, string.Format("{0}_{1}_{2}", v.X, v.Y, v.Z)); int vertexCount = ObjInstance.WriteSpecificCube(fileOutPath, v, options); vertexCounts.Add(v, vertexCount); }); return(vertexCounts); }
private void WriteObjFormattedFile(string path, string mtlOverride, List <Face> chunkFaceList, Vector2 tile, SlicingOptions options, string comment = "") { // Build a list of vertices indexes needed for these faces List <int> requiredVertices = null; List <int> requiredTextureVertices = null; var tv = Task.Run(() => { requiredVertices = chunkFaceList.AsParallel().SelectMany(f => f.VertexIndexList).Distinct().ToList(); }); var ttv = Task.Run(() => { requiredTextureVertices = chunkFaceList.AsParallel().SelectMany(f => f.TextureVertexIndexList).Distinct().ToList(); }); Task.WaitAll(new Task[] { tv, ttv }); using (var outStream = File.OpenWrite(path)) using (var writer = new StreamWriter(outStream)) { // Write some header data writer.WriteLine("# Generated by PyriteCli"); if (!string.IsNullOrEmpty(comment)) { writer.WriteLine("# " + comment); } if (!string.IsNullOrEmpty(mtlOverride)) { writer.WriteLine("mtllib " + mtlOverride); } else if (!string.IsNullOrEmpty(_mtl) && !options.RequiresTextureProcessing()) { writer.WriteLine("mtllib " + _mtl); } else if (options.RequiresTextureProcessing() && options.WriteMtl) { writer.WriteLine("mtllib texture\\{0}_{1}.mtl", tile.X, tile.Y); } // Write each vertex and update faces _verticesRequireReset = true; int newVertexIndex = 0; Parallel.ForEach(requiredVertices, i => { Vertex moving = VertexList[i - 1]; int newIndex = WriteVertexWithNewIndex(moving, ref newVertexIndex, writer); var facesRequiringUpdate = chunkFaceList.Where(f => f.VertexIndexList.Contains(i)); foreach (var face in facesRequiringUpdate) { face.UpdateVertexIndex(moving.Index, newIndex); } }); // Write each texture vertex and update faces int newTextureVertexIndex = 0; Parallel.ForEach(requiredTextureVertices, i => { TextureVertex moving = TextureList[i - 1]; int newIndex = WriteVertexWithNewIndex(moving, ref newTextureVertexIndex, writer); var facesRequiringUpdate = chunkFaceList.Where(f => f.TextureVertexIndexList.Contains(i)); foreach (var face in facesRequiringUpdate) { face.UpdateTextureVertexIndex(moving.Index, newIndex); } }); // Write the faces chunkFaceList.ForEach(f => writer.WriteLine(f)); } }
/// <summary> /// Parse and load an OBJ file into memory. Will consume memory /// at aproximately 120% the size of the file. /// </summary> /// <param name="path">path to obj file on disk</param> /// <param name="linesProcessedCallback">callback for status updates</param> public void LoadObj(string path, Action <int> linesProcessedCallback, Vector3 gridSize, SlicingOptions options) { VertexList = new List <Vertex>(); FaceList = new List <Face>(); FaceMatrix = new List <Face> [gridSize.X, gridSize.Y, gridSize.Z]; TextureList = new List <TextureVertex>(); var input = File.ReadLines(path); int linesProcessed = 0; foreach (string line in input) { processLine(line); // Handle a callback for a status update linesProcessed++; if (linesProcessedCallback != null && linesProcessed % 1000 == 0) { linesProcessedCallback(linesProcessed); } } if (linesProcessedCallback != null) { linesProcessedCallback(linesProcessed); } updateSize(); populateMatrix(FaceList, FaceMatrix, options.ForceCubicalCubes ? CubicalSize : Size); _verticesRequireReset = false; }
public void TransformUVsForTextureTile(SlicingOptions options, Vector2 textureTile, RectangleTransform[] uvTransforms, CancellationToken cancellationToken) { Trace.TraceInformation("Transforming UV points for texture tile {0},{1}", textureTile.X, textureTile.Y); int newUVCount = 0, failedUVCount = 0, transformUVCount = 0; cancellationToken.ThrowIfCancellationRequested(); var faces = Texture.GetFaceListFromTextureTile( options.TextureSliceY, options.TextureSliceX, textureTile.X, textureTile.Y, this); var uvIndices = faces.AsParallel().SelectMany(f => f.TextureVertexIndexList).WithCancellation(cancellationToken).Distinct(); var uvs = uvIndices.Select(i => TextureList[i - 1]).ToList(); Trace.TraceInformation("Selected UVs"); foreach (var uv in uvs) { cancellationToken.ThrowIfCancellationRequested(); var transforms = uvTransforms.Where(t => t.ContainsPoint(uv.OriginalX, uv.OriginalY)); if (transforms.Any()) { RectangleTransform transform = transforms.First(); lock (uv) { if (uv.Transformed) { // This was already transformed in another extent, so we'll have to copy it int newIndex = uv.CloneOriginal(TextureList); TextureList[newIndex - 1].Transform(transform); // Update all faces using the old UV in this extent faces.AsParallel().Where(f => f.TextureVertexIndexList.Contains(uv.Index)).WithCancellation(cancellationToken).ForAll(face => face.UpdateTextureVertexIndex(uv.Index, newIndex, false)); newUVCount++; } else { uv.Transform(transform); transformUVCount++; } } } else { failedUVCount++; } } Trace.TraceInformation("UV Transform results ({3},{4}): {0} success, {1} new, {2} failed.", transformUVCount, newUVCount, failedUVCount, textureTile.X, textureTile.Y); // Write out a marked up image file showing where lost UV's occured if (options.Debug) { var notTransformedUVs = uvs.Where(u => !u.Transformed).ToArray(); var relevantTransforms = uvTransforms; options.TextureInstance.MarkupTextureTransforms(options.Texture, relevantTransforms, notTransformedUVs, textureTile); } }
// The z axis is collapsed for the purpose of texture slicing. // Texture tiles correlate to a column of mesh data which is unbounded in the Z axis. public RectangleTransform[] GenerateTextureTile(string outputPath, Vector2 tile, SlicingOptions options, CancellationToken cancellationToken) { List <Face> chunkFaceList = GetFaceListFromTextureTile(options.TextureSliceY, options.TextureSliceX, tile.X, tile.Y, TargetObj).ToList(); if (!chunkFaceList.Any()) { Trace.TraceInformation("No faces found in tile {0}. No texture generated.", tile); return(new RectangleTransform[0]); } Size originalSize; // Create a clone of the source to use independent of other threads Image clonedSource; lock (sourceLock) { clonedSource = (Image)source.Clone(); } try { originalSize = clonedSource.Size; Size newSize = new Size(); Trace.TraceInformation("Generating sparse texture for tile {0}", tile); // Identify blob rectangles var groupedFaces = FindConnectedFaces(chunkFaceList, cancellationToken); var uvRects = FindUVRectangles(groupedFaces); Rectangle[] sourceRects = TransformUVRectToBitmapRect(uvRects, originalSize, 3); // Bin pack rects, growing to a maximum 16384. // Estimate ideal bin size var totalArea = sourceRects.Sum(r => r.Height * r.Width); var startingSize = NextPowerOfTwo((int)Math.Sqrt(totalArea)); Rectangle[] destinationRects = PackTextures(sourceRects, startingSize, startingSize, 16384, cancellationToken); // Identify the cropped size of our new texture newSize.Width = destinationRects.Max <Rectangle, int>(r => r.X + r.Width); newSize.Height = destinationRects.Max <Rectangle, int>(r => r.Y + r.Height); // Round new texture size up to nearest power of 2 newSize.Width = NextPowerOfTwo(newSize.Width); newSize.Height = NextPowerOfTwo(newSize.Height); // Build the new bin packed and cropped texture WriteNewTexture(outputPath, options.TextureScale, newSize, clonedSource, sourceRects, destinationRects, cancellationToken); // Write an MTL if appropriate if (options.WriteMtl) { WriteNewMtl(outputPath, Path.ChangeExtension(outputPath, "mtl")); } // Generate the UV transform array return(GenerateUVTransforms(originalSize, newSize, sourceRects, destinationRects)); } finally { clonedSource.Dispose(); } }