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.
///
///
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); }
///
/// 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.