• Blog
  • Work
  • Resources
    • Rigs
  • Resume
  • Contact
I do technical things...

Saving and Loading Mesh Data in 3ds Max .Net SDK

7/19/2014

0 Comments

 
I've been making some progress with 3ds Max and its .Net api. As a learning exerscise I thought I'd try and write some code that saves and loads mesh data back from XML using serialization.

Here's the 'Populate' method (part of the save process) that grabs the data needed from a given mesh (actually I have the ability to save multiple meshes per file). Later in the code the class this code lives in is serialized to XML.
   ///  
/// Function that populates necassary mesh data to this class, from scene data. Called before serialization. ///
  ///  
 The IINode, containing the mesh, to be serialized public void Populate(IINode node) {         // make sure the node is valid         if (node == null)                 return;          // get the object ref as a tri object         ITriObject triObj = MxsSharp.MeshOps.GetITriObject(node);         // bail if we failed to retrieve         if (triObj == null)                 return;                  // get the mesh/IMesh from the tri object         IMesh mesh = triObj.Mesh;         // bail if we failed to retrieve         if (mesh == null)                 return;          // initialize the base data variables          this.Verts = new List();         this.Faces = new List();         this.Normals = new List();         this.Name = node.Name;          // serialize the transform         this.Transform = new Matrix();         this.Transform.Populate(node);          // create serialized data for mesh vertex         int count = 0;         foreach (IPoint3 vPos in mesh.Verts)         {                             // Create custom Vertex class that can be serialized                 Vertex vrt = new Vertex();                 // Populate the vertex class with data - cannot do this on itilialize due to serialization                 vrt.Populate(vPos);                 // Add to this class' Verts list                 this.Verts.Add(vrt);                  count++;         }          // create serialized data for mesh faces         foreach (IFace iface in mesh.Faces)         {                 // Create custom Face class that can be serialized                 Face face = new Face();                 // Populate the Face class with data - cannot do this on itilialize due to serialization                 face.Populate(iface);                 // Add to this class' Faces list                 this.Faces.Add(face);         }          // Serialize any map channels the mesh may have.. uvs and such         if (mesh.NumMaps > 0)         {                 this.Maps = new List();                 for (int mi = 0; mi < mesh.NumMaps; mi++)                 {                         // Create custom MeshMap class that can be serialized                         MeshMap map = new MeshMap();                         // Populate the MeshMap class with data - cannot do this on itilialize due to serialization                         map.Populate(mesh, mi);                         // Add to this class' Maps list                         this.Maps.Add(map);                 }         }          // build / check normals before we serialize them. otherwise we may not have any normals.         mesh.BuildNormals();         mesh.BuildRenderNormals();                  // create serialized data for mesh normals         for (int i = 0; i < mesh.NormalCount; i++)         {                     // Create custom Normal class that can be serialized                             IPoint3 inml = mesh.GetNormal(i);                 Normal normal = new Normal();                 // Populate the Normal class with data - cannot do this on itilialize due to serialization                 normal.Populate(inml);                 // Add to this class' Normals list                 this.Normals.Add(normal);         }          mesh.DisplayNormals(1, 3.0f);   }   
Below is the code that is called after my class is de-serialized and I want the loaded data re-created in the scene. It accesses data outside of this method, that lives in its containing class, but it should be pretty self explanatory.
   ///  
/// This method is used to build a mesh with the data contained in the class. /// It will ususally will be called when the object is rebuilt during deserialization ///
   ///  private ITriObject BuildMesh(bool build_normals = true, bool build_maps = true) {         // got guidance from here : http://eliang.blogspot.com/2011/10/creating-triangle-mesh-with-3ds-max-sdk.html          // Make a new TriObject class         ITriObject triobj = Kernel.Global.TriObject.Create();         // bail if null         if (triobj == null)                 return null;          // grab the IMesh instance in the TriObject         IMesh mesh = triobj.Mesh;         // bail if null         if (triobj == null)                 return null;          int numVertices =   this.Verts.Count;         int numTriangles =  this.Faces.Count;         int numNormals =    this.Normals.Count;         int numMaps =       this.Maps.Count;          // set vertex array size in the mesh         mesh.SetNumVerts(numVertices, false, true);         // set triangle array size in the mesh         mesh.SetNumFaces(numTriangles, false, true);          // Loop and set vertex positions         for (int i = 0; i < numVertices; i++)                 mesh.SetVert(i, this.Verts[i].Position.ToIPoint3());          // Loop and set the basic face data         for (int i = 0, j = 0; i < numTriangles; i++, j += 3)         {                 int v0 = (int)this.Faces[i].Verts[0];                 int v1 = (int)this.Faces[i].Verts[1];                 int v2 = (int)this.Faces[i].Verts[2];                  // vertex positions                 IFace face = mesh.Faces[i];                 face.MatID = 1;                 face.SetEdgeVisFlags(this.Faces[i].EdgeVis[0], this.Faces[i].EdgeVis[1], this.Faces[i].EdgeVis[2]);                 face.SetVerts(v0, v1, v2);                  // set the fac smoothing group if there was one set.                 if (this.Faces[i].SmoothingGroup != -1)                 {                         face.SmGroup = (uint)this.Faces[i].SmoothingGroup;                 }          }          // build uv information using the maps of the mesh         if (build_maps == true)         {                 // initialize the map array to the needed size                 mesh.SetNumMaps(numMaps, false);                  // start at 1 as zero is Vert color.. i think.                 for (int m = 1; m < numMaps; m++)                 {                         // enable map support                         mesh.SetMapSupport(m, true);  // enable map channel                          // get the current IMeshMap iteration                         IMeshMap map = mesh.Maps[m];                          // set num of verts to the vert count of the map List<>                         int numMapVerts = this.Maps[m].TVerts.Count;                         mesh.SetNumMapVerts(m, numMapVerts, false);                          for (int i = 0; i < numMapVerts; i++)                         {                                 mesh.SetMapVert(m, i, (this.Maps[m].TVerts[i]).ToIPoint3());                         }                           // set num of verts to the face count of the map List<>                         int numMapFaces = this.Maps[m].TFaces.Count;                         mesh.SetNumMapFaces(m, numMapFaces, false, 0);                          // grab the now intitialzed list of faces                         IList mapFaces = mesh.MapFaces(m);                          // loop and associate vertecies by the stored indecies.                         for (int i = 0; i < numMapFaces; i++)                         {                                 ITVFace tFace = mapFaces[i];                                 int v0 = (int)this.Maps[m].TFaces[i].Verts[0];                                 int v1 = (int)this.Maps[m].TFaces[i].Verts[1];                                 int v2 = (int)this.Maps[m].TFaces[i].Verts[2];                                  tFace.SetTVerts(v0, v1, v2);                         }                  }         }          // build normal information for each vert and face association         if (build_normals == true)         {                 // call this to initialize the normal arrays                 mesh.BuildNormals();                  // tell the mesh we will be specifying normals                 mesh.SpecifyNormals();                  // class to aid normal updating                 IMeshNormalSpec normalSpec = mesh.SpecifiedNormals;                                  // clear any pre calculated normals                 normalSpec.ClearNormals();                  // set the normal vertex array size                 normalSpec.SetNumNormals(numVertices);                  // loop and set vertex normals                 for (int i = 0; i < numVertices; i++)                 {                         IPoint3 ip3 = (this.Normals[i].Direction.ToIPoint3()).Normalize;                         normalSpec.NormalArray.Set(ip3.X, ip3.Y, ip3.Z);                         normalSpec.SetNormalExplicit(i, false);                 }                  // set normal face array size                 normalSpec.SetNumFaces(numTriangles);                 for (int i = 0, j = 0; i < numTriangles; i++, j += 3)                 {                         int v0 = (int)this.Faces[i].Verts[0];                         int v1 = (int)this.Faces[i].Verts[1];                         int v2 = (int)this.Faces[i].Verts[2];                          // vertex positions                         IFace face = mesh.Faces[i];                          // vertex normals                         IMeshNormalFace normalFace = normalSpec.Face(i);                          normalSpec.SetNormal(i, v0, (this.Normals[v0].Direction.ToIPoint3()).Normalize);                         normalSpec.SetNormal(i, v1, (this.Normals[v1].Direction.ToIPoint3()).Normalize);                         normalSpec.SetNormal(i, v2, (this.Normals[v2].Direction.ToIPoint3()).Normalize);                          normalFace.SpecifyAll(true);                         //normalFace.SetNormalID(0, v0);                         //normalFace.SetNormalID(1, v1);                         //normalFace.SetNormalID(2, v2);                         normalFace.SetSpecified(0, true);                         normalFace.SetSpecified(1, true);                         normalFace.SetSpecified(2, true);                  }         }          // tell the mesh to rebuild its internal cache's... like a recompute.         mesh.InvalidateGeomCache();         mesh.InvalidateTopologyCache();                  return triobj; }  ///  
/// Is called after the class is deserialized from xml. Takes the data that was deserialized and /// creates/sets max scene data as needed. /// Called from the MeshFile.DoDeserialize() ///
   public void DoDeserialize() {         if (this.Verts == null)                 return;         if (this.Faces == null)                 return;          ITriObject triObj = this.BuildMesh();          // if the tri obj creation clang'd out         if (triObj == null)         {                 Kernel.Print("Tri Object creation failed!");                 return;         }         string tmp_name = this.Name;         Kernel.Interface.MakeNameUnique(ref tmp_name);         IINode node = Kernel.Interface.CreateObjectNode(triObj, tmp_name);          node.SetNodeTM(0, this.Transform.ToIMatrix3()); }   
The full code currently saves and re-builds mesh vertices, faces, normals and uv maps to and from xml, and is nice and fast too. I was pretty suprised ho quickly I could cobble this together also, all in all it took around a weekend. The code needs some cleanup and could use some more error checking and handling, but the guts of the it is there.

When starting out, I scoured the net for some help on this, and found a few nuggets on cgtalk and other sites. However this is the post that helped me the most. So many thanks to Chang-Hung Liang for posting that! As a result I thought I'd post my findings also... to help the next person.
0 Comments

    Andy T.

    I do Tech things for Artist.

    Archives

    July 2014
    February 2014
    January 2014
    April 2013
    August 2012
    March 2012
    February 2012
    December 2011
    July 2010

    Categories

    All
    3ds Max C#
    3ds Max .net Api
    Animation
    Api
    Face Rig
    Maxscript
    Maya
    Mel
    Python
    Python Api
    Rigging
    Scripting
    Ui
    Work

    Links To Cool Folks

    Mic Marvin
    Alex Utting
    Rick Vicens
    Gary Talbot
    Richie Prado
    Felipe Nogueira

    RSS Feed