private void CreateAtlas(Hashtable aRow)
    {
        Material atlasMat = (Material)aRow["atlasMat"];
        Material origMat = (Material)aRow["origMat"];

        // find 1st row that uses this atlasMat
        // (1st row defines which keys are active)
        int minOrder = 999999;
        Hashtable masterRow = null;
        for(int i=0;i<submeshRows.Count;i++) {
            Hashtable row = (Hashtable)submeshRows[i];
            if((Material)row["atlasMat"] == atlasMat) {
                if(minOrder > (int)row["order"]) {
                    masterRow = row;
                    minOrder = (int)row["order"];
                }
            }
        }
        int maxAtlasSize = (int)masterRow["atlasMaxSize"];

        // find texture keys to use
        atlasMat = (Material)masterRow["atlasMat"];
        List<string> keys = new List<string>();
        string masterTexKey = null;
        Texture masterTex = null;
        foreach(string key in textureKeys) {
            if(atlasMat.HasProperty(key)) {
                keys.Add(key);
                if(masterTexKey == null || masterTex == null || masterTex.GetType() != typeof(Texture2D)) {
                    masterTexKey = key;
                    masterTex = atlasMat.GetTexture(key);
                }
            }
        }
        if(masterTex == null || masterTex.GetType() != typeof(Texture2D)) return;

        // find all rows that have the same base texture or same atlasMat as masterRow
        List<Hashtable> atlasRows = new List<Hashtable>();
        for(int i=0;i<submeshRows.Count;i++) {
            Hashtable row = (Hashtable)submeshRows[i];
            Material m = (Material)row["atlasMat"];
            if(row["atlasMat"] == atlasMat || (m.HasProperty(masterTexKey) && m.GetTexture(masterTexKey) == masterTex)) {
                atlasRows.Add(row);
            }
        }

        // find base textures per material key
        List<Texture2D> texturesToInclude = new List<Texture2D>();
        for(int i=0;i<keys.Count;i++) {
            string key = keys[i];
            for(int j=0;j<atlasRows.Count;j++) {
                Material m = (Material)atlasRows[j]["origMat"];
                Texture tex = null;
                if(m.HasProperty(key)) tex = m.GetTexture(key);
                if(tex != null && tex.GetType() != typeof(Texture2D)) tex = null;
                texturesToInclude.Add((Texture2D)tex);
            }
        }

        if(texturesToInclude.Count <= keys.Count) { // Only 1 row included in atlas
            for(int j=0;j<atlasRows.Count;j++) {
                origMat = (Material)atlasRows[j]["origMat"];
                atlasMat = (Material)atlasRows[j]["atlasMat"];
                for(int i=0;i<keys.Count;i++) {
                    string key = keys[i];
                    Texture tex = null;
                    if(origMat.HasProperty(key)) tex = origMat.GetTexture(key);
                    if(atlasMat.HasProperty(key)) atlasMat.SetTexture(key, tex);
                }
            }
        } else {  // make an atlas per key

            string key = keys[0];
            List<Texture2D> tList = new List<Texture2D>();
            List<int> rowIndexes = new List<int>();
            int nrOfUniqueTextures = 0;
            for(int j=0;j<atlasRows.Count;j++) {
                Texture2D t = texturesToInclude[j];
                if(t != null) {
                    float atlasSize  = (float)((int)atlasRows[j]["atlasSize"]) / 2f;
                    float scale = 1f / atlasSize;
                    if(scale != 1f) t = t.ScaledCopy((int)(t.width * scale), (int)(t.height * scale), false);
                    int k=0;
                    for(;k<tList.Count;k++) {
                        if(tList[k] == t) break;
                    }
                    if(k>=tList.Count) nrOfUniqueTextures++;
                    tList.Add(t);
                    rowIndexes.Add(j);
                }
            }

            Rect[] rects = null;
            Texture2D atlas = null;

            if(nrOfUniqueTextures == 1) {
                for(int j=0;j<atlasRows.Count;j++) {
                    origMat = (Material)atlasRows[j]["origMat"];
                    atlasMat = (Material)atlasRows[j]["atlasMat"];
                    for(int i=0;i<keys.Count;i++) {
                        key = keys[i];
                        Texture tex = null;
                        if(origMat.HasProperty(key)) tex = origMat.GetTexture(key);
                        if(atlasMat.HasProperty(key)) atlasMat.SetTexture(key, tex);
                    }
                    atlasRows[j]["isAtlas"] = false;
                }
            } else if(tList.Count>1) {
                atlas = new Texture2D(512, 512);
                rects = atlas.PackTextures(tList.ToArray(), 0, maxAtlasSize);
                masterRow["atlasMaxSize"] = Mathf.Max(atlas.width, atlas.height);
                for(int j=0;j<rowIndexes.Count;j++) {  // clear current texture
                    Hashtable row = atlasRows[rowIndexes[j]];
                    atlasMat = (Material)row["atlasMat"];
                    row["atlasRect"] = new Rect(0,0,1,1);
                    if(atlasMat.HasProperty(key)) atlasMat.SetTexture(key, null);
                }
                for(int j=0;j<rowIndexes.Count;j++) {  // set atlas texture
                    Hashtable row = atlasRows[rowIndexes[j]];
                    atlasMat = (Material)row["atlasMat"];
                    row["atlasRect"] = rects[j];
                    if(atlasMat.HasProperty(key)) atlasMat.SetTexture(key, atlas);
                    row["isAtlas"] = true;
                }

                for(int i=1;i<keys.Count;i++) {
                    key = keys[i];
                    Texture2D subAtlas = new Texture2D(atlas.width, atlas.height, TextureFormat.RGBA32, true);
                    subAtlas.Fill(new Color(0,0,0,0));
                    for(int j=0;j<rowIndexes.Count;j++) {  // clear current texture
                        Hashtable row = atlasRows[rowIndexes[j]];
                        Rect atlasRect = (Rect)row["atlasRect"];
                        origMat = (Material)row["origMat"];
                        if(origMat.HasProperty(key) && atlasMat.HasProperty(key)) {
                            Texture t = origMat.GetTexture(key);
                            if(t != null && t.GetType() == typeof(Texture2D)) {
                                if(key == "_BumpMap" || key == "_NormalMap" || key == "_DetailBumpMap" || key == "_DetailNormalMap") {
                                    t = ((Texture2D)t).FromUnityNormalMap();
                                }
                                t = ((Texture2D)t).ScaledCopy(Mathf.RoundToInt(atlas.width * atlasRect.width), Mathf.RoundToInt(atlas.height * atlasRect.height), false);
                                subAtlas.CopyFrom((Texture2D)t, Mathf.RoundToInt(atlas.width * atlasRect.x), Mathf.RoundToInt(atlas.height * atlasRect.y), 0, 0, t.width, t.height);
                                atlasMat.SetTexture(key, subAtlas);
                            }
                        }
                    }
                }
            }
        }
        Resources.UnloadUnusedAssets();
    }