Search Unity

How do I duplicate a mesh asset?

Discussion in 'Scripting' started by Jessy, Nov 27, 2009.

  1. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I have a Blender file. In it, lies a mesh. I want to take this mesh, and manipulate it. Doing so directly doesn't work. How do I get a copy, then? If I use the == operator on a Mesh variable, I get a pointer to the data, instead of a copy.
     
  2. cannon

    cannon

    Joined:
    Jun 5, 2009
    Posts:
    751
    You actually have to copy the vertices and triangles over and not the mesh, as the mesh counts as an asset and all you get are references when you pass it around.

    What I did was create a function to manually copy over the following:

    Vertices
    Triangles
    UV, uv2
    normal
    tangent
    colors

    then save it to a new asset using AssetDatabase.
     
    kareemhammad and ArminHarbick like this.
  3. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    That sounds terrible. I'm trying out using Object.Instantiate, which seems to work, aside from the undesired suffix (Clone).

    Nevermind. Not having any success there, either, though that did fix some sort of wacky naming problem I was having.

    I hate this.
     
  4. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Okay, now I'm doing that. Why is this vertex color change not having any effect?

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. class VertexRGB2A {
    5.  
    6.  
    7. [ MenuItem("Assets/Copy RGB Vertex Colors to Alpha") ]
    8. static void VertexRGB2AMenuItem ()
    9. {  
    10.     Mesh rgbMesh = Selection.activeObject as Mesh;
    11.     Mesh rgbaMesh = new Mesh();
    12.     rgbaMesh.vertices = rgbMesh.vertices;
    13.     rgbaMesh.colors = rgbMesh.colors;
    14.     rgbaMesh.triangles = rgbMesh.triangles;
    15.    
    16.     for (int i = 0; i < rgbaMesh.colors.Length; ++i)
    17.         rgbaMesh.colors[i].a = rgbaMesh.colors[i].grayscale;
    18.        
    19.     // Get the path of the enclosing folder.
    20.     string path = AssetDatabase.GetAssetPath(Selection.activeObject);
    21.     path = path.Substring(0, path.LastIndexOf('/') );
    22.    
    23.     AssetDatabase.CreateAsset(rgbaMesh, path + '/' + rgbMesh.name + "  (VertRGB->A)" + ".asset");
    24. }
    25.  
    26. [ MenuItem("Assets/Copy RGB Vertex Colors to Alpha", true) ]
    27. static bool ValidateSyncFBXToHierarchy ()
    28. {      
    29.     return Selection.activeObject.GetType() == typeof(Mesh);
    30. }
    31.    
    32.  
    33. }
     
  5. cannon

    cannon

    Joined:
    Jun 5, 2009
    Posts:
    751
    Here's a quick script I just hacked up, since I thought it would be useful in a couple of places.

    Code (csharp):
    1.  
    2.     [MenuItem("Assets/CopyMesh")]
    3.     static void CopyMesh()
    4.     {
    5.         Mesh mesh = Selection.activeObject as Mesh;
    6.         Mesh newmesh = new Mesh();
    7.         newmesh.vertices = mesh.vertices;
    8.         newmesh.triangles = mesh.triangles;
    9.         newmesh.uv = mesh.uv;
    10.         newmesh.normals = mesh.normals;
    11.         newmesh.colors = mesh.colors;
    12.         newmesh.tangents = mesh.tangents;
    13.         AssetDatabase.CreateAsset(newmesh, AssetDatabase.GetAssetPath(mesh) + " copy.asset");
    14.     }
    15.  
    You can also do this at runtime instead of in-editor.
     
    EvSpentys, tokar_dev, bugbeeb and 7 others like this.
  6. cannon

    cannon

    Joined:
    Jun 5, 2009
    Posts:
    751
    Oh, simul-post...

    Anyway, you have to reassign your RGB color array back to your mesh, otherwise you're just editing a copy:

    Color[] colors = rgbaMesh.colors;

    // Edit colors here
    for( i=0 to <blah>)
    colors.a = <random alpha>
    // <SNIP>

    // now reassign it back
    rgbaMesh.colors = colors;
     
  7. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Hooray for posting in the same minute!

    I don't like that kind of stuff!


    I don't understand this, but I would like to. Regardless, this works. Thanks a bunch!

    Code (csharp):
    1. Mesh rgbMesh = Selection.activeObject as Mesh; 
    2. Color[] colors = rgbMesh.colors;
    3. for (int i = 0; i < rgbMesh.colors.Length; ++i)
    4.     colors[i].a = colors[i].grayscale;
    5.  
    6. Mesh rgbaMesh = new Mesh();
    7. rgbaMesh.vertices = rgbMesh.vertices;
    8. rgbaMesh.triangles = rgbMesh.triangles;
    9. rgbaMesh.colors = colors;
     
  8. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Hey there,

    I'm trying to do the same, but for a whole GameObject.

    First, I Object.Instantiate() an FBX asset, and then I try to create a new asset from this GameObject.
    Lastly, I use SaveAssets().

    But the new asset loses its components each time I restart Unity ...

    Does this mean I have to recreate all the components from the old to the new asset ?

    Or is the instanciated GameObject reference is sufficient to include all its components with CreateAsset ?


    Thank you in advance.


    P.S : There is a lack of examples and docs about how to handle prefabs and assets without messing everything up.
     
  9. cannon

    cannon

    Joined:
    Jun 5, 2009
    Posts:
    751
    I got curious enough to see if it would work.

    Here's what I did:

    1. Instantiate an FBX through the editor.
    2. Added a component, changed some of the default settings.
    3. Used an editor script to save it to an asset file.

    Observations:

    If the object is active, Unity complains and sets it to inactive, and then saves it.
    If I set the object and script to inactive, there are no errors, and the prefab seems to be created and retains the settings of the components.

    However; deleting the created prefab always crashes Unity on my machine in either case. I'm not quite ready to risk a full reimport on my current project so I'm stopping the experiments here for now 8).

    After a little bit more dangerous experimenting, (because I'm too lazy to open up a blank project), CreateAsset using a GameObject does not create a Prefab, but instead tries to create an invalid asset file that has a reference to the GameObject in the scene hierarchy.

    Personally, if you needed that facility, unless anyone has a better suggestion I would highly recommend just using reflection to store the game object's components and settings and just recreate them. :?
     
  10. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Well, I'm running into the exact same crashes and weird things than you decribe. It's turning me mad :p


    I really don't understand, it's like every single bit of attribution is not a new instance but just a reference... Here is where I'm at actually :

    0) I instantiate the original asset into a GameObject, and it will be the object I'll work with in further steps
    1) I create a new GameObject
    2) I create a new Animation component
    3) I build its curves by copying the old asset's ones, like NewCurve = OldAssetCurve
    4) I export this new GameObject into a new asset with CreateAsset(). Here, everyting is fine, it contain my clips, and my Transform.
    5) SetDirty() + SaveAssets() + Save Project
    6) restart Unity
    7) Then I receive that damn message "Component could not be loaded. Cleaning up!" And my New Asset is empty.

    Please don't tell me that in order to manipulate an FBX file, we have to recreate manually every single bit of it into a new asset ? :cry:

    I tried the CopyAsset approach in point 0), but the created asset is unreadable, not containing any component at all.

    I also tried the approach of creating every component with new, and then EditorUtility.CopySerialized the old ones, but still it wouldn't save to disk.

    Finally I even tried EditorUtility.CloneComponent(), but GameObject.animation is read only, so it was useless.

    How could it be so complicated to just manipulate a Unity resource file ?

    Anyway, thanks for the tips. What do you mean by using Reflection ? (sorry I'm not fully used to C# :roll: )
     
  11. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I don't have the patience to go sorting through my logged bugs right now, but I have encountered several related problems, over the past couple of months, and tried to log a bug about each one. As I see it, Unity really doesn't seem to want me to manipulate any assets in the Editor, and prefers for me to work in Start(). Which I don't want to do, because as a gamer, I am impatient with any sort of loading, and don't want that in my own products. 2.5 brought improvements to this area with improved ability to take control of the Editor, but the foundation needs to become more sound for procedural creation and manipulation of content in the Editor.
     
  12. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Seconded, it's simply impossible to do some generation stuff at runtime as it would take ages to launch the game.

    Now after an ultimate experiment, I feel something is seriously wrong :

    I recreated a brand new Animation(), and then recreated each of its curves by the use of new Keyframe(). each of these keyframes were set to the values of another asset's animation keyframes.

    In short, I just can't go deeper to the data source.

    Then I saved the Animation into a new .ANIM extension asset, to ensure that nothing else would interfere.

    As usual, everything looks ok after operation.
    Restarting Unity --> "Component could not be loaded. Cleaning up!" And my new .ANIM asset is empty.

    Depressing.
     
  13. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Ok, after ten centuries of trials and errors, I finally found why the data wouldn't persist after restarting Unity.

    We have to do an AssetDatabase.AddObjectToAsset() for each animationClip referenced in the Animation component of the new asset.

    Docs should have specified this key step ...

    Anyway, this makes me do the relative with what the Editor shows when we select a component member of a FBX file : a visual white line to the child element in the inspector. So I guess we have to do AssetDatabase.AddObjectToAsset() for Meshes too. Simple reflection of vertices, etc wouldn't work.


    edit : haha now I'm screwed, as we can't tell AssetDatabase.AddObjectToAsset() to write at the root, every components that are in the root of the new asset are lost at restart ...
    Well, let's go ahead of ten more centuries of trials and errors. :?


    edit 2 : ok I found how to tell AssetDatabase.AddObjectToAsset() to write at where we want : AddObjectToAsset (objectToAdd : Object, assetObject : Object) instead of AddObjectToAsset (objectToAdd : Object, assetPath : string).
    Everything is persistent now.


    Oooookaaaaaaaay, now I can work. Fine.
     
  14. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I've never used AssetDatabase.AddObjectToAsset before now, but I'm glad of that. All it does is turn Unity into a crashing machine. Very disappointing, but completely in-line with what I'm used to, from the editor classes. :(
     
  15. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Yep, it seems to be fragile ...

    Anyway, adding new animationClip now just works fine.

    I'll soon publish a clean script where we can do stuff in a FBX (like procedural stuff) and then copy it to a new safe asset.
     
  16. cannon

    cannon

    Joined:
    Jun 5, 2009
    Posts:
    751
  17. Delph

    Delph

    Joined:
    Aug 1, 2012
    Posts:
    2
    Old thread I know, but I came across this when looking to see if Mesh had a copy constructor, thought I'd share this method to copy across the properties via reflection.

    Edit: Updated to extension method (see http://msdn.microsoft.com/en-us//library/bb383977.aspx).

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. namespace UnityExtensions
    4. {
    5.     public static class MeshExtensions
    6.     {
    7.         public static Mesh Copy(this Mesh mesh)
    8.         {
    9.             var copy = new Mesh();
    10.             foreach(var property in typeof(Mesh).GetProperties())
    11.             {
    12.                 if(property.GetSetMethod() != null  property.GetGetMethod() != null)
    13.                 {
    14.                     property.SetValue(copy, property.GetValue(mesh, null), null);
    15.                 }
    16.             }
    17.             return copy;
    18.         }
    19.     }
    20. }
     
    Last edited: Apr 4, 2014
  18. Sycoforge

    Sycoforge

    Joined:
    Oct 6, 2013
    Posts:
    751
    Calling setter/getter (or any other method) by reflection is generally a bad idea, at least for the code's performance. You would better write the assignments line by line and make direct calls. I guess your code will run about ~400% faster!
     
  19. Plapi100

    Plapi100

    Joined:
    Mar 15, 2014
    Posts:
    3
    For me, the best and simple way to clone a mesh is to use Instantiate. Like this:

    Code (CSharp):
    1. if (Selection.activeGameObject != null && Selection.activeGameObject.TryGetComponent(out MeshFilter meshFilter)) {
    2.      GameObject obj = new GameObject("obj");
    3.      obj.AddComponent<MeshRenderer>().sharedMaterials = meshFilter.GetComponent<MeshRenderer>().sharedMaterials;
    4.      obj.AddComponent<MeshFilter>().sharedMesh = Instantiate(meshFilter.sharedMesh);
    5.  }
     
    FV-HIROSE likes this.
  20. Steven-1

    Steven-1

    Joined:
    Sep 11, 2010
    Posts:
    471
    That's not a copy, that's the same mesh it references.

    The easiest way to copy a mesh is simply to call MeshFilter.mesh, but for some stupid reason Unity logs an error when you do that in an editor script.

    I've been trying to make an exact copy of a mesh by manually copying everything, and it's easy enough, unless the mesh has blendshapes, I can't get these to copy correctly
     
  21. jaszunio15

    jaszunio15

    Joined:
    Jan 9, 2016
    Posts:
    4
    For future me and others, I found a simple script that copies the full mesh data, including submeshes, without dealing of manually copying the mesh data itself. I didn't check if it works with bindposes etc. but it's a nice shortcut anyway.

    Code (CSharp):
    1. public static Mesh CloneMesh(Mesh source)
    2. {
    3.     Mesh mesh = new Mesh();
    4.  
    5.     // Using unity combine mesh to combine a single mesh into another one, making a full copy.
    6.     CombineInstance[] instancesToCombine = new CombineInstance[source.subMeshCount];
    7.     for (int i = 0; i < source.subMeshCount; i++)
    8.     {
    9.         instancesToCombine[i] = new CombineInstance()
    10.         {
    11.             mesh = source,
    12.             subMeshIndex = i,
    13.             lightmapScaleOffset = new Vector4(1, 1, 0, 0),
    14.             realtimeLightmapScaleOffset = new Vector4(1, 1, 0, 0),
    15.             transform = Matrix4x4.identity
    16.         };
    17.     }
    18.     mesh.CombineMeshes(instancesToCombine, false, false, false);
    19.  
    20.     mesh.name = source.name;
    21.     return mesh;
    22. }
     
    melos_han_tani and AlejMC like this.
  22. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,443
    I saw this thread started in 2009 and was just about to whine about necro-posting, but this is a pretty neat little future-proofing solution. Theoretically, if Mesh grows new features in the future, then the CombineMeshes API will grow to cover them automatically, instead of you needing to revisit your code here.

    https://docs.unity3d.com/ScriptReference/CombineInstance.html
     
    AlejMC likes this.
  23. melos_han_tani

    melos_han_tani

    Joined:
    Jan 11, 2018
    Posts:
    79
    Nice trick. I want to point out a little gotcha I found for a weird edge case: if you ever are saving Mesh *ASSETS* to disk and want to modify the mesh data in place without breaking prefabs that reference that mesh asset, you can't use this method - you have to load the mesh asset, then individually replace its vertices, colors, etc fields.

    In my case I have prefabs that reference certain mesh assets in order to handle vertex color palette swaps.