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;
        }
    }
    static Texture2D ProcessTexture(bool premultipliedAlpha, bool additive, bool stretchPad, Texture2D srcTex, int sx, int sy, int tw, int th, ref SCGE.SpriteLut spriteLut, int padAmount)
    {
        // Can't have additive without premultiplied alpha
        if (!premultipliedAlpha)
        {
            additive = false;
        }

        int[] ww = new int[tw];
        int[] hh = new int[th];
        for (int x = 0; x < tw; ++x)
        {
            ww[x] = 0;
        }
        for (int y = 0; y < th; ++y)
        {
            hh[y] = 0;
        }
        int numNotTransparent = 0;

        for (int x = 0; x < tw; ++x)
        {
            for (int y = 0; y < th; ++y)
            {
                Color col = srcTex.GetPixel(sx + x, sy + y);
                if (col.a > 0)
                {
                    ww[x] = 1;
                    hh[y] = 1;
                    numNotTransparent++;
                }
            }
        }

        if (numNotTransparent > 0)
        {
            int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
            for (int x = 0; x < tw; ++x)
            {
                if (ww[x] == 1)
                {
                    x0 = x; break;
                }
            }
            for (int x = tw - 1; x >= 0; --x)
            {
                if (ww[x] == 1)
                {
                    x1 = x; break;
                }
            }
            for (int y = 0; y < th; ++y)
            {
                if (hh[y] == 1)
                {
                    y0 = y; break;
                }
            }
            for (int y = th - 1; y >= 0; --y)
            {
                if (hh[y] == 1)
                {
                    y1 = y; break;
                }
            }

            int w1 = x1 - x0 + 1;
            int h1 = y1 - y0 + 1;

            Texture2D dtex = new Texture2D(w1 + padAmount * 2, h1 + padAmount * 2);
            for (int x = 0; x < w1; ++x)
            {
                for (int y = 0; y < h1; ++y)
                {
                    Color col = srcTex.GetPixel(sx + x0 + x, sy + y0 + y);
                    dtex.SetPixel(x + padAmount, y + padAmount, col);
                }
            }
            PadTexture(dtex, padAmount, stretchPad);

            if (premultipliedAlpha)
            {
                for (int x = 0; x < dtex.width; ++x)
                {
                    for (int y = 0; y < dtex.height; ++y)
                    {
                        Color col = dtex.GetPixel(x, y);
                        col.r *= col.a; col.g *= col.a; col.b *= col.a;
                        col.a  = additive?0.0f:col.a;
                        dtex.SetPixel(x, y, col);
                    }
                }
            }

            dtex.Apply();

            spriteLut.rx  = sx + x0;
            spriteLut.ry  = sy + y0;
            spriteLut.rw  = w1;
            spriteLut.rh  = h1;
            spriteLut.tex = dtex;

            return(dtex);
        }
        else
        {
            return(null);
        }
    }
    public static void Rebuild(tk2dSpriteCollection gen)
    {
        // avoid "recursive" build being triggered by texture watcher
        if (currentBuild == gen)
        {
            return;
        }

        currentBuild = gen;

        string path       = AssetDatabase.GetAssetPath(gen);
        string subDirName = Path.GetDirectoryName(path.Substring(7));

        if (subDirName.Length > 0)
        {
            subDirName += "/";
        }

        string dataDirFullPath = Application.dataPath + "/" + subDirName + Path.GetFileNameWithoutExtension(path) + "_Data";
        string dataDirName     = "Assets/" + dataDirFullPath.Substring(Application.dataPath.Length + 1) + "/";

        if (gen.atlasTextures == null || gen.atlasTextures.Length == 0 ||
            gen.atlasMaterials == null || gen.atlasMaterials.Length == 0 ||
            gen.spriteCollection == null)
        {
            if (!Directory.Exists(dataDirFullPath))
            {
                Directory.CreateDirectory(dataDirFullPath);
            }
            AssetDatabase.Refresh();
        }

        string prefabObjectPath = gen.spriteCollection?AssetDatabase.GetAssetPath(gen.spriteCollection):(dataDirName + "data.prefab");

        if (!CheckAndFixUpParams(gen))
        {
            // Params failed check
            return;
        }

        SetUpSourceTextureFormats(gen);

        SetUpSpriteSheets(gen);

        TrimTextureList(gen);

        // blank texture used when texture has been deleted
        Texture2D blankTexture = new Texture2D(2, 2);

        blankTexture.SetPixel(0, 0, Color.magenta);
        blankTexture.SetPixel(0, 1, Color.yellow);
        blankTexture.SetPixel(1, 0, Color.cyan);
        blankTexture.SetPixel(1, 1, Color.grey);
        blankTexture.Apply();

        // make local texture sources
        sourceTextures = new Texture2D[gen.textureRefs.Length];
        for (int i = 0; i < gen.textureParams.Length; ++i)
        {
            var param = gen.textureParams[i];
            if (param.extractRegion && gen.textureRefs[i] != null)
            {
                Texture2D localTex = new Texture2D(param.regionW, param.regionH);
                for (int y = 0; y < param.regionH; ++y)
                {
                    for (int x = 0; x < param.regionW; ++x)
                    {
                        localTex.SetPixel(x, y, gen.textureRefs[i].GetPixel(param.regionX + x, param.regionY + y));
                    }
                }
                localTex.name = gen.textureRefs[i].name + "/" + param.regionId.ToString();
                localTex.Apply();
                sourceTextures[i] = localTex;
            }
            else
            {
                sourceTextures[i] = gen.textureRefs[i];
            }
        }

        // catalog all textures to atlas
        int numTexturesToAtlas           = 0;
        List <SCGE.SpriteLut> spriteLuts = new List <SCGE.SpriteLut>();

        for (int i = 0; i < gen.textureParams.Length; ++i)
        {
            Texture2D currentTexture = sourceTextures[i];

            if (sourceTextures[i] == null)
            {
                gen.textureParams[i].dice            = false;
                gen.textureParams[i].anchor          = tk2dSpriteCollectionDefinition.Anchor.MiddleCenter;
                gen.textureParams[i].name            = "";
                gen.textureParams[i].extractRegion   = false;
                gen.textureParams[i].fromSpriteSheet = false;

                currentTexture = blankTexture;
            }
            else
            {
                if (gen.textureParams[i].name == null || gen.textureParams[i].name == "" || gen.textureParams[i].texture != currentTexture)
                {
                    gen.textureParams[i].name = currentTexture.name;
                }
            }

            gen.textureParams[i].texture = currentTexture;


            if (gen.textureParams[i].dice)
            {
                // prepare to dice this up
                int diceUnitX = gen.textureParams[i].diceUnitX;
                int diceUnitY = gen.textureParams[i].diceUnitY;
                if (diceUnitX <= 0)
                {
                    diceUnitX = 128;                                 // something sensible, please
                }
                if (diceUnitY <= 0)
                {
                    diceUnitY = diceUnitX;                                 // make square if not set
                }
                Texture2D srcTex = currentTexture;
                for (int sx = 0; sx < srcTex.width; sx += diceUnitX)
                {
                    for (int sy = 0; sy < srcTex.height; sy += diceUnitY)
                    {
                        int tw = Mathf.Min(diceUnitX, srcTex.width - sx);
                        int th = Mathf.Min(diceUnitY, srcTex.height - sy);

                        SCGE.SpriteLut diceLut = new SCGE.SpriteLut();
                        diceLut.source      = i;
                        diceLut.isSplit     = true;
                        diceLut.sourceTex   = srcTex;
                        diceLut.isDuplicate = false;                         // duplicate diced textures can be chopped up differently, so don't detect dupes here

                        Texture2D dest = ProcessTexture(gen.premultipliedAlpha, gen.textureParams[i].additive, true, srcTex, sx, sy, tw, th, ref diceLut, GetPadAmount(gen));
                        if (dest)
                        {
                            diceLut.atlasIndex = numTexturesToAtlas++;
                            spriteLuts.Add(diceLut);
                        }
                    }
                }
            }
            else
            {
                SCGE.SpriteLut lut = new SCGE.SpriteLut();
                lut.sourceTex = currentTexture;
                lut.source    = i;

                lut.isSplit     = false;
                lut.isDuplicate = false;
                for (int j = 0; j < spriteLuts.Count; ++j)
                {
                    if (spriteLuts[j].sourceTex == lut.sourceTex)
                    {
                        lut.isDuplicate = true;
                        lut.atlasIndex  = spriteLuts[j].atlasIndex;
                        lut.tex         = spriteLuts[j].tex;                 // get old processed tex

                        lut.rx = spriteLuts[j].rx; lut.ry = spriteLuts[j].ry;
                        lut.rw = spriteLuts[j].rw; lut.rh = spriteLuts[j].rh;

                        break;
                    }
                }

                if (!lut.isDuplicate)
                {
                    lut.atlasIndex = numTexturesToAtlas++;
                    bool stretchPad = false;
                    if (gen.textureParams[i].pad == tk2dSpriteCollectionDefinition.Pad.Extend)
                    {
                        stretchPad = true;
                    }

                    Texture2D dest = ProcessTexture(gen.premultipliedAlpha, gen.textureParams[i].additive, stretchPad, currentTexture, 0, 0, currentTexture.width, currentTexture.height, ref lut, GetPadAmount(gen));
                    if (dest == null)
                    {
                        // fall back to a tiny blank texture
                        lut.tex = new Texture2D(1, 1);
                        lut.tex.SetPixel(0, 0, new Color(0, 0, 0, 0));
                        PadTexture(lut.tex, GetPadAmount(gen), stretchPad);
                        lut.tex.Apply();

                        lut.rx = currentTexture.width / 2; lut.ry = currentTexture.height / 2;
                        lut.rw = 1; lut.rh = 1;
                    }
                }

                spriteLuts.Add(lut);
            }
        }

        // Create texture
        Texture2D[] textureList = new Texture2D[numTexturesToAtlas];
        int         titer       = 0;

        for (int i = 0; i < spriteLuts.Count; ++i)
        {
            SCGE.SpriteLut _lut = spriteLuts[i];
            if (!_lut.isDuplicate)
            {
                textureList[titer++] = _lut.tex;
            }
        }

        // Build atlas
        Atlas.AtlasBuilder atlasBuilder = new Atlas.AtlasBuilder(gen.maxTextureSize, gen.maxTextureSize, gen.allowMultipleAtlases?64:1);
        if (textureList.Length > 0)
        {
            foreach (Texture2D currTexture in textureList)
            {
                atlasBuilder.AddRect(currTexture.width, currTexture.height);
            }
            if (atlasBuilder.Build() != 0)
            {
                if (atlasBuilder.HasOversizeTextures())
                {
                    EditorUtility.DisplayDialog("Unable to fit in atlas",
                                                "You have a texture which exceeds the atlas size." +
                                                "Consider putting it in a separate atlas, enabling dicing, or" +
                                                "reducing the texture size",
                                                "Ok");
                }
                else
                {
                    EditorUtility.DisplayDialog("Unable to fit textures in requested atlas area",
                                                "There are too many textures in this collection for the requested " +
                                                "atlas size.",
                                                "Ok");
                }
                return;
            }
        }

        // Fill atlas textures
        Atlas.AtlasData[] atlasData = atlasBuilder.GetAtlasData();
        System.Array.Resize(ref gen.atlasTextures, atlasData.Length);
        System.Array.Resize(ref gen.atlasMaterials, atlasData.Length);
        for (int atlasIndex = 0; atlasIndex < atlasData.Length; ++atlasIndex)
        {
            Texture2D tex = new Texture2D(64, 64, TextureFormat.ARGB32, false);

            gen.atlasWastage = (1.0f - atlasData[0].occupancy) * 100.0f;
            gen.atlasWidth   = atlasData[0].width;
            gen.atlasHeight  = atlasData[0].height;

            tex.Resize(atlasData[atlasIndex].width, atlasData[atlasIndex].height);

            // Clear texture, unsure if this is REALLY necessary
            for (int yy = 0; yy < tex.height; ++yy)
            {
                for (int xx = 0; xx < tex.width; ++xx)
                {
                    tex.SetPixel(xx, yy, Color.black);
                }
            }

            for (int i = 0; i < atlasData[atlasIndex].entries.Length; ++i)
            {
                var       entry  = atlasData[atlasIndex].entries[i];
                Texture2D source = textureList[entry.index];

                if (!entry.flipped)
                {
                    for (int y = 0; y < source.height; ++y)
                    {
                        for (int x = 0; x < source.width; ++x)
                        {
                            tex.SetPixel(entry.x + x, entry.y + y, source.GetPixel(x, y));
                        }
                    }
                }
                else
                {
                    for (int y = 0; y < source.height; ++y)
                    {
                        for (int x = 0; x < source.width; ++x)
                        {
                            tex.SetPixel(entry.x + y, entry.y + x, source.GetPixel(x, y));
                        }
                    }
                }
            }

            tex.Apply();

            string texturePath = gen.atlasTextures[atlasIndex]?AssetDatabase.GetAssetPath(gen.atlasTextures[atlasIndex]):(dataDirName + "atlas" + atlasIndex + ".png");

            // Write filled atlas to disk
            byte[] bytes            = tex.EncodeToPNG();
            System.IO.FileStream fs = System.IO.File.Create(texturePath);
            fs.Write(bytes, 0, bytes.Length);
            fs.Close();

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            tex = AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D)) as Texture2D;
            gen.atlasTextures[atlasIndex] = tex;

            // Make sure texture is set up with the correct max size and compression type
            SetUpTargetTexture(gen, tex);

            // Create material if necessary
            if (gen.atlasMaterials[atlasIndex] == null)
            {
                Material mat;
                if (gen.premultipliedAlpha)
                {
                    mat = new Material(Shader.Find("tk2d/PremulVertexColor"));
                }
                else
                {
                    mat = new Material(Shader.Find("tk2d/BlendVertexColor"));
                }

                mat.mainTexture = tex;

                string materialPath = gen.atlasMaterials[atlasIndex]?AssetDatabase.GetAssetPath(gen.atlasMaterials[atlasIndex]):(dataDirName + "atlas" + atlasIndex + "_material.mat");
                AssetDatabase.CreateAsset(mat, materialPath);
                AssetDatabase.SaveAssets();

                gen.atlasMaterials[atlasIndex] = AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material)) as Material;
            }
        }

        // Create prefab
        if (gen.spriteCollection == null)
        {
            Object     p  = EditorUtility.CreateEmptyPrefab(prefabObjectPath);
            GameObject go = new GameObject();
            go.AddComponent <tk2dSpriteCollectionData>();
            EditorUtility.ReplacePrefab(go, p);
            GameObject.DestroyImmediate(go);
            AssetDatabase.SaveAssets();

            gen.spriteCollection = AssetDatabase.LoadAssetAtPath(prefabObjectPath, typeof(tk2dSpriteCollectionData)) as tk2dSpriteCollectionData;
        }

        tk2dSpriteCollectionData coll = gen.spriteCollection;

        coll.textures  = new Texture[gen.atlasTextures.Length];
        coll.materials = new Material[gen.atlasMaterials.Length];
        for (int i = 0; i < gen.atlasTextures.Length; ++i)
        {
            coll.textures[i] = gen.atlasTextures[i];
        }
        for (int i = 0; i < gen.atlasMaterials.Length; ++i)
        {
            coll.materials[i] = gen.atlasMaterials[i];
        }

        // Wipe out legacy data
        coll.material = null;

        coll.premultipliedAlpha   = gen.premultipliedAlpha;
        coll.spriteDefinitions    = new tk2dSpriteDefinition[gen.textureParams.Length];
        coll.version              = tk2dSpriteCollectionData.CURRENT_VERSION;
        coll.spriteCollectionGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(gen));
        coll.spriteCollectionName = gen.name;
        for (int i = 0; i < coll.spriteDefinitions.Length; ++i)
        {
            coll.spriteDefinitions[i] = new tk2dSpriteDefinition();
            if (gen.textureRefs[i])
            {
                string assetPath = AssetDatabase.GetAssetPath(gen.textureRefs[i]);
                string guid      = AssetDatabase.AssetPathToGUID(assetPath);
                coll.spriteDefinitions[i].sourceTextureGUID = guid;
            }
            else
            {
                coll.spriteDefinitions[i].sourceTextureGUID = "";
            }
            coll.spriteDefinitions[i].extractRegion = gen.textureParams[i].extractRegion;
            coll.spriteDefinitions[i].regionX       = gen.textureParams[i].regionX;
            coll.spriteDefinitions[i].regionY       = gen.textureParams[i].regionY;
            coll.spriteDefinitions[i].regionW       = gen.textureParams[i].regionW;
            coll.spriteDefinitions[i].regionH       = gen.textureParams[i].regionH;
        }
        coll.allowMultipleAtlases = gen.allowMultipleAtlases;
        UpdateVertexCache(gen, atlasData, coll, spriteLuts);

        // refresh existing
        tk2dSprite[] sprs = Resources.FindObjectsOfTypeAll(typeof(tk2dSprite)) as tk2dSprite[];
        foreach (tk2dSprite spr in sprs)
        {
            if (spr.collection == gen.spriteCollection)
            {
                if (spr.spriteId < 0 || spr.spriteId >= spr.collection.spriteDefinitions.Length)
                {
                    spr.spriteId = 0;
                }

                spr.Build();
            }
        }
        tk2dStaticSpriteBatcher[] batchedSprs = Resources.FindObjectsOfTypeAll(typeof(tk2dStaticSpriteBatcher)) as tk2dStaticSpriteBatcher[];
        foreach (var spr in batchedSprs)
        {
            if (spr.spriteCollection == gen.spriteCollection)
            {
                spr.Build();
            }
        }

        // save changes
        EditorUtility.SetDirty(gen.spriteCollection);
        EditorUtility.SetDirty(gen);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();

        sourceTextures = null;         // need to clear, its static
        currentBuild   = null;

        tk2dEditorUtility.GetOrCreateIndex().AddSpriteCollectionData(gen.spriteCollection);
        tk2dEditorUtility.CommitIndex();
    }
    public static bool Rebuild(tk2dSpriteCollection gen)
    {
        // avoid "recursive" build being triggered by texture watcher
        if (currentBuild == gen)
            return false;

        currentBuild = gen;

        string path = AssetDatabase.GetAssetPath(gen);
        string subDirName = Path.GetDirectoryName( path.Substring(7) );
        if (subDirName.Length > 0) subDirName += "/";

        string dataDirFullPath = Application.dataPath + "/" + subDirName + Path.GetFileNameWithoutExtension(path) + "_Data";
        string dataDirName = "Assets/" + dataDirFullPath.Substring( Application.dataPath.Length + 1 ) + "/";

        if (gen.atlasTextures == null || gen.atlasTextures.Length == 0 ||
            gen.atlasMaterials == null || gen.atlasMaterials.Length == 0 ||
            gen.spriteCollection == null)
        {
            if (!Directory.Exists(dataDirFullPath)) Directory.CreateDirectory(dataDirFullPath);
            AssetDatabase.Refresh();
        }

        string prefabObjectPath = gen.spriteCollection?AssetDatabase.GetAssetPath(gen.spriteCollection):(dataDirName + "data.prefab");

          	if (!CheckAndFixUpParams(gen))
        {
            // Params failed check
            return false;
        }

        SetUpSourceTextureFormats(gen);

        SetUpSpriteSheets(gen);

        TrimTextureList(gen);

        // blank texture used when texture has been deleted
        Texture2D blankTexture = new Texture2D(2, 2);
        blankTexture.SetPixel(0, 0, Color.magenta);
        blankTexture.SetPixel(0, 1, Color.yellow);
        blankTexture.SetPixel(1, 0, Color.cyan);
        blankTexture.SetPixel(1, 1, Color.grey);
        blankTexture.Apply();

        // make local texture sources
        sourceTextures = new Texture2D[gen.textureRefs.Length];
        for (int i = 0; i < gen.textureParams.Length; ++i)
        {
            var param = gen.textureParams[i];
            if (param.extractRegion && gen.textureRefs[i] != null)
            {
                Texture2D localTex = new Texture2D(param.regionW, param.regionH);
                for (int y = 0; y < param.regionH; ++y)
                {
                    for (int x = 0; x < param.regionW; ++x)
                    {
                        localTex.SetPixel(x, y, gen.textureRefs[i].GetPixel(param.regionX + x, param.regionY + y));
                    }
                }
                localTex.name = gen.textureRefs[i].name + "/" + param.regionId.ToString();
                localTex.Apply();
                sourceTextures[i] = localTex;
            }
            else
            {
                sourceTextures[i] = gen.textureRefs[i];
            }
        }

        // catalog all textures to atlas
        int numTexturesToAtlas = 0;
        List<SCGE.SpriteLut> spriteLuts = new List<SCGE.SpriteLut>();
        for (int i = 0; i < gen.textureParams.Length; ++i)
        {
            Texture2D currentTexture = sourceTextures[i];

            if (sourceTextures[i] == null)
            {
                gen.textureParams[i].dice = false;
                gen.textureParams[i].anchor = tk2dSpriteCollectionDefinition.Anchor.MiddleCenter;
                gen.textureParams[i].name = "";
                gen.textureParams[i].extractRegion = false;
                gen.textureParams[i].fromSpriteSheet = false;

                currentTexture = blankTexture;
            }
            else
            {
                if (gen.textureParams[i].name == null || gen.textureParams[i].name == "" || gen.textureParams[i].texture != currentTexture)
                    gen.textureParams[i].name = currentTexture.name;
            }

            gen.textureParams[i].texture = currentTexture;

            if (gen.textureParams[i].dice)
            {
                // prepare to dice this up
                int diceUnitX = gen.textureParams[i].diceUnitX;
                int diceUnitY = gen.textureParams[i].diceUnitY;
                if (diceUnitX <= 0) diceUnitX = 128; // something sensible, please
                if (diceUnitY <= 0) diceUnitY = diceUnitX; // make square if not set

                Texture2D srcTex = currentTexture;
                for (int sx = 0; sx < srcTex.width; sx += diceUnitX)
                {
                    for (int sy = 0; sy < srcTex.height; sy += diceUnitY)
                    {
                        int tw = Mathf.Min(diceUnitX, srcTex.width - sx);
                        int th = Mathf.Min(diceUnitY, srcTex.height - sy);

                        SCGE.SpriteLut diceLut = new SCGE.SpriteLut();
                        diceLut.source = i;
                        diceLut.isSplit = true;
                        diceLut.sourceTex = srcTex;
                        diceLut.isDuplicate = false; // duplicate diced textures can be chopped up differently, so don't detect dupes here

                        Texture2D dest = ProcessTexture(gen.premultipliedAlpha, gen.textureParams[i].additive, true, srcTex, sx, sy, tw, th, ref diceLut, GetPadAmount(gen));
                        if (dest)
                        {
                            diceLut.atlasIndex = numTexturesToAtlas++;
                            spriteLuts.Add(diceLut);
                        }
                    }
                }
            }
            else
            {
                SCGE.SpriteLut lut = new SCGE.SpriteLut();
                lut.sourceTex = currentTexture;
                lut.source = i;

                lut.isSplit = false;
                lut.isDuplicate = false;
                for (int j = 0; j < spriteLuts.Count; ++j)
                {
                    if (spriteLuts[j].sourceTex == lut.sourceTex)
                    {
                        lut.isDuplicate = true;
                        lut.atlasIndex = spriteLuts[j].atlasIndex;
                        lut.tex = spriteLuts[j].tex; // get old processed tex

                        lut.rx = spriteLuts[j].rx; lut.ry = spriteLuts[j].ry;
                        lut.rw = spriteLuts[j].rw; lut.rh = spriteLuts[j].rh;

                        break;
                    }
                }

                if (!lut.isDuplicate)
                {
                    lut.atlasIndex = numTexturesToAtlas++;
                    bool stretchPad = false;
                    if (gen.textureParams[i].pad == tk2dSpriteCollectionDefinition.Pad.Extend) stretchPad = true;

                    Texture2D dest = ProcessTexture(gen.premultipliedAlpha, gen.textureParams[i].additive, stretchPad, currentTexture, 0, 0, currentTexture.width, currentTexture.height, ref lut, GetPadAmount(gen));
                    if (dest == null)
                    {
                        // fall back to a tiny blank texture
                        lut.tex = new Texture2D(1, 1);
                        lut.tex.SetPixel(0, 0, new Color( 0, 0, 0, 0 ));
                        PadTexture(lut.tex, GetPadAmount(gen), stretchPad);
                        lut.tex.Apply();

                        lut.rx = currentTexture.width / 2; lut.ry = currentTexture.height / 2;
                        lut.rw = 1; lut.rh = 1;
                    }
                }

                spriteLuts.Add(lut);
            }
        }

        // Create texture
        Texture2D[] textureList = new Texture2D[numTexturesToAtlas];
        int titer = 0;
        for (int i = 0; i < spriteLuts.Count; ++i)
        {
            SCGE.SpriteLut _lut = spriteLuts[i];
            if (!_lut.isDuplicate)
            {
                textureList[titer++] = _lut.tex;
            }
        }

        // Build atlas
        tk2dAtlas.AtlasBuilder atlasBuilder = new tk2dAtlas.AtlasBuilder(gen.maxTextureSize, gen.maxTextureSize, gen.allowMultipleAtlases?64:1);
        if (textureList.Length > 0)
        {
            foreach (Texture2D currTexture in textureList)
            {
                atlasBuilder.AddRect(currTexture.width, currTexture.height);
            }
            if (atlasBuilder.Build() != 0)
            {
                if (atlasBuilder.HasOversizeTextures())
                {
                    EditorUtility.DisplayDialog("Unable to fit in atlas",
                                                "You have a texture which exceeds the atlas size." +
                                                "Consider putting it in a separate atlas, enabling dicing, or" +
                                                "reducing the texture size",
                                                "Ok");
                }
                else
                {
                    EditorUtility.DisplayDialog("Unable to fit textures in requested atlas area",
                                                "There are too many textures in this collection for the requested " +
                                                "atlas size.",
                                                "Ok");
                }
                return false;
            }
        }

        // Fill atlas textures
        tk2dAtlas.AtlasData[] atlasData = atlasBuilder.GetAtlasData();
        System.Array.Resize(ref gen.atlasTextures, atlasData.Length);
        System.Array.Resize(ref gen.atlasMaterials, atlasData.Length);
        for (int atlasIndex = 0; atlasIndex < atlasData.Length; ++atlasIndex)
        {
            Texture2D tex = new Texture2D(64, 64, TextureFormat.ARGB32, false);

            gen.atlasWastage = (1.0f - atlasData[0].occupancy) * 100.0f;
            gen.atlasWidth = atlasData[0].width;
            gen.atlasHeight = atlasData[0].height;

            tex.Resize(atlasData[atlasIndex].width, atlasData[atlasIndex].height);

            // Clear texture, unsure if this is REALLY necessary
            // Turns out it is
            for (int yy = 0; yy < tex.height; ++yy)
            {
                for (int xx = 0; xx < tex.width; ++xx)
                {
                    tex.SetPixel(xx, yy, Color.clear);
                }
            }

            for (int i = 0; i < atlasData[atlasIndex].entries.Length; ++i)
            {
                var entry = atlasData[atlasIndex].entries[i];
                Texture2D source = textureList[entry.index];

                if (!entry.flipped)
                {
                    for (int y = 0; y < source.height; ++y)
                    {
                        for (int x = 0; x < source.width; ++x)
                        {
                            tex.SetPixel(entry.x + x, entry.y + y, source.GetPixel(x, y));
                        }
                    }
                }
                else
                {
                    for (int y = 0; y < source.height; ++y)
                    {
                        for (int x = 0; x < source.width; ++x)
                        {
                            tex.SetPixel(entry.x + y, entry.y + x, source.GetPixel(x, y));
                        }
                    }
                }
            }

            tex.Apply();

            string texturePath = gen.atlasTextures[atlasIndex]?AssetDatabase.GetAssetPath(gen.atlasTextures[atlasIndex]):(dataDirName + "atlas" + atlasIndex + ".png");

            // Write filled atlas to disk
            byte[] bytes = tex.EncodeToPNG();
            System.IO.FileStream fs = System.IO.File.Create(texturePath);
            fs.Write(bytes, 0, bytes.Length);
            fs.Close();

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            tex = AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D)) as Texture2D;
            gen.atlasTextures[atlasIndex] = tex;

            // Make sure texture is set up with the correct max size and compression type
            SetUpTargetTexture(gen, tex);

            // Create material if necessary
            if (gen.atlasMaterials[atlasIndex] == null)
            {
                Material mat;
                if (gen.premultipliedAlpha)
                    mat = new Material(Shader.Find("tk2d/PremulVertexColor"));
                else
                    mat = new Material(Shader.Find("tk2d/BlendVertexColor"));

                mat.mainTexture = tex;

                string materialPath = gen.atlasMaterials[atlasIndex]?AssetDatabase.GetAssetPath(gen.atlasMaterials[atlasIndex]):(dataDirName + "atlas" + atlasIndex + "_material.mat");
                AssetDatabase.CreateAsset(mat, materialPath);
                AssetDatabase.SaveAssets();

                gen.atlasMaterials[atlasIndex] = AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material)) as Material;
            }
        }

        // Create prefab
        if (gen.spriteCollection == null)
        {
            Object p = EditorUtility.CreateEmptyPrefab(prefabObjectPath);
            GameObject go = new GameObject();
            go.AddComponent<tk2dSpriteCollectionData>();
            EditorUtility.ReplacePrefab(go, p);
            GameObject.DestroyImmediate(go);
            AssetDatabase.SaveAssets();

            gen.spriteCollection = AssetDatabase.LoadAssetAtPath(prefabObjectPath, typeof(tk2dSpriteCollectionData)) as tk2dSpriteCollectionData;
        }

        tk2dSpriteCollectionData coll = gen.spriteCollection;
        coll.textures = new Texture[gen.atlasTextures.Length];
        coll.materials = new Material[gen.atlasMaterials.Length];
        for (int i = 0; i < gen.atlasTextures.Length; ++i)
        {
            coll.textures[i] = gen.atlasTextures[i];
        }
        for (int i = 0; i < gen.atlasMaterials.Length; ++i)
        {
            coll.materials[i] = gen.atlasMaterials[i];
        }

        // Wipe out legacy data
        coll.material = null;

        coll.premultipliedAlpha = gen.premultipliedAlpha;
        coll.spriteDefinitions = new tk2dSpriteDefinition[gen.textureParams.Length];
        coll.version = tk2dSpriteCollectionData.CURRENT_VERSION;
        coll.spriteCollectionGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(gen));
        coll.spriteCollectionName = gen.name;
        coll.invOrthoSize = 1.0f / gen.targetOrthoSize;

        int buildKey = Random.Range(0, int.MaxValue);
        while (buildKey == coll.buildKey)
        {
            buildKey = Random.Range(0, int.MaxValue);
        }
        coll.buildKey = buildKey; // a random build number so we can identify changed collections quickly

        for (int i = 0; i < coll.spriteDefinitions.Length; ++i)
        {
            coll.spriteDefinitions[i] = new tk2dSpriteDefinition();
            if (gen.textureRefs[i])
            {
                string assetPath = AssetDatabase.GetAssetPath(gen.textureRefs[i]);
                string guid = AssetDatabase.AssetPathToGUID(assetPath);
                coll.spriteDefinitions[i].sourceTextureGUID = guid;
            }
            else
            {
                coll.spriteDefinitions[i].sourceTextureGUID = "";
            }

            coll.spriteDefinitions[i].extractRegion = gen.textureParams[i].extractRegion;
            coll.spriteDefinitions[i].regionX = gen.textureParams[i].regionX;
            coll.spriteDefinitions[i].regionY = gen.textureParams[i].regionY;
            coll.spriteDefinitions[i].regionW = gen.textureParams[i].regionW;
            coll.spriteDefinitions[i].regionH = gen.textureParams[i].regionH;
        }
        coll.allowMultipleAtlases = gen.allowMultipleAtlases;
        coll.guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(coll));
        UpdateVertexCache(gen, atlasData, coll, spriteLuts);

        // refresh existing
        tk2dSprite[] sprs = Resources.FindObjectsOfTypeAll(typeof(tk2dSprite)) as tk2dSprite[];
        foreach (tk2dSprite spr in sprs)
        {
            if (spr.collection == gen.spriteCollection)
            {
                if (spr.spriteId < 0 || spr.spriteId >= spr.collection.spriteDefinitions.Length)
                    spr.spriteId = 0;

                spr.Build();
                spr.EditMode__CreateCollider();
            }
        }
        tk2dStaticSpriteBatcher[] batchedSprs = Resources.FindObjectsOfTypeAll(typeof(tk2dStaticSpriteBatcher)) as tk2dStaticSpriteBatcher[];
        foreach (var spr in batchedSprs)
        {
            if (spr.spriteCollection == gen.spriteCollection)
            {
                spr.Build();
            }
        }

        // save changes
        var index = tk2dEditorUtility.GetOrCreateIndex();
        index.AddSpriteCollectionData(gen.spriteCollection);
        EditorUtility.SetDirty(gen.spriteCollection);
        EditorUtility.SetDirty(gen);

        sourceTextures = null; // need to clear, its static
        currentBuild = null;

        tk2dEditorUtility.GetOrCreateIndex().AddSpriteCollectionData(gen.spriteCollection);
        tk2dEditorUtility.CommitIndex();

        tk2dSpriteCollectionEditorPopup.OnRebuild();

        return true;
    }