Vector2[] _getMeshUV2s(Mesh m) { Vector2[] uv = m.uv2; if (uv.Length == 0) { if (this.mc.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no uv2s. Generating"); } if (this.mc.LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Mesh " + m + " didn't have uv2s. Generating uv2s."); } if (this.mc._lightmapOption == MB2_LightmapOptions.copy_UV2_unchanged_to_separate_rects) { Debug.LogError("Mesh " + m + " did not have a UV2 channel. Nothing to copy when trying to copy UV2 to separate rects. The combined mesh will not lightmap properly. Try using generate new uv2 layout."); } uv = new Vector2[m.vertexCount]; for (int i = 0; i < uv.Length; i++) { uv[i] = _HALF_UV; } } return(uv); }
public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource = true) { //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); //PART 1 ==== Validate if (_usingTemporaryTextureBakeResult && gos != null && gos.Length > 0) { MB_Utility.Destroy(_textureBakeResults); _textureBakeResults = null; _usingTemporaryTextureBakeResult = false; } //if all objects use the same material we can create a temporary _textureBakeResults if (_textureBakeResults == null && gos != null && gos.Length > 0 && gos[0] != null) { if (!_CreateTemporaryTextrueBakeResult(gos, GetMaterialsOnTargetRenderer())) { return(false); } } if (!_validate(gos, deleteGOinstanceIDs)) { return(false); } _distributeAmongBakers(gos, deleteGOinstanceIDs); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("MB2_MultiMeshCombiner.AddDeleteGameObjects numCombinedMeshes: " + meshCombiners.Count + " added:" + gos + " deleted:" + deleteGOinstanceIDs + " disableRendererInSource:" + disableRendererInSource + " maxVertsPerCombined:" + _maxVertsInMesh); } return(_bakeStep1(gos, deleteGOinstanceIDs, disableRendererInSource)); }
public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource = true) { if (this._usingTemporaryTextureBakeResult && gos != null && gos.Length > 0) { MB_Utility.Destroy(this._textureBakeResults); this._textureBakeResults = null; this._usingTemporaryTextureBakeResult = false; } if (this._textureBakeResults == null && gos != null && gos.Length > 0 && gos[0] != null && !this._CheckIfAllObjsToAddUseSameMaterialsAndCreateTemporaryTextrueBakeResult(gos)) { return(false); } if (!this._validate(gos, deleteGOinstanceIDs)) { return(false); } this._distributeAmongBakers(gos, deleteGOinstanceIDs); if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "MB2_MultiMeshCombiner.AddDeleteGameObjects numCombinedMeshes: ", this.meshCombiners.Count, " added:", gos, " deleted:", deleteGOinstanceIDs, " disableRendererInSource:", disableRendererInSource, " maxVertsPerCombined:", this._maxVertsInMesh }), new object[0]); } return(this._bakeStep1(gos, deleteGOinstanceIDs, disableRendererInSource)); }
public Vector2[] GetMeshUV1s(Mesh m, MB2_LogLevel LOG_LEVEL) { Vector2[] uv; #if (UNITY_4_6 || UNITY_4_7 || UNITY_4_5 || UNITY_4_3 || UNITY_4_2 || UNITY_4_1 || UNITY_4_0_1 || UNITY_4_0 || UNITY_3_5) uv = m.uv1; #else if (LOG_LEVEL >= MB2_LogLevel.warn) { MB2_Log.LogDebug("UV1 does not exist in Unity 5+"); } uv = m.uv; #endif if (uv.Length == 0) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no uv1s. Generating"); } if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Mesh " + m + " didn't have uv1s. Generating uv1s."); } uv = new Vector2[m.vertexCount]; for (int i = 0; i < uv.Length; i++) { uv[i] = _HALF_UV; } } return(uv); }
public Vector2[] GetMeshUV3orUV4(Mesh m, bool get3, MB2_LogLevel LOG_LEVEL) { Vector2[] uvs; #if (UNITY_4_6 || UNITY_4_7 || UNITY_4_5 || UNITY_4_3 || UNITY_4_2 || UNITY_4_1 || UNITY_4_0_1 || UNITY_4_0 || UNITY_3_5) if (LOG_LEVEL >= MB2_LogLevel.warn) { MB2_Log.LogDebug("UV3 and UV4 do not exist in Unity 4"); } uvs = m.uv; #else if (get3) { uvs = m.uv3; } else { uvs = m.uv4; } #endif if (uvs.Length == 0) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no uv" + (get3 ? "3" : "4") + ". Generating"); } uvs = new Vector2[m.vertexCount]; for (int i = 0; i < uvs.Length; i++) { uvs[i] = _HALF_UV; } } return(uvs); }
Vector4[] _getMeshTangents(Mesh m) { Vector4[] ts = m.tangents; if (ts.Length == 0) { if (this.mc.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no tangents. Generating"); } if (this.mc.LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Mesh " + m + " didn't have tangents. Generating tangents."); } Vector3[] verts = m.vertices; Vector2[] uvs = GetUv0Raw(m); Vector3[] norms = _getMeshNormals(m); ts = new Vector4[m.vertexCount]; for (int i = 0; i < m.subMeshCount; i++) { int[] tris = m.GetTriangles(i); _generateTangents(tris, verts, uvs, norms, ts); } } return(ts); }
public Vector2[] GetMeshUV3orUV4(Mesh m, bool get3, MB2_LogLevel LOG_LEVEL) { Vector2[] array; if (get3) { array = m.uv3; } else { array = m.uv4; } if (array.Length == 0) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "Mesh ", m, " has no uv", (!get3) ? "4" : "3", ". Generating" }), new object[0]); } array = new Vector2[m.vertexCount]; for (int i = 0; i < array.Length; i++) { array[i] = this._HALF_UV; } } return(array); }
public Vector2[] GetMeshUV1s(Mesh m, MB2_LogLevel LOG_LEVEL) { if (LOG_LEVEL >= MB2_LogLevel.warn) { MB2_Log.LogDebug("UV1 does not exist in Unity 5+", new object[0]); } Vector2[] array = m.uv; if (array.Length == 0) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no uv1s. Generating", new object[0]); } if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Mesh " + m + " didn't have uv1s. Generating uv1s."); } array = new Vector2[m.vertexCount]; for (int i = 0; i < array.Length; i++) { array[i] = this._HALF_UV; } } return(array); }
public Vector2[] GetMeshUVChannel(int channel, Mesh m, MB2_LogLevel LOG_LEVEL) { Vector2[] uvs = new Vector2[0]; switch (channel) { case 0: uvs = m.uv; break; case 2: uvs = m.uv2; break; case 3: uvs = m.uv3; break; case 4: uvs = m.uv4; break; #if UNITY_2018_2_OR_NEWER case 5: uvs = m.uv5; break; case 6: uvs = m.uv6; break; case 7: uvs = m.uv7; break; case 8: uvs = m.uv8; break; #endif default: Debug.LogError("Mesh does not have UV channel " + channel); break; } if (uvs.Length == 0) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no uv" + channel + ". Generating"); } uvs = new Vector2[m.vertexCount]; for (int i = 0; i < uvs.Length; i++) { uvs[i] = _HALF_UV; } } return(uvs); }
public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource = true) { //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); //PART 1 ==== Validate if (!_validate(gos, deleteGOinstanceIDs)) { return(false); } _distributeAmongBakers(gos, deleteGOinstanceIDs); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("MB2_MultiMeshCombiner.AddDeleteGameObjects numCombinedMeshes: " + meshCombiners.Count + " added:" + gos + " deleted:" + deleteGOinstanceIDs + " disableRendererInSource:" + disableRendererInSource + " maxVertsPerCombined:" + _maxVertsInMesh); } return(_bakeStep1(gos, deleteGOinstanceIDs, disableRendererInSource)); }
bool ProbeMultiAtlas(Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDimX, int maxAtlasDimY, ProbeResult pr) { int numAtlases = 0; Node root = new Node(NodeType.maxDim); root.r = new PixRect(0, 0, idealAtlasW, idealAtlasH); for (int i = 0; i < imgsToAdd.Length; i++) { Node n = root.Insert(imgsToAdd[i], false); if (n == null) { if (imgsToAdd[i].x > idealAtlasW && imgsToAdd[i].y > idealAtlasH) { return(false); } else { // create a new root node wider than previous atlas Node newRoot = new Node(NodeType.Container); newRoot.r = new PixRect(0, 0, root.r.w + idealAtlasW, idealAtlasH); // create a new right child Node newRight = new Node(NodeType.maxDim); newRight.r = new PixRect(root.r.w, 0, idealAtlasW, idealAtlasH); newRoot.child[1] = newRight; // insert root as a new left child newRoot.child[0] = root; root = newRoot; n = root.Insert(imgsToAdd[i], false); numAtlases++; } } } pr.numAtlases = numAtlases; pr.root = root; //todo atlas may not be maxDim * maxDim. Do some checking to see what actual needed sizes are and update pr.totalArea pr.totalAtlasArea = numAtlases * maxAtlasDimX * maxAtlasDimY; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Probe success efficiency numAtlases=" + numAtlases + " totalArea=" + pr.totalAtlasArea); } return(true); }
Vector3[] _getMeshNormals(Mesh m) { Vector3[] ns = m.normals; if (ns.Length == 0) { if (this.mc.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no normals. Generating"); } if (this.mc.LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Mesh " + m + " didn't have normals. Generating normals."); } Mesh tempMesh = (Mesh)GameObject.Instantiate(m); tempMesh.RecalculateNormals(); ns = tempMesh.normals; MB_Utility.Destroy(tempMesh); } return(ns); }
Vector2[] _getMeshUVs(Mesh m) { Vector2[] uv = m.uv; if (uv.Length == 0) { if (this.mc.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no uvs. Generating"); } if (this.mc.LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Mesh " + m + " didn't have uvs. Generating uvs."); } uv = new Vector2[m.vertexCount]; for (int i = 0; i < uv.Length; i++) { uv[i] = _HALF_UV; } } return(uv); }
Color[] _getMeshColors(Mesh m) { Color[] cs = m.colors; if (cs.Length == 0) { if (this.mc.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Mesh " + m + " has no colors. Generating"); } if (this.mc.LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Mesh " + m + " didn't have colors. Generating an array of white colors"); } cs = new Color[m.vertexCount]; for (int i = 0; i < cs.Length; i++) { cs[i] = Color.white; } } return(cs); }
bool Probe(Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDim, ProbeResult pr) { Node root = new Node(); root.r = new PixRect(0, 0, idealAtlasW, idealAtlasH); for (int i = 0; i < imgsToAdd.Length; i++) { Node n = root.Insert(imgsToAdd[i], false); if (n == null) { return(false); } else if (i == imgsToAdd.Length - 1) { int usedW = 0; int usedH = 0; GetExtent(root, ref usedW, ref usedH); float squareness; float efficiency = 1f - (usedW * usedH - imgArea) / (usedW * usedH); if (usedW < usedH) { squareness = (float)usedW / (float)usedH; } else { squareness = (float)usedH / (float)usedW; } bool fitsInMaxDim = usedW <= maxAtlasDim && usedH <= maxAtlasDim; pr.Set(usedW, usedH, root, fitsInMaxDim, efficiency, squareness); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Probe success efficiency w=" + usedW + " h=" + usedH + " e=" + efficiency + " sq=" + squareness + " fits=" + fitsInMaxDim); } return(true); } } Debug.LogError("Should never get here."); return(false); }
public Rect[] GetRects(List <Vector2> imgWidthHeights, int maxDimension, int padding, out int outW, out int outH) { float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { Image im = imgsToAdd[i] = new Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, padding); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } // List<Node> ns = new List<Node>(); //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW = sqrtArea; int idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } if (idealAtlasW == 0) { idealAtlasW = 1; } if (idealAtlasH == 0) { idealAtlasH = 1; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } // bool doStepHeight = true; // bool successH = false; int numWIterations = 2; int steppedHeight = idealAtlasH; while (numWIterations > 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; int steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (Probe(imgsToAdd, steppedWidth, steppedHeight, area, maxDimension, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore() > bestRoot.GetScore()) { bestRoot = pr; } } else { numWIterations++; steppedWidth += stepW; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight += stepH; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } outW = 0; outH = 0; if (bestRoot == null) { return(null); } if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Best fit found: w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.fitsInMaxSize); } outW = bestRoot.w; outH = bestRoot.h; List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); if (images.Count != imgsToAdd.Length) { Debug.LogError("Result images not the same lentgh as source"); } //scale images if too large float padX = (float)padding / (float)bestRoot.w; if (bestRoot.w > maxDimension) { padX = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.w; if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas width shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; int right = (int)((im.x + im.w) * scaleFactor); im.x = (int)(scaleFactor * im.x); im.w = right - im.x; if (im.w == 0) { Debug.LogError("rounding scaled image w to zero"); } } outW = maxDimension; } float padY = (float)padding / (float)bestRoot.h; if (bestRoot.h > maxDimension) { padY = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.h; if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas height shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; int bottom = (int)((im.y + im.h) * scaleFactor); im.y = (int)(scaleFactor * im.y); im.h = bottom - im.y; if (im.h == 0) { Debug.LogError("rounding scaled image h to zero"); } } outH = maxDimension; } Rect[] rs = new Rect[images.Count]; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r = rs[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2, (float)im.h / (float)outH - padY * 2); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + padding); } } if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Done GetRects"); } return(rs); }
private Rect[] _GetRects(List <Vector2> imgWidthHeights, int maxDimension, int padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, out int outW, out int outH, int recursionDepth) { if (this.LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log(string.Format("_GetRects numImages={0}, maxDimension={1}, padding={2}, minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}, recursionDepth={7}", new object[] { imgWidthHeights.Count, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth })); } if (recursionDepth > 10) { Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures."); outW = 0; outH = 0; return(new Rect[0]); } float num = 0f; int num2 = 0; int num3 = 0; MB2_TexturePacker.Image[] array = new MB2_TexturePacker.Image[imgWidthHeights.Count]; for (int i = 0; i < array.Length; i++) { MB2_TexturePacker.Image image = array[i] = new MB2_TexturePacker.Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, padding, minImageSizeX, minImageSizeY); num += (float)(image.w * image.h); num2 = Mathf.Max(num2, image.w); num3 = Mathf.Max(num3, image.h); } if ((float)num3 / (float)num2 > 2f) { if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using height Comparer", new object[0]); } Array.Sort <MB2_TexturePacker.Image>(array, new MB2_TexturePacker.ImageHeightComparer()); } else if ((double)((float)num3 / (float)num2) < 0.5) { if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using width Comparer", new object[0]); } Array.Sort <MB2_TexturePacker.Image>(array, new MB2_TexturePacker.ImageWidthComparer()); } else { if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using area Comparer", new object[0]); } Array.Sort <MB2_TexturePacker.Image>(array, new MB2_TexturePacker.ImageAreaComparer()); } int num4 = (int)Mathf.Sqrt(num); int num6; int num5; if (this.doPowerOfTwoTextures) { num5 = (num6 = this.RoundToNearestPositivePowerOfTwo(num4)); if (num2 > num6) { num6 = this.CeilToNearestPowerOfTwo(num6); } if (num3 > num5) { num5 = this.CeilToNearestPowerOfTwo(num5); } } else { num6 = num4; num5 = num4; if (num2 > num4) { num6 = num2; num5 = Mathf.Max(Mathf.CeilToInt(num / (float)num2), num3); } if (num3 > num4) { num6 = Mathf.Max(Mathf.CeilToInt(num / (float)num3), num2); num5 = num3; } } if (num6 == 0) { num6 = 1; } if (num5 == 0) { num5 = 1; } int num7 = (int)((float)num6 * 0.15f); int num8 = (int)((float)num5 * 0.15f); if (num7 == 0) { num7 = 1; } if (num8 == 0) { num8 = 1; } int num9 = 2; int num10 = num5; while (num9 >= 1 && num10 < num4 * 1000) { bool flag = false; num9 = 0; int num11 = num6; while (!flag && num11 < num4 * 1000) { MB2_TexturePacker.ProbeResult probeResult = new MB2_TexturePacker.ProbeResult(); if (this.LOG_LEVEL >= MB2_LogLevel.trace) { Debug.Log(string.Concat(new object[] { "Probing h=", num10, " w=", num11 })); } if (this.Probe(array, num11, num10, num, maxDimension, probeResult)) { flag = true; if (this.bestRoot == null) { this.bestRoot = probeResult; } else if (probeResult.GetScore(this.doPowerOfTwoTextures) > this.bestRoot.GetScore(this.doPowerOfTwoTextures)) { this.bestRoot = probeResult; } } else { num9++; num11 = this.StepWidthHeight(num11, num7, maxDimension); if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "increasing Width h=", num10, " w=", num11 }), new object[0]); } } } num10 = this.StepWidthHeight(num10, num8, maxDimension); if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "increasing Height h=", num10, " w=", num11 }), new object[0]); } } outW = 0; outH = 0; if (this.doPowerOfTwoTextures) { outW = Mathf.Min(this.CeilToNearestPowerOfTwo(this.bestRoot.w), maxDimension); outH = Mathf.Min(this.CeilToNearestPowerOfTwo(this.bestRoot.h), maxDimension); if (outH < outW / 2) { outH = outW / 2; } if (outW < outH / 2) { outW = outH / 2; } } else { outW = this.bestRoot.w; outH = this.bestRoot.h; } if (this.bestRoot == null) { return(null); } if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "Best fit found: atlasW=", outW, " atlasH", outH, " w=", this.bestRoot.w, " h=", this.bestRoot.h, " efficiency=", this.bestRoot.efficiency, " squareness=", this.bestRoot.squareness, " fits in max dimension=", this.bestRoot.fitsInMaxSize }), new object[0]); } List <MB2_TexturePacker.Image> list = new List <MB2_TexturePacker.Image>(); MB2_TexturePacker.flattenTree(this.bestRoot.root, list); list.Sort(new MB2_TexturePacker.ImgIDComparer()); if (list.Count != array.Length) { Debug.LogError("Result images not the same lentgh as source"); } int minImageSizeX2 = minImageSizeX; int minImageSizeY2 = minImageSizeY; bool flag2 = false; float num12 = (float)padding / (float)outW; if (this.bestRoot.w > maxDimension) { num12 = (float)padding / (float)maxDimension; float num13 = (float)maxDimension / (float)this.bestRoot.w; if (this.LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas width shrinking to " + num13); } for (int j = 0; j < list.Count; j++) { MB2_TexturePacker.Image image2 = list[j]; if ((float)image2.w * num13 < (float)masterImageSizeX) { if (this.LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeX."); } flag2 = true; minImageSizeX2 = Mathf.CeilToInt((float)minImageSizeX / num13); } int num14 = (int)((float)(image2.x + image2.w) * num13); image2.x = (int)(num13 * (float)image2.x); image2.w = num14 - image2.x; } outW = maxDimension; } float num15 = (float)padding / (float)outH; if (this.bestRoot.h > maxDimension) { num15 = (float)padding / (float)maxDimension; float num16 = (float)maxDimension / (float)this.bestRoot.h; if (this.LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas height shrinking to " + num16); } for (int k = 0; k < list.Count; k++) { MB2_TexturePacker.Image image3 = list[k]; if ((float)image3.h * num16 < (float)masterImageSizeY) { if (this.LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeY."); } flag2 = true; minImageSizeY2 = Mathf.CeilToInt((float)minImageSizeY / num16); } int num17 = (int)((float)(image3.y + image3.h) * num16); image3.y = (int)(num16 * (float)image3.y); image3.h = num17 - image3.y; } outH = maxDimension; } Rect[] array2; if (!flag2) { array2 = new Rect[list.Count]; for (int l = 0; l < list.Count; l++) { MB2_TexturePacker.Image image4 = list[l]; Rect rect = array2[l] = new Rect((float)image4.x / (float)outW + num12, (float)image4.y / (float)outH + num15, (float)image4.w / (float)outW - num12 * 2f, (float)image4.h / (float)outH - num15 * 2f); if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "Image: ", l, " imgID=", image4.imgId, " x=", rect.x * (float)outW, " y=", rect.y * (float)outH, " w=", rect.width * (float)outW, " h=", rect.height * (float)outH, " padding=", padding }), new object[0]); } } } else { if (this.LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("==================== REDOING PACKING ================"); } this.bestRoot = null; array2 = this._GetRects(imgWidthHeights, maxDimension, padding, minImageSizeX2, minImageSizeY2, masterImageSizeX, masterImageSizeY, out outW, out outH, recursionDepth + 1); } if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Done GetRects", new object[0]); } return(array2); }
private bool Probe(MB2_TexturePacker.Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDim, MB2_TexturePacker.ProbeResult pr) { MB2_TexturePacker.Node node = new MB2_TexturePacker.Node(); node.r = new MB2_TexturePacker.PixRect(0, 0, idealAtlasW, idealAtlasH); for (int i = 0; i < imgsToAdd.Length; i++) { if (node.Insert(imgsToAdd[i], false) == null) { return(false); } if (i == imgsToAdd.Length - 1) { int num = 0; int num2 = 0; this.GetExtent(node, ref num, ref num2); bool flag; float num8; float num9; if (this.doPowerOfTwoTextures) { int num3 = Mathf.Min(this.CeilToNearestPowerOfTwo(num), maxAtlasDim); int num4 = Mathf.Min(this.CeilToNearestPowerOfTwo(num2), maxAtlasDim); if (num4 < num3 / 2) { num4 = num3 / 2; } if (num3 < num4 / 2) { num3 = num4 / 2; } flag = (num <= maxAtlasDim && num2 <= maxAtlasDim); float num5 = Mathf.Max(1f, (float)num / (float)maxAtlasDim); float num6 = Mathf.Max(1f, (float)num2 / (float)maxAtlasDim); float num7 = (float)num3 * num5 * (float)num4 * num6; num8 = 1f - (num7 - imgArea) / num7; num9 = 1f; } else { num8 = 1f - ((float)(num * num2) - imgArea) / (float)(num * num2); if (num < num2) { num9 = (float)num / (float)num2; } else { num9 = (float)num2 / (float)num; } flag = (num <= maxAtlasDim && num2 <= maxAtlasDim); } pr.Set(num, num2, node, flag, num8, num9); if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "Probe success efficiency w=", num, " h=", num2, " e=", num8, " sq=", num9, " fits=", flag }), new object[0]); } return(true); } } Debug.LogError("Should never get here."); return(false); }
//------------------ Algorithm for fitting everything into one atlas and scaling down // // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size. // Sort images from big to small using either height, width or area comparer // Explore space to find a resonably efficient packing. Grow the atlas gradually until a fit is found // Scale atlas to fit // AtlasPackingResult _GetRectsSingleAtlas(List <Vector2> imgWidthHeights, int maxDimension, int padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimension={1}, padding={2}, minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}, recursionDepth={7}", imgWidthHeights.Count, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); } if (recursionDepth > 10) { if (LOG_LEVEL >= MB2_LogLevel.error) { Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures."); } return(null); } float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; Image im = imgsToAdd[i] = new Image(i, iw, ih, padding, minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (doPowerOfTwoTextures) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (LOG_LEVEL >= MB2_LogLevel.trace) { Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); } if (ProbeSingleAtlas(imgsToAdd, steppedWidth, steppedHeight, area, maxDimension, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(doPowerOfTwoTextures) > bestRoot.GetScore(doPowerOfTwoTextures)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimension); if (LOG_LEVEL >= MB2_LogLevel.trace) { MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimension); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } if (bestRoot == null) { return(null); } int outW = 0; int outH = 0; if (doPowerOfTwoTextures) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimension); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimension); if (outH < outW / 2) { outH = outW / 2; //smaller dim can't be less than half larger } if (outW < outH / 2) { outW = outH / 2; } } else { outW = Mathf.Min(bestRoot.w, maxDimension); outH = Mathf.Min(bestRoot.h, maxDimension); } bestRoot.outW = outW; bestRoot.outH = outH; if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); } //Debug.Assert(images.Count != imgsToAdd.Length, "Result images not the same lentgh as source")); //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit AtlasPackingResult res = ScaleAtlasToFitMaxDim(bestRoot, imgWidthHeights, images, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, outW, outH, recursionDepth); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(String.Format("Done GetRects atlasW={0} atlasH={1}", bestRoot.w, bestRoot.h)); } return(res); }
bool _bakeStep1(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource) { //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.2"); //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); //PART 3 ==== Add delete meshes from combined for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; if (cm.combinedMesh.targetRenderer == null) { cm.combinedMesh.resultSceneObject = _resultSceneObject; cm.combinedMesh.BuildSceneMeshObject(gos, true); if (_LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("BuildSO combiner {0} goID {1} targetRenID {2} meshID {3}", i, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID()); } } else { if (cm.combinedMesh.targetRenderer.transform.parent != resultSceneObject.transform) { Debug.LogError("targetRender objects must be children of resultSceneObject"); return(false); } } if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0) { cm.combinedMesh.AddDeleteGameObjectsByID(cm.gosToAdd.ToArray(), cm.gosToDelete.ToArray(), disableRendererInSource); if (_LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Baked combiner {0} obsAdded {1} objsRemoved {2} goID {3} targetRenID {4} meshID {5}", i, cm.gosToAdd.Count, cm.gosToDelete.Count, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID()); } } Renderer r = cm.combinedMesh.targetRenderer; Mesh m = cm.combinedMesh.GetMesh(); if (r is MeshRenderer) { MeshFilter mf = r.gameObject.GetComponent <MeshFilter>(); mf.sharedMesh = m; } else { SkinnedMeshRenderer smr = (SkinnedMeshRenderer)r; smr.sharedMesh = m; } } for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; for (int j = 0; j < cm.gosToDelete.Count; j++) { obj2MeshCombinerMap.Remove(cm.gosToDelete[j]); } } for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; for (int j = 0; j < cm.gosToAdd.Count; j++) { obj2MeshCombinerMap.Add(cm.gosToAdd[j].GetInstanceID(), cm); } if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0) { cm.gosToDelete.Clear(); cm.gosToAdd.Clear(); cm.numVertsInListToDelete = 0; cm.numVertsInListToAdd = 0; cm.isDirty = true; } } //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); if (LOG_LEVEL >= MB2_LogLevel.debug) { string s = "Meshes in combined:"; for (int i = 0; i < meshCombiners.Count; i++) { s += " mesh" + i + "(" + meshCombiners[i].combinedMesh.GetObjectsInCombined().Count + ")\n"; } s += "children in result: " + resultSceneObject.transform.childCount; MB2_Log.LogDebug(s, LOG_LEVEL); } if (meshCombiners.Count > 0) { return(true); } else { return(false); } }
void _distributeAmongBakers(GameObject[] gos, int[] deleteGOinstanceIDs) { if (gos == null) { gos = empty; } if (deleteGOinstanceIDs == null) { deleteGOinstanceIDs = emptyIDs; } if (resultSceneObject == null) { resultSceneObject = new GameObject("CombinedMesh-" + name); } //PART 2 ==== calculate which bakers to add objects to for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].extraSpace = _maxVertsInMesh - meshCombiners[i].combinedMesh.GetMesh().vertexCount; } //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.1"); //first delete game objects from the existing combinedMeshes keep track of free space for (int i = 0; i < deleteGOinstanceIDs.Length; i++) { CombinedMesh c = null; if (obj2MeshCombinerMap.TryGetValue(deleteGOinstanceIDs[i], out c)) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("MB2_MultiMeshCombiner.Removing " + deleteGOinstanceIDs[i] + " from meshCombiner " + meshCombiners.IndexOf(c)); } c.numVertsInListToDelete += c.combinedMesh.GetNumVerticesFor(deleteGOinstanceIDs[i]); //m.vertexCount; c.gosToDelete.Add(deleteGOinstanceIDs[i]); } else { Debug.LogWarning("Object " + deleteGOinstanceIDs[i] + " in the list of objects to delete is not in the combined mesh."); } } for (int i = 0; i < gos.Length; i++) { GameObject go = gos[i]; int numVerts = MB_Utility.GetMesh(go).vertexCount; CombinedMesh cm = null; for (int j = 0; j < meshCombiners.Count; j++) { if (meshCombiners[j].extraSpace + meshCombiners[j].numVertsInListToDelete - meshCombiners[j].numVertsInListToAdd > numVerts) { cm = meshCombiners[j]; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("MB2_MultiMeshCombiner.Added " + gos[i] + " to combinedMesh " + j, LOG_LEVEL); } break; } } if (cm == null) { cm = new CombinedMesh(maxVertsInMesh, _resultSceneObject, _LOG_LEVEL); _setMBValues(cm.combinedMesh); meshCombiners.Add(cm); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("MB2_MultiMeshCombiner.Created new combinedMesh"); } } cm.gosToAdd.Add(go); cm.numVertsInListToAdd += numVerts; // obj2MeshCombinerMap.Add(go,cm); } }
/* * Packed rects may exceed atlas size and require scaling * When scaling want pixel perfect fit in atlas. Corners of rects should exactly align with pixel grid * Padding should be subtracted from pixel perfect rect to create pixel perfect square */ AtlasPackingResult ScaleAtlasToFitMaxDim(ProbeResult root, List <Vector2> imgWidthHeights, List <Image> images, int maxDimension, int padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int outW, int outH, int recursionDepth) { int newMinSizeX = minImageSizeX; int newMinSizeY = minImageSizeY; bool redoPacking = false; //AtlasPackingResult[] rs = null; // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit float padX = (float)padding / (float)outW; //padding needs to be pixel perfect in size if (root.w > maxDimension) { padX = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)root.w; if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas width shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; if (im.w * scaleFactor < masterImageSizeX) { //check if small images will be rounded too small. If so need to redo packing forcing a larger min size if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeX."); } redoPacking = true; newMinSizeX = Mathf.CeilToInt(minImageSizeX / scaleFactor); } int right = (int)((im.x + im.w) * scaleFactor); im.x = (int)(scaleFactor * im.x); im.w = right - im.x; } outW = maxDimension; } float padY = (float)padding / (float)outH; if (root.h > maxDimension) { //float minSizeY = ((float)minImageSizeY + 1) / maxDimension; padY = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)root.h; if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas height shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; if (im.h * scaleFactor < masterImageSizeY) { //check if small images will be rounded too small. If so need to redo packing forcing a larger min size if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeY."); } redoPacking = true; newMinSizeY = Mathf.CeilToInt(minImageSizeY / scaleFactor); } int bottom = (int)((im.y + im.h) * scaleFactor); im.y = (int)(scaleFactor * im.y); im.h = bottom - im.y; } outH = maxDimension; } AtlasPackingResult res; if (!redoPacking) { res = new AtlasPackingResult(); res.rects = new Rect[images.Count]; res.srcImgIdxs = new int[images.Count]; res.atlasX = outW; res.atlasY = outH; res.usedW = -1; res.usedH = -1; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2f, (float)im.h / (float)outH - padY * 2f); res.srcImgIdxs[i] = im.imgId; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + padding); } } return(res); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("==================== REDOING PACKING ================"); } root = null; return(_GetRectsSingleAtlas(imgWidthHeights, maxDimension, padding, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, recursionDepth + 1)); } }
AtlasPackingResult _GetRectsSingleAtlas(List <Vector2> imgWidthHeights, int maxDimension, int padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) { AtlasPackingResult res = new AtlasPackingResult(); List <Rect> rects = new List <Rect>(); int extent = 0; int maxh = 0; int maxw = 0; List <Image> images = new List <Image>(); if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Packing rects for: " + imgWidthHeights.Count); } for (int i = 0; i < imgWidthHeights.Count; i++) { Image im = new Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, padding, minImageSizeX, minImageSizeY); // if images are stacked horizontally then there is no padding at the top or bottom if (packingOrientation == TexturePackingOrientation.vertical) { im.h -= padding * 2; im.x = extent; im.y = 0; rects.Add(new Rect(im.w, im.h, extent, 0)); extent += im.w; maxh = Mathf.Max(maxh, im.h); } else { im.w -= padding * 2; im.y = extent; im.x = 0; rects.Add(new Rect(im.w, im.h, 0, extent)); extent += im.h; maxw = Mathf.Max(maxw, im.w); } images.Add(im); } //scale atlas to fit maxDimension Vector2 rootWH; if (packingOrientation == TexturePackingOrientation.vertical) { rootWH = new Vector2(extent, maxh); } else { rootWH = new Vector2(maxw, extent); } int outW = (int)rootWH.x; int outH = (int)rootWH.y; if (packingOrientation == TexturePackingOrientation.vertical) { if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(outW), maxDimension); } else { outW = Mathf.Min(outW, maxDimension); } } else { if (atlasMustBePowerOfTwo) { outH = Mathf.Min(CeilToNearestPowerOfTwo(outH), maxDimension); } else { outH = Mathf.Min(outH, maxDimension); } } float padX, padY; int newMinSizeX, newMinSizeY; if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY)) { res = new AtlasPackingResult(); res.rects = new Rect[images.Count]; res.srcImgIdxs = new int[images.Count]; res.atlasX = outW; res.atlasY = outH; res.usedW = -1; res.usedH = -1; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r; if (packingOrientation == TexturePackingOrientation.vertical) { r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH, (float)im.w / (float)outW - padX * 2f, stretchImagesToEdges ? 1f : (float)im.h / (float)outH); // all images are stretched to fill the height } else { r = res.rects[i] = new Rect((float)im.x / (float)outW, (float)im.y / (float)outH + padY, (stretchImagesToEdges ? 1f : ((float)im.w / (float)outW)), (float)im.h / (float)outH - padY * 2f); // all images are stretched to fill the height } res.srcImgIdxs[i] = im.imgId; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + padding + " outW=" + outW + " outH=" + outH); } } return(res); } Debug.Log("Packing failed returning null atlas result"); return(null); }
private void _distributeAmongBakers(GameObject[] gos, int[] deleteGOinstanceIDs) { if (gos == null) { gos = MB3_MultiMeshCombiner.empty; } if (deleteGOinstanceIDs == null) { deleteGOinstanceIDs = MB3_MultiMeshCombiner.emptyIDs; } if (this.resultSceneObject == null) { this.resultSceneObject = new GameObject("CombinedMesh-" + base.name); } for (int i = 0; i < this.meshCombiners.Count; i++) { this.meshCombiners[i].extraSpace = this._maxVertsInMesh - this.meshCombiners[i].combinedMesh.GetMesh().vertexCount; } for (int j = 0; j < deleteGOinstanceIDs.Length; j++) { MB3_MultiMeshCombiner.CombinedMesh combinedMesh = null; if (this.obj2MeshCombinerMap.TryGetValue(deleteGOinstanceIDs[j], out combinedMesh)) { if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "MB2_MultiMeshCombiner.Removing ", deleteGOinstanceIDs[j], " from meshCombiner ", this.meshCombiners.IndexOf(combinedMesh) }), new object[0]); } combinedMesh.numVertsInListToDelete += combinedMesh.combinedMesh.GetNumVerticesFor(deleteGOinstanceIDs[j]); combinedMesh.gosToDelete.Add(deleteGOinstanceIDs[j]); } else { Debug.LogWarning("Object " + deleteGOinstanceIDs[j] + " in the list of objects to delete is not in the combined mesh."); } } for (int k = 0; k < gos.Length; k++) { GameObject gameObject = gos[k]; int vertexCount = MB_Utility.GetMesh(gameObject).vertexCount; MB3_MultiMeshCombiner.CombinedMesh combinedMesh2 = null; for (int l = 0; l < this.meshCombiners.Count; l++) { if (this.meshCombiners[l].extraSpace + this.meshCombiners[l].numVertsInListToDelete - this.meshCombiners[l].numVertsInListToAdd > vertexCount) { combinedMesh2 = this.meshCombiners[l]; if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(string.Concat(new object[] { "MB2_MultiMeshCombiner.Added ", gos[k], " to combinedMesh ", l }), new object[] { this.LOG_LEVEL }); } break; } } if (combinedMesh2 == null) { combinedMesh2 = new MB3_MultiMeshCombiner.CombinedMesh(this.maxVertsInMesh, this._resultSceneObject, this._LOG_LEVEL); this._setMBValues(combinedMesh2.combinedMesh); this.meshCombiners.Add(combinedMesh2); if (this.LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("MB2_MultiMeshCombiner.Created new combinedMesh", new object[0]); } } combinedMesh2.gosToAdd.Add(gameObject); combinedMesh2.numVertsInListToAdd += vertexCount; } }
//----------------- Algorithm for fitting everything into multiple Atlases // // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size. // Sort images from big to small using either height, width or area comparer // // If an image is bigger than maxDim, then shrink it to max size on the largest dimension // distribute images using the new algorithm, should never have to expand the atlas instead create new atlases as needed // should not need to scale atlases // AtlasPackingResult[] _GetRectsMultiAtlas(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionPassedX, int maxDimensionPassedY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY) { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimensionX={1}, maxDimensionY={2} minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}", imgWidthHeights.Count, maxDimensionPassedX, maxDimensionPassedY, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY)); } float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; int maxDimensionX = maxDimensionPassedX; int maxDimensionY = maxDimensionPassedY; if (atlasMustBePowerOfTwo) { maxDimensionX = RoundToNearestPositivePowerOfTwo(maxDimensionX); maxDimensionY = RoundToNearestPositivePowerOfTwo(maxDimensionY); } for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; //shrink the image so that it fits in maxDimenion if it is larger than maxDimension if atlas exceeds maxDim x maxDim then new alas will be created iw = Mathf.Min(iw, maxDimensionX - paddings[i].leftRight * 2); ih = Mathf.Min(ih, maxDimensionY - paddings[i].topBottom * 2); Image im = imgsToAdd[i] = new Image(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } //explore the space to find a resonably efficient packing //int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (atlasMustBePowerOfTwo) { idealAtlasH = RoundToNearestPositivePowerOfTwo(maxDimensionY); idealAtlasW = RoundToNearestPositivePowerOfTwo(maxDimensionX); } else { idealAtlasH = maxDimensionY; idealAtlasW = maxDimensionX; } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } ProbeResult pr = new ProbeResult(); Array.Sort(imgsToAdd, new ImageHeightComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { bestRoot = pr; } Array.Sort(imgsToAdd, new ImageWidthComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { if (pr.totalAtlasArea < bestRoot.totalAtlasArea) { bestRoot = pr; } } Array.Sort(imgsToAdd, new ImageAreaComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { if (pr.totalAtlasArea < bestRoot.totalAtlasArea) { bestRoot = pr; } } if (bestRoot == null) { return(null); } if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Best fit found: w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); } //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <AtlasPackingResult> rs = new List <AtlasPackingResult>(); // find all Nodes that are an individual atlas List <Node> atlasNodes = new List <Node>(); Stack <Node> stack = new Stack <Node>(); Node node = bestRoot.root; while (node != null) { stack.Push(node); node = node.child[0]; } // traverse the tree collecting atlasNodes while (stack.Count > 0) { node = stack.Pop(); if (node.isFullAtlas == NodeType.maxDim) { atlasNodes.Add(node); } if (node.child[1] != null) { node = node.child[1]; while (node != null) { stack.Push(node); node = node.child[0]; } } } //pack atlases so they all fit for (int i = 0; i < atlasNodes.Count; i++) { List <Image> images = new List <Image>(); flattenTree(atlasNodes[i], images); Rect[] rss = new Rect[images.Count]; int[] srcImgIdx = new int[images.Count]; for (int j = 0; j < images.Count; j++) { rss[j] = (new Rect(images[j].x - atlasNodes[i].r.x, images[j].y, images[j].w, images[j].h)); srcImgIdx[j] = images[j].imgId; } AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); GetExtent(atlasNodes[i], ref res.usedW, ref res.usedH); res.usedW -= atlasNodes[i].r.x; int outW = atlasNodes[i].r.w; int outH = atlasNodes[i].r.h; if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(res.usedW), atlasNodes[i].r.w); outH = Mathf.Min(CeilToNearestPowerOfTwo(res.usedH), atlasNodes[i].r.h); if (outH < outW / 2) { outH = outW / 2; //smaller dim can't be less than half larger } if (outW < outH / 2) { outW = outH / 2; } } else { outW = res.usedW; outH = res.usedH; } res.atlasY = outH; res.atlasX = outW; res.rects = rss; res.srcImgIdxs = srcImgIdx; res.CalcUsedWidthAndHeight(); rs.Add(res); ConvertToRectsWithoutPaddingAndNormalize01(res, paddings[i]); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(String.Format("Done GetRects ")); } } return(rs.ToArray()); }
private bool _bakeStep1(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource) { for (int i = 0; i < this.meshCombiners.Count; i++) { MB3_MultiMeshCombiner.CombinedMesh combinedMesh = this.meshCombiners[i]; if (combinedMesh.combinedMesh.targetRenderer == null) { combinedMesh.combinedMesh.resultSceneObject = this._resultSceneObject; combinedMesh.combinedMesh.BuildSceneMeshObject(gos, true); if (this._LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("BuildSO combiner {0} goID {1} targetRenID {2} meshID {3}", new object[] { i, combinedMesh.combinedMesh.targetRenderer.gameObject.GetInstanceID(), combinedMesh.combinedMesh.targetRenderer.GetInstanceID(), combinedMesh.combinedMesh.GetMesh().GetInstanceID() }); } } else if (combinedMesh.combinedMesh.targetRenderer.transform.parent != this.resultSceneObject.transform) { Debug.LogError("targetRender objects must be children of resultSceneObject"); return(false); } if (combinedMesh.gosToAdd.Count > 0 || combinedMesh.gosToDelete.Count > 0) { combinedMesh.combinedMesh.AddDeleteGameObjectsByID(combinedMesh.gosToAdd.ToArray(), combinedMesh.gosToDelete.ToArray(), disableRendererInSource); if (this._LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Baked combiner {0} obsAdded {1} objsRemoved {2} goID {3} targetRenID {4} meshID {5}", new object[] { i, combinedMesh.gosToAdd.Count, combinedMesh.gosToDelete.Count, combinedMesh.combinedMesh.targetRenderer.gameObject.GetInstanceID(), combinedMesh.combinedMesh.targetRenderer.GetInstanceID(), combinedMesh.combinedMesh.GetMesh().GetInstanceID() }); } } Renderer targetRenderer = combinedMesh.combinedMesh.targetRenderer; Mesh mesh = combinedMesh.combinedMesh.GetMesh(); if (targetRenderer is MeshRenderer) { MeshFilter component = targetRenderer.gameObject.GetComponent <MeshFilter>(); component.sharedMesh = mesh; } else { SkinnedMeshRenderer skinnedMeshRenderer = (SkinnedMeshRenderer)targetRenderer; skinnedMeshRenderer.sharedMesh = mesh; } } for (int j = 0; j < this.meshCombiners.Count; j++) { MB3_MultiMeshCombiner.CombinedMesh combinedMesh2 = this.meshCombiners[j]; for (int k = 0; k < combinedMesh2.gosToDelete.Count; k++) { this.obj2MeshCombinerMap.Remove(combinedMesh2.gosToDelete[k]); } } for (int l = 0; l < this.meshCombiners.Count; l++) { MB3_MultiMeshCombiner.CombinedMesh combinedMesh3 = this.meshCombiners[l]; for (int m = 0; m < combinedMesh3.gosToAdd.Count; m++) { this.obj2MeshCombinerMap.Add(combinedMesh3.gosToAdd[m].GetInstanceID(), combinedMesh3); } if (combinedMesh3.gosToAdd.Count > 0 || combinedMesh3.gosToDelete.Count > 0) { combinedMesh3.gosToDelete.Clear(); combinedMesh3.gosToAdd.Clear(); combinedMesh3.numVertsInListToDelete = 0; combinedMesh3.numVertsInListToAdd = 0; combinedMesh3.isDirty = true; } } if (this.LOG_LEVEL >= MB2_LogLevel.debug) { string text = "Meshes in combined:"; for (int n = 0; n < this.meshCombiners.Count; n++) { string text2 = text; text = string.Concat(new object[] { text2, " mesh", n, "(", this.meshCombiners[n].combinedMesh.GetObjectsInCombined().Count, ")\n" }); } text = text + "children in result: " + this.resultSceneObject.transform.childCount; MB2_Log.LogDebug(text, new object[] { this.LOG_LEVEL }); } return(this.meshCombiners.Count > 0); }
bool ProbeSingleAtlas(Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDimX, int maxAtlasDimY, ProbeResult pr) { Node root = new Node(NodeType.maxDim); root.r = new PixRect(0, 0, idealAtlasW, idealAtlasH); //Debug.Assert(maxAtlasDim >= 1); for (int i = 0; i < imgsToAdd.Length; i++) { Node n = root.Insert(imgsToAdd[i], false); if (n == null) { return(false); } else if (i == imgsToAdd.Length - 1) { int usedW = 0; int usedH = 0; GetExtent(root, ref usedW, ref usedH); float efficiency, squareness; bool fitsInMaxDim; int atlasW = usedW; int atlasH = usedH; if (atlasMustBePowerOfTwo) { atlasW = Mathf.Min(CeilToNearestPowerOfTwo(usedW), maxAtlasDimX); atlasH = Mathf.Min(CeilToNearestPowerOfTwo(usedH), maxAtlasDimY); if (atlasH < atlasW / 2) { atlasH = atlasW / 2; } if (atlasW < atlasH / 2) { atlasW = atlasH / 2; } fitsInMaxDim = usedW <= maxAtlasDimX && usedH <= maxAtlasDimY; float scaleW = Mathf.Max(1f, ((float)usedW) / maxAtlasDimX); float scaleH = Mathf.Max(1f, ((float)usedH) / maxAtlasDimY); float atlasArea = atlasW * scaleW * atlasH * scaleH; //area if we scaled it up to something large enough to contain images efficiency = 1f - (atlasArea - imgArea) / atlasArea; squareness = 1f; //don't care about squareness in power of two case } else { efficiency = 1f - (usedW * usedH - imgArea) / (usedW * usedH); if (usedW < usedH) { squareness = (float)usedW / (float)usedH; } else { squareness = (float)usedH / (float)usedW; } fitsInMaxDim = usedW <= maxAtlasDimX && usedH <= maxAtlasDimY; } pr.Set(usedW, usedH, atlasW, atlasH, root, fitsInMaxDim, efficiency, squareness); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Probe success efficiency w=" + usedW + " h=" + usedH + " e=" + efficiency + " sq=" + squareness + " fits=" + fitsInMaxDim); } return(true); } } Debug.LogError("Should never get here."); return(false); }
public void LogDebug(string msg, params object[] args) { _CacheLogMessage(MB2_Log.LogDebug(msg, args)); }
//------------------ Algorithm for fitting everything into one atlas and scaling down // // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size. // Sort images from big to small using either height, width or area comparer // Explore space to find a resonably efficient packing. Grow the atlas gradually until a fit is found // Scale atlas to fit // AtlasPackingResult _GetRectsSingleAtlas(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimension={1}, minImageSizeX={2}, minImageSizeY={3}, masterImageSizeX={4}, masterImageSizeY={5}, recursionDepth={6}", imgWidthHeights.Count, maxDimensionX, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); } if (recursionDepth > MAX_RECURSION_DEPTH) { if (LOG_LEVEL >= MB2_LogLevel.error) { Debug.LogError("Maximum recursion depth reached. The baked atlas is likely not very good. " + " This happens when the packed atlases exceeds the maximum" + " atlas size in one or both dimensions so that the atlas needs to be downscaled AND there are some very thin or very small images (only-a-few-pixels)." + " these very thin images can 'vanish' completely when the atlas is downscaled.\n\n" + " Try one or more of the following: using multiple atlases, increase the maximum atlas size, don't use 'force-power-of-two', remove the source materials that are are using very small/thin textures."); } //return null; } float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; Image im = imgsToAdd[i] = new Image(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (atlasMustBePowerOfTwo) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (LOG_LEVEL >= MB2_LogLevel.trace) { Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); } if (ProbeSingleAtlas(imgsToAdd, steppedWidth, steppedHeight, area, maxDimensionX, maxDimensionY, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(atlasMustBePowerOfTwo) > bestRoot.GetScore(atlasMustBePowerOfTwo)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimensionX); if (LOG_LEVEL >= MB2_LogLevel.trace) { MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimensionY); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } if (bestRoot == null) { return(null); } int outW = 0; int outH = 0; if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimensionX); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimensionY); if (outH < outW / 2) { outH = outW / 2; //smaller dim can't be less than half larger } if (outW < outH / 2) { outW = outH / 2; } } else { outW = Mathf.Min(bestRoot.w, maxDimensionX); outH = Mathf.Min(bestRoot.h, maxDimensionY); } bestRoot.outW = outW; bestRoot.outH = outH; if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); } //Debug.Assert(images.Count != imgsToAdd.Length, "Result images not the same lentgh as source")); //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit Vector2 rootWH = new Vector2(bestRoot.w, bestRoot.h); float padX, padY; int newMinSizeX, newMinSizeY; if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimensionX, maxDimensionY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY) || recursionDepth > MAX_RECURSION_DEPTH) { AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); res.rects = new Rect[images.Count]; res.srcImgIdxs = new int[images.Count]; res.atlasX = outW; res.atlasY = outH; res.usedW = -1; res.usedH = -1; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2f, (float)im.h / (float)outH - padY * 2f); res.srcImgIdxs[i] = im.imgId; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + (paddings[i].leftRight * 2) + "x" + (paddings[i].topBottom * 2)); } } res.CalcUsedWidthAndHeight(); return(res); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("==================== REDOING PACKING ================"); } //root = null; return(_GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, recursionDepth + 1)); } //if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug(String.Format("Done GetRects atlasW={0} atlasH={1}", bestRoot.w, bestRoot.h)); //return res; }
Rect[] _GetRects(List <Vector2> imgWidthHeights, int maxDimension, int padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, out int outW, out int outH, int recursionDepth) { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimension={1}, padding={2}, minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}, recursionDepth={7}", imgWidthHeights.Count, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); } if (recursionDepth > 10) { Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures."); outW = 0; outH = 0; return(new Rect[0]); } float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { Image im = imgsToAdd[i] = new Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, padding, minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (doPowerOfTwoTextures) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 1; } if (idealAtlasH == 0) { idealAtlasH = 1; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (LOG_LEVEL >= MB2_LogLevel.trace) { Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); } if (Probe(imgsToAdd, steppedWidth, steppedHeight, area, maxDimension, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(doPowerOfTwoTextures) > bestRoot.GetScore(doPowerOfTwoTextures)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimension); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimension); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } outW = 0; outH = 0; if (doPowerOfTwoTextures) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimension); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimension); if (outH < outW / 2) { outH = outW / 2; //smaller dim can't be less than half larger } if (outW < outH / 2) { outW = outH / 2; } } else { outW = bestRoot.w; outH = bestRoot.h; } if (bestRoot == null) { return(null); } if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.fitsInMaxSize); } List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); if (images.Count != imgsToAdd.Length) { Debug.LogError("Result images not the same lentgh as source"); } //scale images if too large int newMinSizeX = minImageSizeX; int newMinSizeY = minImageSizeY; bool redoPacking = false; float padX = (float)padding / (float)outW; if (bestRoot.w > maxDimension) { //float minSizeX = ((float)minImageSizeX + 1) / maxDimension; padX = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.w; if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas width shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; if (im.w * scaleFactor < masterImageSizeX) //check if small images will be rounded too small. If so need to redo packing forcing a larger min size { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeX."); } redoPacking = true; newMinSizeX = Mathf.CeilToInt(minImageSizeX / scaleFactor); } int right = (int)((im.x + im.w) * scaleFactor); im.x = (int)(scaleFactor * im.x); im.w = right - im.x; } outW = maxDimension; } float padY = (float)padding / (float)outH; if (bestRoot.h > maxDimension) { //float minSizeY = ((float)minImageSizeY + 1) / maxDimension; padY = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.h; if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas height shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; if (im.h * scaleFactor < masterImageSizeY) //check if small images will be rounded too small. If so need to redo packing forcing a larger min size { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeY."); } redoPacking = true; newMinSizeY = Mathf.CeilToInt(minImageSizeY / scaleFactor); } int bottom = (int)((im.y + im.h) * scaleFactor); im.y = (int)(scaleFactor * im.y); im.h = bottom - im.y; } outH = maxDimension; } Rect[] rs; if (!redoPacking) { rs = new Rect[images.Count]; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r = rs[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2f, (float)im.h / (float)outH - padY * 2f); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + padding); } } } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("==================== REDOING PACKING ================"); } bestRoot = null; rs = _GetRects(imgWidthHeights, maxDimension, padding, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, out outW, out outH, recursionDepth + 1); } if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Done GetRects"); } return(rs); }