Пример #1
0
    void AddCombine(SA3TextureCombine tb, string key, List <Renderer> gaws)
    {
        int numVerts = 0;

        for (int i = 0; i < gaws.Count; i++)
        {
            Mesh m = SAUtility.GetMesh(gaws[i].gameObject);
            if (m != null)
            {
                numVerts += m.vertexCount;
            }
        }

        GameObject nmb = new GameObject("CombineIn." + key);

        nmb.transform.position = Vector3.zero;
        SA3MeshCombineCommon newCombine;

        if (numVerts >= 65535)
        {
            newCombine = nmb.AddComponent <SA3MultiMeshCombine>();
            newCombine.useObjsToMeshFromTexBaker = false;
        }
        else
        {
            newCombine = nmb.AddComponent <SA3MeshCombine>();
            newCombine.useObjsToMeshFromTexBaker = false;
        }
        newCombine.textureBakeResults = tb.textureBakeResults;
        newCombine.transform.parent   = tb.transform;
        for (int i = 0; i < gaws.Count; i++)
        {
            newCombine.GetObjectsToCombine().Add(gaws[i].gameObject);
        }
    }
Пример #2
0
    /**
     * pass in System.IO.File.WriteAllBytes for parameter fileSaveFunction. This is necessary because on Web Player file saving
     * functions only exist for Editor classes
     */
    public void SaveAtlasToAssetDatabase(Texture2D atlas, string texPropertyName, int atlasNum, Material resMat)
    {
        string prefabPth = AssetDatabase.GetAssetPath(resMat);

        if (prefabPth == null || prefabPth.Length == 0)
        {
            Debug.LogError("Could save atlas. Could not find result material in AssetDatabase.");
            return;
        }
        string baseName       = System.IO.Path.GetFileNameWithoutExtension(prefabPth);
        string folderPath     = prefabPth.Substring(0, prefabPth.Length - baseName.Length - 4);
        string fullFolderPath = Application.dataPath + folderPath.Substring("Assets".Length, folderPath.Length - "Assets".Length);

        string pth = fullFolderPath + baseName + "-" + texPropertyName + "-atlas" + atlasNum + ".png";

        Debug.Log("Created atlas for: " + texPropertyName + " at " + pth);
        //need to create a copy because sometimes the packed atlases are not in ARGB32 format
        Texture2D newTex = SAUtility.createTextureCopy(atlas);
        int       size   = Mathf.Max(newTex.height, newTex.width);

        byte[] bytes = newTex.EncodeToPNG();
        Editor.DestroyImmediate(newTex);


        System.IO.File.WriteAllBytes(pth, bytes);

        AssetDatabase.Refresh();

        string relativePath = folderPath + baseName + "-" + texPropertyName + "-atlas" + atlasNum + ".png";

        SetTextureSize((Texture2D)(AssetDatabase.LoadAssetAtPath(relativePath, typeof(Texture2D))), size);
        SetMaterialTextureProperty(resMat, texPropertyName, relativePath);
    }
Пример #3
0
 public override void DestroyMesh()
 {
     for (int i = 0; i < meshCombiners.Count; i++)
     {
         if (meshCombiners[i].combinedMesh.targetRenderer != null)
         {
             SAUtility.Destroy(meshCombiners[i].combinedMesh.targetRenderer.gameObject);
         }
         meshCombiners[i].combinedMesh.ClearMesh();
     }
     obj2MeshCombinerMap.Clear();
     meshCombiners.Clear();
 }
Пример #4
0
    static bool validateSubmeshOverlap(SA3MeshCombineRoot mom)
    {
        List <GameObject> objsToMesh = mom.GetObjectsToCombine();

        for (int i = 0; i < objsToMesh.Count; i++)
        {
            Mesh m = SAUtility.GetMesh(objsToMesh[i]);
            if (SAUtility.doSubmeshesShareVertsOrTris(m) != 0)
            {
                Debug.LogWarning("Object " + objsToMesh[i] + " in the list of objects to combine has overlapping submeshes (submeshes share vertices). If the UVs associated with the shared vertices are important then this bake may not work. If you are using multiple materials then this object can only be combined with objects that use the exact same set of textures (each atlas contains one texture). There may be other undesirable side affects as well. Mesh Master, available in the asset store can fix overlapping submeshes.");
                return(true);
            }
        }
        return(true);
    }
Пример #5
0
 public void EnableDisableSourceObjectRenderers(bool show)
 {
     for (int i = 0; i < GetObjectsToCombine().Count; i++)
     {
         GameObject go = GetObjectsToCombine()[i];
         if (go != null)
         {
             Renderer mr = SAUtility.GetRenderer(go);
             if (mr != null)
             {
                 mr.enabled = show;
             }
         }
     }
 }
Пример #6
0
 public bool ValidateSkinnedMeshes(List <GameObject> objs)
 {
     for (int i = 0; i < objs.Count; i++)
     {
         Renderer r = SAUtility.GetRenderer(objs[i]);
         if (r is SkinnedMeshRenderer)
         {
             Transform[] bones = ((SkinnedMeshRenderer)r).bones;
             if (bones.Length == 0)
             {
                 Debug.LogWarning("SkinnedMesh " + i + " (" + objs[i] + ") in the list of objects to combine has no bones. Check that 'optimize game object' is not checked in the 'Rig' tab of the asset importer. Mesh Baker cannot combine optimized skinned meshes because the bones are not available.");
             }
         }
     }
     return(true);
 }
Пример #7
0
 /// <summary>
 /// Creates the atlases.
 /// </summary>
 /// <returns>
 /// The atlases.
 /// </returns>
 /// <param name='progressInfo'>
 /// Progress info is a delegate function that displays a progress dialog. Can be null
 /// </param>
 /// <param name='saveAtlasesAsAssets'>
 /// if true atlases are saved as assets in the project folder. Othersise they are instances in memory
 /// </param>
 /// <param name='editorMethods'>
 /// Texture format tracker. Contains editor functionality such as save assets. Can be null.
 /// </param>
 public SAAtlasesAndRects[] CreateAtlases(ProgressUpdateDelegate progressInfo, bool saveAtlasesAsAssets = false, SA2EditorMethodsInterface editorMethods = null)
 {
     SAAtlasesAndRects[] mAndAs = null;
     try
     {
         mAndAs = _CreateAtlases(progressInfo, saveAtlasesAsAssets, editorMethods);
     }
     catch (Exception e)
     {
         Debug.LogError(e);
     }
     finally
     {
         if (saveAtlasesAsAssets)
         { //Atlases were saved to project so we don't need these ones
             if (mAndAs != null)
             {
                 for (int j = 0; j < mAndAs.Length; j++)
                 {
                     SAAtlasesAndRects mAndA = mAndAs[j];
                     if (mAndA != null && mAndA.atlases != null)
                     {
                         for (int i = 0; i < mAndA.atlases.Length; i++)
                         {
                             if (mAndA.atlases[i] != null)
                             {
                                 if (editorMethods != null)
                                 {
                                     editorMethods.Destroy(mAndA.atlases[i]);
                                 }
                                 else
                                 {
                                     SAUtility.Destroy(mAndA.atlases[i]);
                                 }
                             }
                         }
                     }
                 }
             }
         }
     }
     return(mAndAs);
 }
Пример #8
0
    public static bool DoCombinedValidate(SA3MeshCombineRoot mom, SAObjsToCombineTypes objToCombineType, SA2EditorMethodsInterface editorMethods)
    {
        if (mom.textureBakeResults == null)
        {
            Debug.LogError("Need to set Material Bake Result on " + mom);
            return(false);
        }
        if (!(mom is SA3TextureCombine))
        {
            SA3TextureCombine tb = mom.GetComponent <SA3TextureCombine>();
            if (tb != null && tb.textureBakeResults != mom.textureBakeResults)
            {
                Debug.LogWarning("Material Bake Result on this component is not the same as the Material Bake Result on the SA3TextureCombine.");
            }
        }

        List <GameObject> objsToMesh = mom.GetObjectsToCombine();

        for (int i = 0; i < objsToMesh.Count; i++)
        {
            GameObject go = objsToMesh[i];
            if (go == null)
            {
                Debug.LogError("The list of objects to combine contains a null at position." + i + " Select and use [shift] delete to remove");
                return(false);
            }
            for (int j = i + 1; j < objsToMesh.Count; j++)
            {
                if (objsToMesh[i] == objsToMesh[j])
                {
                    Debug.LogError("The list of objects to combine contains duplicates.");
                    return(false);
                }
            }
            if (SAUtility.GetGOMaterials(go) == null)
            {
                Debug.LogError("Object " + go + " in the list of objects to be combined does not have a material");
                return(false);
            }
            if (SAUtility.GetMesh(go) == null)
            {
                Debug.LogError("Object " + go + " in the list of objects to be combined does not have a mesh");
                return(false);
            }
        }

        if (mom.textureBakeResults.doMultiMaterial)
        {
            if (!validateSubmeshOverlap(mom))
            {//only warns currently
                return(false);
            }
        }

        List <GameObject> objs = objsToMesh;

        if (mom is SA3MeshCombine)
        {
            objs = mom.GetObjectsToCombine();
            //if (((SA3MeshCombine)mom).useObjsToMeshFromTexBaker && tb != null) objs = tb.GetObjectsToCombine();
            if (objs == null || objs.Count == 0)
            {
                Debug.LogError("No meshes to combine. Please assign some meshes to combine.");
                return(false);
            }
            if (mom is SA3MeshCombine && ((SA3MeshCombine)mom).meshCombiner.renderType == SARenderType.skinnedMeshRenderer)
            {
                if (!editorMethods.ValidateSkinnedMeshes(objs))
                {
                    return(false);
                }
            }
        }

        if (editorMethods != null)
        {
            editorMethods.CheckPrefabTypes(objToCombineType, objsToMesh);
        }
        return(true);
    }
Пример #9
0
    List <GameObject> GetFilteredList()
    {
        List <GameObject>  newMomObjs = new List <GameObject>();
        SA3MeshCombineRoot mom        = (SA3MeshCombineRoot)target;

        if (mom == null)
        {
            Debug.LogError("Must select a target MeshBaker to add objects to");
            return(newMomObjs);
        }
        GameObject dontAddMe = null;
        Renderer   r         = SAUtility.GetRenderer(mom.gameObject);

        if (r != null)
        { //make sure that this MeshBaker object is not in list
            dontAddMe = r.gameObject;
        }

        int numInSelection      = 0;
        int numStaticExcluded   = 0;
        int numEnabledExcluded  = 0;
        int numLightmapExcluded = 0;
        int numOBuvExcluded     = 0;

        GameObject[] gos = Selection.gameObjects;
        if (gos.Length == 0)
        {
            Debug.LogWarning("No objects selected in hierarchy view. Nothing added.");
        }

        for (int i = 0; i < gos.Length; i++)
        {
            GameObject go  = gos[i];
            Renderer[] mrs = go.GetComponentsInChildren <Renderer>();
            for (int j = 0; j < mrs.Length; j++)
            {
                if (mrs[j] is MeshRenderer || mrs[j] is SkinnedMeshRenderer)
                {
                    if (mrs[j].GetComponent <TextMesh>() != null)
                    {
                        continue; //don't add TextMeshes
                    }
                    numInSelection++;
                    if (!newMomObjs.Contains(mrs[j].gameObject))
                    {
                        bool addMe = true;
                        if (!mrs[j].gameObject.isStatic && onlyStaticObjects)
                        {
                            numStaticExcluded++;
                            addMe = false;
                        }

                        if (!mrs[j].enabled && onlyEnabledObjects)
                        {
                            numEnabledExcluded++;
                            addMe = false;
                        }

                        if (lightmapIndex != -2)
                        {
                            if (mrs[j].lightmapIndex != lightmapIndex)
                            {
                                numLightmapExcluded++;
                                addMe = false;
                            }
                        }

                        Mesh mm = SAUtility.GetMesh(mrs[j].gameObject);
                        if (mm != null)
                        {
                            Rect dummy = new Rect();
                            if (SAUtility.hasOutOfBoundsUVs(mm, ref dummy) && excludeMeshesWithOBuvs)
                            {
                                if (shaderMat != null)
                                {
                                    numOBuvExcluded++;
                                    addMe = false;
                                }
                            }
                            else
                            {
                                Debug.LogWarning("Object " + mrs[j].gameObject.name + " uses uvs that are outside the range (0,1)" +
                                                 "this object can only be combined with other objects that use the exact same set of source textures (one image in each atlas)" +
                                                 " unless fix out of bounds UVs is used");
                            }
                        }

                        if (shaderMat != null)
                        {
                            Material[] nMats      = mrs[j].sharedMaterials;
                            bool       usesShader = false;
                            foreach (Material nMat in nMats)
                            {
                                if (nMat != null && nMat.shader == shaderMat.shader)
                                {
                                    usesShader = true;
                                }
                            }
                            if (!usesShader)
                            {
                                addMe = false;
                            }
                        }

                        if (mat != null)
                        {
                            Material[] nMats   = mrs[j].sharedMaterials;
                            bool       usesMat = false;
                            foreach (Material nMat in nMats)
                            {
                                if (nMat == mat)
                                {
                                    usesMat = true;
                                }
                            }
                            if (!usesMat)
                            {
                                addMe = false;
                            }
                        }

                        if (addMe && mrs[j].gameObject != dontAddMe)
                        {
                            if (!newMomObjs.Contains(mrs[j].gameObject))
                            {
                                newMomObjs.Add(mrs[j].gameObject);
                            }
                        }
                    }
                }
            }
        }
        Debug.Log("Total objects in selection " + numInSelection);
        if (numStaticExcluded > 0)
        {
            Debug.Log(numStaticExcluded + " objects were excluded because they were not static");
        }
        if (numEnabledExcluded > 0)
        {
            Debug.Log(numEnabledExcluded + " objects were excluded because they were disabled");
        }
        if (numOBuvExcluded > 0)
        {
            Debug.Log(numOBuvExcluded + " objects were excluded because they had out of bounds uvs");
        }
        if (numLightmapExcluded > 0)
        {
            Debug.Log(numLightmapExcluded + " objects did not match lightmap filter.");
        }
        return(newMomObjs);
    }
Пример #10
0
    bool _ValidateResultMaterials()
    {
        HashSet <Material> allMatsOnObjs = new HashSet <Material>();

        for (int i = 0; i < objsToMesh.Count; i++)
        {
            if (objsToMesh[i] != null)
            {
                Material[] ms = SAUtility.GetGOMaterials(objsToMesh[i]);
                for (int j = 0; j < ms.Length; j++)
                {
                    if (ms[j] != null)
                    {
                        allMatsOnObjs.Add(ms[j]);
                    }
                }
            }
        }
        HashSet <Material> allMatsInMapping = new HashSet <Material>();

        for (int i = 0; i < resultMaterials.Length; i++)
        {
            SAMultiMaterial mm = resultMaterials[i];
            if (mm.combinedMaterial == null)
            {
                Debug.LogError("Combined Material is null please create and assign a result material.");
                return(false);
            }
            Shader targShader = mm.combinedMaterial.shader;
            for (int j = 0; j < mm.sourceMaterials.Count; j++)
            {
                if (mm.sourceMaterials[j] == null)
                {
                    Debug.LogError("There are null entries in the list of Source Materials");
                    return(false);
                }
                if (targShader != mm.sourceMaterials[j].shader)
                {
                    Debug.LogWarning("Source material " + mm.sourceMaterials[j] + " does not use shader " + targShader + " it may not have the required textures. If not empty textures will be generated.");
                }
                if (allMatsInMapping.Contains(mm.sourceMaterials[j]))
                {
                    Debug.LogError("A Material " + mm.sourceMaterials[j] + " appears more than once in the list of source materials in the source material to combined mapping. Each source material must be unique.");
                    return(false);
                }
                allMatsInMapping.Add(mm.sourceMaterials[j]);
            }
        }

        if (allMatsOnObjs.IsProperSubsetOf(allMatsInMapping))
        {
            allMatsInMapping.ExceptWith(allMatsOnObjs);
            Debug.LogWarning("There are materials in the mapping that are not used on your source objects: " + PrintSet(allMatsInMapping));
        }
        if (allMatsInMapping.IsProperSubsetOf(allMatsOnObjs))
        {
            allMatsOnObjs.ExceptWith(allMatsInMapping);
            Debug.LogError("There are materials on the objects to combine that are not in the mapping: " + PrintSet(allMatsOnObjs));
            return(false);
        }
        return(true);
    }
Пример #11
0
    SAAtlasesAndRects[] _CreateAtlases(ProgressUpdateDelegate progressInfo, bool saveAtlasesAsAssets = false, SA2EditorMethodsInterface editorMethods = null)
    {
        //validation
        if (saveAtlasesAsAssets && editorMethods == null)
        {
            Debug.LogError("Error in CreateAtlases If saveAtlasesAsAssets = true then editorMethods cannot be null.");
            return(null);
        }
        if (saveAtlasesAsAssets && !Application.isEditor)
        {
            Debug.LogError("Error in CreateAtlases If saveAtlasesAsAssets = true it must be called from the Unity Editor.");
            return(null);
        }
        if (!DoCombinedValidate(this, SAObjsToCombineTypes.dontCare, editorMethods))
        {
            return(null);
        }
        if (_doMultiMaterial && !_ValidateResultMaterials())
        {
            return(null);
        }
        else if (!_doMultiMaterial)
        {
            if (_resultMaterial == null)
            {
                Debug.LogError("Combined Material is null please create and assign a result material.");
                return(null);
            }
            Shader targShader = _resultMaterial.shader;
            for (int i = 0; i < objsToMesh.Count; i++)
            {
                Material[] ms = SAUtility.GetGOMaterials(objsToMesh[i]);
                for (int j = 0; j < ms.Length; j++)
                {
                    Material m = ms[j];

                    if (m == null)
                    {
                        Debug.LogError("Game object " + objsToMesh[i] + " has a null material. Can't build atlases");
                        return(null);
                    }
                    else if (m != null && m.shader != targShader)
                    {
                        Debug.LogWarning("Game object " + objsToMesh[i] + " does not use shader " + targShader + " it may not have the required textures. If not 2x2 clear textures will be generated.");
                    }
                }
            }
        }

        //for (int i = 0; i < objsToMesh.Count; i++)
        //{
        //    Material[] ms = SAUtility.GetGOMaterials(objsToMesh[i]);
        //    for (int j = 0; j < ms.Length; j++)
        //    {
        //        Material m = ms[j];

        //        if (m == null)
        //        {
        //            Debug.LogError("Game object " + objsToMesh[i] + " has a null material. Can't build atlases");
        //            return null;
        //        }
        //    }
        //}

        SA3TextureCombiner combiner = new SA3TextureCombiner();

        combiner.LOG_LEVEL             = LOG_LEVEL;
        combiner.atlasPadding          = _atlasPadding;
        combiner.customShaderPropNames = _customShaderPropNames;
        combiner.fixOutOfBoundsUVs     = _fixOutOfBoundsUVs;
        combiner.maxTilingBakeSize     = _maxTilingBakeSize;
        combiner.packingAlgorithm      = _packingAlgorithm;
        combiner.combineTexturePackerForcePowerOfTwo = _combineTexturePackerForcePowerOfTwo;
        combiner.resizePowerOfTwoTextures            = _resizePowerOfTwoTextures;
        combiner.saveAtlasesAsAssets = saveAtlasesAsAssets;

        // if editor analyse meshes and suggest treatment
        if (!Application.isPlaying)
        {
            Material[] rms;
            if (_doMultiMaterial)
            {
                rms = new Material[resultMaterials.Length];
                for (int i = 0; i < rms.Length; i++)
                {
                    rms[i] = resultMaterials[i].combinedMaterial;
                }
            }
            else
            {
                rms    = new Material[1];
                rms[0] = _resultMaterial;
            }
            combiner.SuggestTreatment(objsToMesh, rms, combiner.customShaderPropNames);
        }

        //initialize structure to store results
        int numResults = 1;

        if (_doMultiMaterial)
        {
            numResults = resultMaterials.Length;
        }
        SAAtlasesAndRects[] resultAtlasesAndRects = new SAAtlasesAndRects[numResults];
        for (int i = 0; i < resultAtlasesAndRects.Length; i++)
        {
            resultAtlasesAndRects[i] = new SAAtlasesAndRects();
        }

        //Do the material combining.
        for (int i = 0; i < resultAtlasesAndRects.Length; i++)
        {
            Material        resMatToPass = null;
            List <Material> sourceMats   = null;
            if (_doMultiMaterial)
            {
                sourceMats   = resultMaterials[i].sourceMaterials;
                resMatToPass = resultMaterials[i].combinedMaterial;
            }
            else
            {
                resMatToPass = _resultMaterial;
            }
            Debug.Log("Creating atlases for result material " + resMatToPass);
            if (!combiner.CombineTexturesIntoAtlases(progressInfo, resultAtlasesAndRects[i], resMatToPass, objsToMesh, sourceMats, editorMethods))
            {
                return(null);
            }
        }

        //Save the results
        textureBakeResults.combinedMaterialInfo = resultAtlasesAndRects;
        textureBakeResults.doMultiMaterial      = _doMultiMaterial;
        textureBakeResults.resultMaterial       = _resultMaterial;
        textureBakeResults.resultMaterials      = resultMaterials;
        textureBakeResults.fixOutOfBoundsUVs    = combiner.fixOutOfBoundsUVs;
        unpackMat2RectMap(textureBakeResults);

        //set the texture bake resultAtlasesAndRects on the Mesh Baker component if it exists
        SA3MeshCombineCommon[] mb = GetComponentsInChildren <SA3MeshCombineCommon>();
        for (int i = 0; i < mb.Length; i++)
        {
            mb[i].textureBakeResults = textureBakeResults;
        }

        if (LOG_LEVEL >= SA2LogLevel.info)
        {
            Debug.Log("Created Atlases");
        }
        return(resultAtlasesAndRects);
    }
Пример #12
0
    void _distributeAmongBakers(GameObject[] gos, int[] deleteGOinstanceIDs)
    {
        if (gos == null)
        {
            gos = empty;
        }
        if (deleteGOinstanceIDs == null)
        {
            deleteGOinstanceIDs = emptyIDs;
        }

        if (resultSceneObject == null)
        {
            resultSceneObject = new GameObject("SAMMesh-" + 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("SA2MultiMeshCombiner.AddDeleteGameObjects1");

        //Profile.Start//Profile("SA2MultiMeshCombiner.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 >= SA2LogLevel.debug)
                {
                    SA2Log.LogDebug("SA2MultiMeshCombiner.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 = SAUtility.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 >= SA2LogLevel.debug)
                    {
                        SA2Log.LogDebug("SA2MultiMeshCombiner.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 >= SA2LogLevel.debug)
                {
                    SA2Log.LogDebug("SA2MultiMeshCombiner.Created new combinedMesh");
                }
            }
            cm.gosToAdd.Add(go);
            cm.numVertsInListToAdd += numVerts;
            //			obj2MeshCombinerMap.Add(go,cm);
        }
    }