static void UpdateVertexCache(tk2dSpriteCollection gen, Atlas.AtlasData[] packers, tk2dSpriteCollectionData coll, List <SCGE.SpriteLut> spriteLuts) { float scale = 2.0f * gen.targetOrthoSize / gen.targetHeight; int padAmount = GetPadAmount(gen); for (int i = 0; i < sourceTextures.Length; ++i) { SCGE.SpriteLut _lut = null; for (int j = 0; j < spriteLuts.Count; ++j) { if (spriteLuts[j].source == i) { _lut = spriteLuts[j]; break; } } tk2dSpriteCollectionDefinition thisTexParam = gen.textureParams[i]; Atlas.AtlasData packer = null; Atlas.AtlasEntry atlasEntry = null; int atlasIndex = 0; foreach (var p in packers) { if ((atlasEntry = p.FindEntryWithIndex(_lut.atlasIndex)) != null) { packer = p; break; } ++atlasIndex; } float fwidth = packer.width; float fheight = packer.height; int tx = atlasEntry.x + padAmount, ty = atlasEntry.y + padAmount, tw = atlasEntry.w - padAmount * 2, th = atlasEntry.h - padAmount * 2; int sd_y = packer.height - ty - th; float uvOffsetX = 0.001f / fwidth; float uvOffsetY = 0.001f / fheight; Vector2 v0 = new Vector2(tx / fwidth + uvOffsetX, 1.0f - (sd_y + th) / fheight + uvOffsetY); Vector2 v1 = new Vector2((tx + tw) / fwidth - uvOffsetX, 1.0f - sd_y / fheight - uvOffsetY); Mesh mesh = null; Transform meshTransform = null; GameObject instantiated = null; if (thisTexParam.overrideMesh) { // Disabled instantiated = GameObject.Instantiate(thisTexParam.overrideMesh) as GameObject; MeshFilter meshFilter = instantiated.GetComponentInChildren <MeshFilter>(); if (meshFilter == null) { Debug.LogError("Unable to find mesh"); GameObject.DestroyImmediate(instantiated); } else { mesh = meshFilter.sharedMesh; meshTransform = meshFilter.gameObject.transform; } } if (mesh) { coll.spriteDefinitions[i].positions = new Vector3[mesh.vertices.Length]; coll.spriteDefinitions[i].uvs = new Vector2[mesh.vertices.Length]; for (int j = 0; j < mesh.vertices.Length; ++j) { coll.spriteDefinitions[i].positions[j] = meshTransform.TransformPoint(mesh.vertices[j]); coll.spriteDefinitions[i].uvs[j] = new Vector2(v0.x + (v1.x - v0.x) * mesh.uv[j].x, v0.y + (v1.y - v0.y) * mesh.uv[j].y); } coll.spriteDefinitions[i].indices = new int[mesh.triangles.Length]; for (int j = 0; j < mesh.triangles.Length; ++j) { coll.spriteDefinitions[i].indices[j] = mesh.triangles[j]; } coll.spriteDefinitions[i].material = gen.atlasMaterials[atlasIndex]; GameObject.DestroyImmediate(instantiated); } else { Texture2D thisTextureRef = sourceTextures[i]; float texHeight = thisTextureRef?thisTextureRef.height:2; float texWidth = thisTextureRef?thisTextureRef.width:2; float h = thisTextureRef?thisTextureRef.height:64; float w = thisTextureRef?thisTextureRef.width:64; h *= thisTexParam.scale.x; w *= thisTexParam.scale.y; float scaleX = w * scale; float scaleY = h * scale; Vector3 pos0 = new Vector3(-0.5f * scaleX, 0, -0.5f * scaleY); switch (thisTexParam.anchor) { case tk2dSpriteCollectionDefinition.Anchor.LowerLeft: pos0 = new Vector3(0, 0, 0); break; case tk2dSpriteCollectionDefinition.Anchor.LowerCenter: pos0 = new Vector3(-0.5f * scaleX, 0, 0); break; case tk2dSpriteCollectionDefinition.Anchor.LowerRight: pos0 = new Vector3(-scaleX, 0, 0); break; case tk2dSpriteCollectionDefinition.Anchor.MiddleLeft: pos0 = new Vector3(0, 0, -0.5f * scaleY); break; case tk2dSpriteCollectionDefinition.Anchor.MiddleCenter: pos0 = new Vector3(-0.5f * scaleX, 0, -0.5f * scaleY); break; case tk2dSpriteCollectionDefinition.Anchor.MiddleRight: pos0 = new Vector3(-scaleX, 0, -0.5f * scaleY); break; case tk2dSpriteCollectionDefinition.Anchor.UpperLeft: pos0 = new Vector3(0, 0, -scaleY); break; case tk2dSpriteCollectionDefinition.Anchor.UpperCenter: pos0 = new Vector3(-0.5f * scaleX, 0, -scaleY); break; case tk2dSpriteCollectionDefinition.Anchor.UpperRight: pos0 = new Vector3(-scaleX, 0, -scaleY); break; case tk2dSpriteCollectionDefinition.Anchor.Custom: { pos0 = new Vector3(-thisTexParam.anchorX * thisTexParam.scale.x * scale, 0, -(h - thisTexParam.anchorY * thisTexParam.scale.y) * scale); } break; } Vector3 pos1 = pos0 + new Vector3(scaleX, 0, scaleY); List <Vector3> positions = new List <Vector3>(); List <Vector2> uvs = new List <Vector2>(); // build mesh if (_lut.isSplit) { for (int j = 0; j < spriteLuts.Count; ++j) { if (spriteLuts[j].source == i) { _lut = spriteLuts[j]; int thisAtlasIndex = 0; foreach (var p in packers) { if ((atlasEntry = p.FindEntryWithIndex(_lut.atlasIndex)) != null) { packer = p; break; } ++thisAtlasIndex; } if (thisAtlasIndex != atlasIndex) { // This is a serious problem, dicing is not supported when multi atlas output is selected Debug.Break(); } fwidth = packer.width; fheight = packer.height; tx = atlasEntry.x + padAmount; ty = atlasEntry.y + padAmount; tw = atlasEntry.w - padAmount * 2; th = atlasEntry.h - padAmount * 2; sd_y = packer.height - ty - th; v0 = new Vector2(tx / fwidth + uvOffsetX, 1.0f - (sd_y + th) / fheight + uvOffsetY); v1 = new Vector2((tx + tw) / fwidth - uvOffsetX, 1.0f - sd_y / fheight - uvOffsetY); float x0 = _lut.rx / texWidth; float y0 = _lut.ry / texHeight; float x1 = (_lut.rx + _lut.rw) / texWidth; float y1 = (_lut.ry + _lut.rh) / texHeight; Vector3 dpos0 = new Vector3(Mathf.Lerp(pos0.x, pos1.x, x0), 0.0f, Mathf.Lerp(pos0.z, pos1.z, y0)); Vector3 dpos1 = new Vector3(Mathf.Lerp(pos0.x, pos1.x, x1), 0.0f, Mathf.Lerp(pos0.z, pos1.z, y1)); positions.Add(new Vector3(dpos0.x, dpos0.z, 0)); positions.Add(new Vector3(dpos1.x, dpos0.z, 0)); positions.Add(new Vector3(dpos0.x, dpos1.z, 0)); positions.Add(new Vector3(dpos1.x, dpos1.z, 0)); if (atlasEntry.flipped) { uvs.Add(new Vector2(v0.x, v0.y)); uvs.Add(new Vector2(v0.x, v1.y)); uvs.Add(new Vector2(v1.x, v0.y)); uvs.Add(new Vector2(v1.x, v1.y)); } else { uvs.Add(new Vector2(v0.x, v0.y)); uvs.Add(new Vector2(v1.x, v0.y)); uvs.Add(new Vector2(v0.x, v1.y)); uvs.Add(new Vector2(v1.x, v1.y)); } } } } else { float x0 = _lut.rx / texWidth; float y0 = _lut.ry / texHeight; float x1 = (_lut.rx + _lut.rw) / texWidth; float y1 = (_lut.ry + _lut.rh) / texHeight; Vector3 dpos0 = new Vector3(Mathf.Lerp(pos0.x, pos1.x, x0), 0.0f, Mathf.Lerp(pos0.z, pos1.z, y0)); Vector3 dpos1 = new Vector3(Mathf.Lerp(pos0.x, pos1.x, x1), 0.0f, Mathf.Lerp(pos0.z, pos1.z, y1)); positions.Add(new Vector3(dpos0.x, dpos0.z, 0)); positions.Add(new Vector3(dpos1.x, dpos0.z, 0)); positions.Add(new Vector3(dpos0.x, dpos1.z, 0)); positions.Add(new Vector3(dpos1.x, dpos1.z, 0)); if (atlasEntry.flipped) { uvs.Add(new Vector2(v0.x, v0.y)); uvs.Add(new Vector2(v0.x, v1.y)); uvs.Add(new Vector2(v1.x, v0.y)); uvs.Add(new Vector2(v1.x, v1.y)); } else { uvs.Add(new Vector2(v0.x, v0.y)); uvs.Add(new Vector2(v1.x, v0.y)); uvs.Add(new Vector2(v0.x, v1.y)); uvs.Add(new Vector2(v1.x, v1.y)); } } // build sprite definition coll.spriteDefinitions[i].indices = new int[6 * (positions.Count / 4)]; for (int j = 0; j < positions.Count / 4; ++j) { coll.spriteDefinitions[i].indices[j * 6 + 0] = j * 4 + 0; coll.spriteDefinitions[i].indices[j * 6 + 1] = j * 4 + 3; coll.spriteDefinitions[i].indices[j * 6 + 2] = j * 4 + 1; coll.spriteDefinitions[i].indices[j * 6 + 3] = j * 4 + 2; coll.spriteDefinitions[i].indices[j * 6 + 4] = j * 4 + 3; coll.spriteDefinitions[i].indices[j * 6 + 5] = j * 4 + 0; } coll.spriteDefinitions[i].positions = new Vector3[positions.Count]; coll.spriteDefinitions[i].uvs = new Vector2[uvs.Count]; for (int j = 0; j < positions.Count; ++j) { coll.spriteDefinitions[i].positions[j] = positions[j]; coll.spriteDefinitions[i].uvs[j] = uvs[j]; } coll.spriteDefinitions[i].material = gen.atlasMaterials[atlasIndex]; } Vector3 boundsMin = new Vector3(1.0e32f, 1.0e32f, 1.0e32f); Vector3 boundsMax = new Vector3(-1.0e32f, -1.0e32f, -1.0e32f); foreach (Vector3 v in coll.spriteDefinitions[i].positions) { boundsMin = Vector3.Min(boundsMin, v); boundsMax = Vector3.Max(boundsMax, v); } coll.spriteDefinitions[i].boundsData = new Vector3[2]; coll.spriteDefinitions[i].boundsData[0] = (boundsMax + boundsMin) / 2.0f; coll.spriteDefinitions[i].boundsData[1] = (boundsMax - boundsMin); coll.spriteDefinitions[i].name = gen.textureParams[i].name; } }
// Returns number of remaining rects public int Build() { // Initialize atlases = new List <AtlasData>(); remainingRectIndices = new List <int>(); bool[] usedRect = new bool[sourceRects.Count]; // Sanity check, can't build with textures larger than the actual max atlas size int minSize = Math.Min(atlasWidth, atlasHeight); int maxSize = Math.Max(atlasWidth, atlasHeight); foreach (RectSize rs in sourceRects) { int maxDim = Math.Max(rs.width, rs.height); int minDim = Math.Min(rs.width, rs.height); // largest texture needs to fit in an atlas if (maxDim > maxSize || (maxDim <= maxSize && minDim > minSize)) { remainingRectIndices = new List <int>(); for (int i = 0; i < sourceRects.Count; ++i) { remainingRectIndices.Add(i); } return(remainingRectIndices.Count); } } // Start with all source rects, this list will get reduced over time List <RectSize> rects = new List <RectSize>(sourceRects); bool allUsed = false; while (allUsed == false && atlases.Count < maxAllowedAtlasCount) { int numPasses = 1; int thisCellW = atlasWidth, thisCellH = atlasHeight; bool reverted = false; while (numPasses > 0) { // Create copy to make sure we can scale textures down when necessary List <RectSize> currRects = new List <RectSize>(rects); // MaxRectsBinPack binPacker = new MaxRectsBinPack(thisCellW, thisCellH); // allUsed = binPacker.Insert(currRects, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestAreaFit); MaxRectsBinPack binPacker = FindBestBinPacker(thisCellW, thisCellH, ref currRects, ref allUsed); float occupancy = binPacker.Occupancy(); // Consider the atlas resolved when after the first pass, all textures are used, and the occupancy > 0.5f, scaling // down by half to maintain PO2 requirements means this is as good as it gets bool firstPassFull = numPasses == 1 && occupancy > 0.5f; // Reverted copes with the case when halving the atlas size when occupancy < 0.5f, the textures don't fit in the // atlas anymore. At this point, size is reverted to the previous value, and the loop should accept this as the final value if (firstPassFull || (numPasses > 1 && occupancy > 0.5f && allUsed) || reverted) { List <AtlasEntry> atlasEntries = new List <AtlasEntry>(); foreach (var t in binPacker.GetMapped()) { int matchedId = -1; bool flipped = false; for (int i = 0; i < sourceRects.Count; ++i) { if (!usedRect[i] && sourceRects[i].width == t.width && sourceRects[i].height == t.height) { matchedId = i; break; } } // Not matched anything yet, so look for the same rects rotated if (matchedId == -1) { for (int i = 0; i < sourceRects.Count; ++i) { if (!usedRect[i] && sourceRects[i].width == t.height && sourceRects[i].height == t.width) { matchedId = i; flipped = true; break; } } } // If this fails its a catastrophic error usedRect[matchedId] = true; AtlasEntry newEntry = new AtlasEntry(); newEntry.flipped = flipped; newEntry.x = t.x; newEntry.y = t.y; newEntry.w = t.width; newEntry.h = t.height; newEntry.index = matchedId; atlasEntries.Add(newEntry); } AtlasData currAtlas = new AtlasData(); currAtlas.width = thisCellW; currAtlas.height = thisCellH; currAtlas.occupancy = binPacker.Occupancy(); currAtlas.entries = atlasEntries.ToArray(); atlases.Add(currAtlas); rects = currRects; break; // done } else { if (!allUsed) { // Can only try another size when it already has been scaled down for the first time if (thisCellW < atlasWidth && thisCellH < atlasHeight) { // Tried to scale down, but the texture doesn't fit, so revert previous change, and // iterate over the data again forcing a pass even though there is wastage if (thisCellW < thisCellH) { thisCellW *= 2; } else { thisCellH *= 2; } } reverted = true; } else { // More than half the texture was unused, scale down by one of the dimensions if (thisCellW < thisCellH) { thisCellH /= 2; } else { thisCellW /= 2; } } numPasses++; } } } remainingRectIndices = new List <int>(); for (int i = 0; i < usedRect.Length; ++i) { if (!usedRect[i]) { remainingRectIndices.Add(i); } } return(remainingRectIndices.Count); }
// Returns number of remaining rects public int Build() { // Initialize atlases = new List<AtlasData>(); remainingRectIndices = new List<int>(); bool[] usedRect = new bool[sourceRects.Count]; // Sanity check, can't build with textures larger than the actual max atlas size int minSize = Math.Min(atlasWidth, atlasHeight); int maxSize = Math.Max(atlasWidth, atlasHeight); foreach (RectSize rs in sourceRects) { int maxDim = Math.Max(rs.width, rs.height); int minDim = Math.Min(rs.width, rs.height); // largest texture needs to fit in an atlas if (maxDim > maxSize || (maxDim <= maxSize && minDim > minSize)) { remainingRectIndices = new List<int>(); for (int i = 0; i < sourceRects.Count; ++i) remainingRectIndices.Add(i); return remainingRectIndices.Count; } } // Start with all source rects, this list will get reduced over time List<RectSize> rects = new List<RectSize>(sourceRects); bool allUsed = false; while (allUsed == false && atlases.Count < maxAllowedAtlasCount) { int numPasses = 1; int thisCellW = atlasWidth, thisCellH = atlasHeight; bool reverted = false; while (numPasses > 0) { // Create copy to make sure we can scale textures down when necessary List<RectSize> currRects = new List<RectSize>(rects); // MaxRectsBinPack binPacker = new MaxRectsBinPack(thisCellW, thisCellH); // allUsed = binPacker.Insert(currRects, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestAreaFit); MaxRectsBinPack binPacker = FindBestBinPacker(thisCellW, thisCellH, ref currRects, ref allUsed); float occupancy = binPacker.Occupancy(); // Consider the atlas resolved when after the first pass, all textures are used, and the occupancy > 0.5f, scaling // down by half to maintain PO2 requirements means this is as good as it gets bool firstPassFull = numPasses == 1 && occupancy > 0.5f; // Reverted copes with the case when halving the atlas size when occupancy < 0.5f, the textures don't fit in the // atlas anymore. At this point, size is reverted to the previous value, and the loop should accept this as the final value if ( firstPassFull || (numPasses > 1 && occupancy > 0.5f && allUsed) || reverted) { List<AtlasEntry> atlasEntries = new List<AtlasEntry>(); foreach (var t in binPacker.GetMapped()) { int matchedId = -1; bool flipped = false; for (int i = 0; i < sourceRects.Count; ++i) { if (!usedRect[i] && sourceRects[i].width == t.width && sourceRects[i].height == t.height) { matchedId = i; break; } } // Not matched anything yet, so look for the same rects rotated if (matchedId == -1) { for (int i = 0; i < sourceRects.Count; ++i) { if (!usedRect[i] && sourceRects[i].width == t.height && sourceRects[i].height == t.width) { matchedId = i; flipped = true; break; } } } // If this fails its a catastrophic error usedRect[matchedId] = true; AtlasEntry newEntry = new AtlasEntry(); newEntry.flipped = flipped; newEntry.x = t.x; newEntry.y = t.y; newEntry.w = t.width; newEntry.h = t.height; newEntry.index = matchedId; atlasEntries.Add(newEntry); } AtlasData currAtlas = new AtlasData(); currAtlas.width = thisCellW; currAtlas.height = thisCellH; currAtlas.occupancy = binPacker.Occupancy(); currAtlas.entries = atlasEntries.ToArray(); atlases.Add(currAtlas); rects = currRects; break; // done } else { if (!allUsed) { // Can only try another size when it already has been scaled down for the first time if (thisCellW < atlasWidth && thisCellH < atlasHeight) { // Tried to scale down, but the texture doesn't fit, so revert previous change, and // iterate over the data again forcing a pass even though there is wastage if (thisCellW < thisCellH) thisCellW *= 2; else thisCellH *= 2; } reverted = true; } else { // More than half the texture was unused, scale down by one of the dimensions if (thisCellW < thisCellH) thisCellH /= 2; else thisCellW /= 2; } numPasses++; } } } remainingRectIndices = new List<int>(); for (int i = 0; i < usedRect.Length; ++i) { if (!usedRect[i]) { remainingRectIndices.Add(i); } } return remainingRectIndices.Count; }