Пример #1
0
 /// <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());
     }
 }
Пример #2
0
        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);
        }
Пример #3
0
        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");
        }
Пример #4
0
        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);
        }
Пример #5
0
        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);
            }
        }
Пример #6
0
        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);
        }
Пример #7
0
        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);
        }
Пример #8
0
        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));
                }
        }
Пример #9
0
        /// <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;
        }
Пример #10
0
        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);
            }
        }
Пример #11
0
        // 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();
            }
        }