/// <inheritdoc /> public PackingResult FindMinimumBoundingBox(IEnumerable <PPRect> rects, CancellationToken cancellationToken = default) { if (!rects.Any()) { return(new PackingResult(0, 0, rects)); } PackingResult bestPacking = null; foreach ((int W, int H) in GetDimensionsToTry(rects)) { if (cancellationToken.IsCancellationRequested) { return(bestPacking); } PackingResult currPacking = placementAlgorithm.PlaceRects(W, H, rects, cancellationToken); if (bestPacking == null || (currPacking != null && currPacking.Width * currPacking.Height < bestPacking.Width * bestPacking.Height)) { bestPacking = currPacking; } } if (bestPacking != null && (!IsPoT(bestPacking.Width) || !IsPoT(bestPacking.Height))) { return(new PackingResult(NextPoT(bestPacking.Width), NextPoT(bestPacking.Height), bestPacking.Rects)); } return(bestPacking); }
/// <summary> /// Checkes whether the packing result is valid /// I.e. it checks whether there are no overlaps between the rectangles /// And that it fit into result.Width x result.Height rectangle /// </summary> /// <param name="result">Packing result to be checked</param> /// <exception cref="ArgumentNullException">Is thrown when the <paramref name="result"/> is null</exception> /// <returns>true if the <paramref name="result"/> is valid, false otherwise</returns> public static bool IsPackingResultValid(PackingResult result) { if (result == null) { throw new ArgumentNullException($"The {nameof(result)} cannot be null"); } var tmp = result.Rects.ToList(); var indices = Enumerable.Range(0, tmp.Count); foreach (var i in indices) { foreach (var j in indices) { if (j < i) { if (tmp[i].IntersectsWith(tmp[j])) { return(false); } } } } return(tmp.Max(x => x.Right) <= result.Width && tmp.Max(x => x.Bottom) <= result.Height); }
/// <summary> /// Calculates the packing area fitness function /// </summary> /// <param name="result">The <see cref="PackingResult"/> for which the packing area fitness will be calculated</param> /// <returns>The packing area fitness function</returns> private static double PackingAreaFitnessFunction(PackingResult result) { if (result == null) { return(1.0 / double.MaxValue); } return(1.0 / (result.Width * result.Height)); }
public static Rect[] Combine(Texture2D texture, Texture2D[] textures, int width, int height, bool forceSquare, int padding, int maxSize) { PackingResult result = new PackingResult(); if (!TexCombine.Packing(ref result, textures, width, height, forceSquare, padding, maxSize)) { return(null); } width = result.width; height = result.height; texture.Resize(width, height); texture.SetPixels(new Color[width * height]); // The returned rects Rect[] rects = new Rect[textures.Length]; for (int i = 0; i < textures.Length; i++) { Texture2D tex = textures[i]; if (tex == null) { continue; } Rect rect = result.data[i].rect; int xPadding = (result.data[i].paddingX ? padding : 0); int yPadding = (result.data[i].paddingY ? padding : 0); Color[] colors = tex.GetPixels(); // Would be used to rotate the texture if need be. if (rect.width != tex.width + xPadding) { Color[] newColors = tex.GetPixels(); for (int x = 0; x < rect.width; x++) { for (int y = 0; y < rect.height; y++) { int prevIndex = ((int)rect.height - (y + 1)) + x * (int)tex.width; newColors[x + y * (int)rect.width] = colors[prevIndex]; } } colors = newColors; } texture.SetPixels((int)rect.x, (int)rect.y, (int)rect.width - xPadding, (int)rect.height - yPadding, colors); rect.x /= width; rect.y /= height; rect.width = (rect.width - xPadding) / width; rect.height = (rect.height - yPadding) / height; rects[i] = rect; } texture.Apply(); return(rects); }
/// <summary> /// Calculates a trim loss value for a given <paramref name="result"/> /// </summary> /// <param name="result">The <see cref="PackingResult"/> for which the trimm loss will be calculated</param> /// <returns>The trim loss value</returns> private static double TrimLossValue(PackingResult result) { if (result == null) { return(double.MaxValue); } double areaOfWholePacking = result.Width * result.Height; double sumOfAreasOfSubRects = result.Rects.Select(x => x.Width * x.Height).Sum(); return((areaOfWholePacking - sumOfAreasOfSubRects) / areaOfWholePacking); }
public PackingResult <TexturedTile, TexturedTileSegment> Pack(bool commitToLevel) { PackingResult <TexturedTile, TexturedTileSegment> result = Pack(); if (result.OrphanCount == 0 && commitToLevel) { Commit(); } return(result); }
/// <summary> /// Creates a texture atlas from a given packing result /// </summary> /// <param name="packing"></param> /// <exception cref="ArgumentNullException">Is thrown when the <paramref name="packing"/> is null</exception> public TextureAtlas(PackingResult packing) { if (packing == null) { throw new ArgumentNullException($"The {nameof(packing)} cannot be null"); } Width = packing.Width; Height = packing.Height; Rects = packing.Rects; bmp = new Lazy <SKBitmap>(() => GetBitmap()); image = new Lazy <PPImage>(GetImage); }
/// <inheritdoc /> public PackingResult FindMinimumBoundingBox(IEnumerable <PPRect> rects, System.Threading.CancellationToken cancellationToken = default) { if (!rects.Any()) { return(new PackingResult(0, 0, Enumerable.Empty <PPRect>())); } (int currBestW, int currBestH, int area, int maxRectW) = GetInitialBoundary(rects); int currW = currBestW - 1, currH = currBestH; PackingResult bestPacking = placementAlgo.PlaceRects(currBestW, currBestH, rects); while (currW > maxRectW) { if (cancellationToken.IsCancellationRequested) { return(bestPacking); } if (currW * currH > currBestW * currBestH) { currW--; continue; } PackingResult currentPacking; if ((currentPacking = placementAlgo.PlaceRects(currW, currH, rects, cancellationToken)) != null) { if (currW * currH < currBestH * currBestW) { currBestW = currW; currBestH = currH; bestPacking = currentPacking; } currW--; } else { currH++; continue; } if (currW * currH < area) { currH++; } } return(bestPacking); }
//Another fitness function ... /// <summary> /// Calculates the packing trim loss fitness function /// </summary> /// <param name="result">The <see cref="PackingResult"/> for which the trim loss fitness will be calculated</param> /// <returns>The packing trim loss fitness function</returns> private static double PackingTrimLossFitnessFunction(PackingResult result) { return(1.0 / (Epsilon + TrimLossValue(result))); }
//Would make more sense to pass GeneticDataStructure instead, be we would not like to spend time of another decode call .. private static double FitnessFunction(PackingResult result) { return(PackingAreaFitnessFunction(result)); }
private static bool Packing(ref PackingResult result, Texture2D[] textures, int width, int height, bool forceSquare, int padding, int maxSize) { if (width > maxSize && height > maxSize) { return(false); } if (width > maxSize || height > maxSize) { int temp = width; width = height; height = temp; } if (forceSquare) { if (width > height) { height = width; } else if (height > width) { width = height; } } if (result.packing == null) { result.packing = new RectangularPacking(width, height, false); } else { result.packing.Reset(width, height, false); } result.width = width; result.height = height; if (result.data == null) { result.data = new PackingData[textures.Length]; } for (int i = 0; i < textures.Length; i++) { Texture2D tex = textures[i]; if (tex == null) { continue; } Rect rect = default(Rect); int xPadding = 1; int yPadding = 1; for (xPadding = 1; xPadding >= 0; --xPadding) { for (yPadding = 1; yPadding >= 0; --yPadding) { rect = result.packing.Insert(tex.width + (xPadding * padding), tex.height + (yPadding * padding), RectangularPacking.Heuristic.BestAreaFit); if (rect.width != 0 && rect.height != 0) { break; } // After having no padding if it still doesn't fit -- increase texture size. else if (xPadding == 0 && yPadding == 0) { return(TexCombine.Packing(ref result, textures, width * (width <= height ? 2 : 1), height * (height < width ? 2 : 1), forceSquare, padding, maxSize)); } } if (rect.width != 0 && rect.height != 0) { break; } } result.data[i].rect = rect; result.data[i].paddingX = (xPadding != 0); result.data[i].paddingY = (yPadding != 0); } return(true); }
// todo show which reqs are fulfilled and which not public override bool GetPacking(List <PackingRequest> request, TrackingSpaceRoot ram, out Dictionary <PackingRequest, PackingResult> result) { var placeableObjects = new List <PlaceableObject>(); foreach (var req in request) { var obj = new PlaceableObject((int)Math.Ceiling(req.Size.x), (int)Math.Ceiling(req.Size.y), req.Places, _convertToSideRequirement(req.WallY), _convertToSideRequirement(req.WallX), req.IsSemiWall, req.PlaceFarAway); obj.Request = req; placeableObjects.Add(obj); } var tilesAvailable = (bool[, ])ram.TileAvailable.Clone(); DeterministicPacker packer = new DeterministicPacker(true); var bestWallScore = int.MaxValue; List <PlacedObject> bestPlacedObjs = null; for (int i = 0; i < InitialRunsPacker; i++) { float acceptProbability = (float)i / InitialRunsPacker; List <PlacedObject> placedObjs; var firstFitScore = packer.Pack(out placedObjs, placeableObjects, tilesAvailable, ExecutionMode == Modes.Performance ? PlacementScoreRequirement.OnlyBestWall : PlacementScoreRequirement.PreferBest, false, acceptProbability); if (placedObjs == null) { continue; } var wallScore = DeterministicPacker.GetScore(placedObjs, true); if (wallScore <= bestWallScore) { bestPlacedObjs = placedObjs; bestWallScore = wallScore; } } if (bestPlacedObjs == null) { result = null; return(false); } if (ExecutionMode == Modes.Quality) { List <PlacedObject> annealedPlacedObjs = null; var annealingScore = packer.ImprovePackingAccordingToPlacementScores(out annealedPlacedObjs, bestPlacedObjs); var wallScore = DeterministicPacker.GetScore(annealedPlacedObjs, true); if (wallScore > 0) { result = null; return(false); } bestPlacedObjs = annealedPlacedObjs; } // convert PackedObject to PackingResult result = new Dictionary <PackingRequest, PackingResult>(); foreach (var placedObj in bestPlacedObjs) { var placeableObj = placedObj.PlaceableObject; var xDim = placedObj.Flipped ? placeableObj.Dimensions.Y : placeableObj.Dimensions.X; var yDim = placedObj.Flipped ? placeableObj.Dimensions.X : placeableObj.Dimensions.Y; var res = new PackingResult() { Allocation = new Vector2Int(xDim, yDim), Pointer = new Vector2Int(placedObj.Position.X, placedObj.Position.Y), Reverse = placedObj.Flipped }; result[placeableObj.Request] = res; } return(true); }
/// <inheritdoc /> public PackingResult PlaceRects(int width, int height, IEnumerable <PPRect> rects, CancellationToken token = default) { Progress = 0; if (width < 0 || height < 0) { throw new ArgumentOutOfRangeException($"The {nameof(width)} and {nameof(height)} should be non-negative"); } var sortedInput = sorter.SortImages(rects); int inputSize = rects.Count(); int placedRects = 0; int actualWidth = 0; int actualHeight = 0; RectComparer rectComparer = new RectComparer(); PointComparer ptComparer = new PointComparer(); SortedSet <PPRect> currentPacking = new SortedSet <PPRect>(rectComparer); SortedDictionary <SKPointI, int> pointsToTry = new SortedDictionary <SKPointI, int>(ptComparer) { { new SKPointI(0, 0), -1 } //the current packing is empty, so only point to try is point [0,0] }; SKPointI[] pointsToAdd = new SKPointI[2]; foreach (var x in sortedInput) { if (token.IsCancellationRequested) { Progress = 0; return(null); } SKPointI?pointToRemove = null; foreach (var ptToTry in pointsToTry) { PPRect tested = new PPRect(ptToTry.Key.X, ptToTry.Key.Y, ptToTry.Key.X + x.Width, ptToTry.Key.Y + x.Height); var possibleIntersections = currentPacking.AsEnumerable(); //have to test everything if (ptToTry.Key.X + x.Width <= width && ptToTry.Key.Y + x.Height <= height && !Intersects(tested, possibleIntersections)) //safe to pack here { if (ptToTry.Key.X + x.Width > actualWidth) { actualWidth = ptToTry.Key.X + x.Width; } if (ptToTry.Key.Y + x.Height > actualHeight) { actualHeight = ptToTry.Key.Y + x.Height; } int improved = 0; if (TryImprove(ref tested, currentPacking, 0)) //Try to position it further to the top / left { improved++; } //Add it to the packing tested.Image = x.Image; currentPacking.Add(tested); if (improved == 0) { pointToRemove = ptToTry.Key; } pointsToAdd[0] = new SKPointI(ptToTry.Key.X + x.Width, ptToTry.Key.Y); pointsToAdd[1] = new SKPointI(ptToTry.Key.X, ptToTry.Key.Y + x.Height); break; } } if (pointToRemove != null) { pointsToTry.Remove(pointToRemove.Value); pointsToTry[pointsToAdd[0]] = -1; pointsToTry[pointsToAdd[1]] = -1; Progress = (int)((++placedRects / (double)inputSize) * 100.0); ProgressChange?.Invoke(this, Progress); } else { Progress = 100; return(null); //we cannot pack it anywhere } } //var result = new PackingResult(width, height, currentPacking.Select(x => (x.Value, x.Key))); // probably better to return result with actual width & height instead of those needed //actual height can be lower than height specified, width also BUT THIS IS NOT DESIRED, BECAUSE THIS CAN BE CALLED FROM FIXEDSIZE..? OR chhange size in FixedSize.. var result = new PackingResult(actualWidth, actualHeight, currentPacking); return(result); }