Working with Meshes

3DS Max Plug-In SDK

Working with Meshes

See Also: Class Mesh, Class TriObject, Class Face, Class TVFace, Class MNMesh.

Overview

This topic presents information on working with meshes. The main classes dealing with meshes are discussed as well as several support classes. The way texture mapping works with meshes is presented. How materials are assigned to meshes is also reviewed.

Overview of the Principal Classes

This section presents an overview of the principal classes used when working with meshes.

Class Mesh

This is the main class for working with mesh objects. It has data members that point to the vertices, faces, texture vertices and texture faces. Methods are provided to access all properties of the mesh and to render, snap to, and hit test the mesh. There are also methods to optimize, apply mapping coordinates, and perform boolean operations on the mesh.

Class TriObject

All procedural objects must be able to convert themselves to TriObjects. This is the class that actually flows down the geometry pipeline. This class contains an instance of the Mesh class.

Class Face

This is the class used to hold a single triangular face of the mesh object. It maintains three indices into the vertex array of the mesh. Methods are provided for setting materials, smoothing groups, edge visibility and hidden status.

Class TVFace

This is the class used to hold a texture face. It contains an array of three indices into the texture vertex array of the mesh.

Support Classes

This section lists several classes that are handy when dealing with mesh objects. There are a set of classes for working with parts of the mesh such as its face structure, element structure, and cluster structure. For details see: Class AdjEdgeList, Class AdjFaceList, Class FaceElementList, Class FaceClusterList.

New for release 2.0 and later, the MNMesh class is provided for temporary use by plug-ins, to help with complex topology-based modifications to Meshes. It has capabilities, such as the ability to recognize faces with more than 3 sides, that are useful in certain applications. See Class MNMesh for details.

Extracting the Mesh from a Node

Developers who want to get the TriObject representation of a geometric object from its node can use the source code shown along with the method INode::EvalWorldState(). See INode::EvalWorldState().

Building Meshes Suitable for MAX Modifiers

Developers should follow a few simple but important rules when building meshes to function ideally with modifiers in the Geometry Pipeline. The six basic rules are:

1) Referencing each edge at most once in each direction.

2) Avoiding self-intersection of faces.

3) Avoiding creating faces with vertices located at the same place.

4) Not 'bridging' separate mesh components with a single vertex.

5) Breaking the mesh into sensible elements.

6) Whenever convenient, closing the mesh.

See the Advanced Topics section Style Guidelines for Creating Pipeline-Friendly Meshes for details on these rules.

Material Assignment

Materials are assigned in 3ds max at the node level. There is one material assigned per node. The material itself defines the meaning of how it acts upon a mesh. For example, the 3ds max Multi/Sub-Object material uses the material ID assigned to each mesh face as indices into its list of sub-materials. In this way, the user may assign several materials to a single mesh object. The Mesh class provides methods to get and set the material ID assigned to a face. For more details see Working with Materials and Textures.

Texture Vertices and Texture Faces

The mesh class keeps a pointer to a list of texture vertices. If mapping coordinates are assigned to the mesh these extra vertices are allocated. These vertices are completely independent of the regular vertices in the mesh. In addition to the texture vertices there are also texture faces. There needs to be one texture face for every regular face in the mesh. Each texture face has three indices into the texture vertex array. This allows every face of the mesh to have its own mapping.

Mapping Channels in Release 3.0 and Later

In the Materials Editor a user can choose which of the channels a texture map is applied to. In the Coordinates rollout of the user interface, in the Texture Mapping dropdown the user may choose "Explicit Map Channel". In this case the Map Channel spinner is enabled and the user may choose a number between 1 and 99. This number is effectively an index into the Mesh class mapping methods. The number 1 corresponds to the pre-R3 Mesh class TVerts array. The numbers 2 through 99 correspond to the R3 and later channels. If the user chooses "Vertex Color Channel" from the dropdown then the Map Channel spinner is disabled and the vertex color channel is used. This corresponds to an index of 0 in the Mesh class mapping methods.

In release 2.0 there were just two possible mapping channels. The original release 1.x mapping channel and the color per vertex channel. These two still exist. However the map channel index numbers have changed. Henceforth at the object level, map channel 0 refers to what was map channel 1, the vertex color channel, while map channel 1 refers to the original map channel. The reason for this change is that vertex colors are treated differently than map channel vertices for some topological operations, and it's better to separate this map channel from all the other map channels.

Note the following details on adding map channels:

1) To support a map channel in a mesh, call Mesh::setMapSupport(channel, TRUE). This works for the vertex colors (channel 0) or the original TVFaces (channel 1) as well as the new channels.

2) The method Mesh::setMapSupport allocates the map faces, since there are always Mesh::numFaces of these, but it does not allocate the map verts, since the number of these can change from map to map.

3) Use Mesh::setNumMapVerts(int mp) to set the number of map verts.

4) Calls to Mesh::setNumFaces(int nf, BOOL keep) also set the number of map faces in all supported maps.

5) Keep in mind that the Mesh::mapVerts(int mp) and Mesh::mapFaces(int mp) methods return pointers that may be invalid later if the number of verts or faces is reallocated. For example, the following won't work:

UVVert *mv = mesh.mapVerts(43); // map channel 43

mesh.setNumMapVerts(43, 98);

for (i=0; i<98; i++) mv[i] = UVVert(0,0,0);

For reference information on the new mapping related methods in 3.0 see Mesh Mapping Related Methods.

Color Per Vertex Information

In 3ds max 2.0 and later color per vertex information is stored with a Mesh. This allows the user to assign colors to vertices. This ability is primarily for game developers and developers of radiosity renderers. This information can be used in conjunction with the Vertex Color map (\MAXSDK\SAMPLES\MATERIALS\VERTCOL.CPP) or the Color Per Vertex utility and modifier (\MAXSDK\SAMPLES\UTILITIES\APPLYVC\APPLYVC.CPP and AVCMOD.CPP).

Three public data members and several methods in the Mesh class provide access to this data.

int numCVerts;

The number of color vertices.

VertColor *vertCol;

Array of color vertices. Note: typedef Point3 VertColor;

TVFace *vcFace;

Array of vertex color faces.

Color Per Vertex Enhancements in Release 4.0 and Later

In 3ds max 4.0 and later it is possible to have the source data for vertex colors come from other than the internal vertex color array (vertCol). The data can come from an external array or one of the map channels. When 3ds max is rendering the color values come from the vertColArray variable.

VertColor *vertColArray;

This array defaults to the internal array (vertCol) but can be set to an external array or a mapping channel.

If an external array is used the following data member is a pointer to it (this defaults to NULL):

VertColor *curVCArray;

If a mapping channel is used the following data member indicates which one (this defaults to 0).

int curVCChan;

When 3ds max is rendering the vertex lookup comes from the vcFaceData variable. This defaults to the vcFace data but if a mapping channel is used for color lookup 3ds max uses its TVFace structure.

TVFace *vcFaceData;

The methods associates with this are as follows:

To set the number of vertex colors:

BOOL setNumVertCol(int ct,BOOL keep=FALSE);

To retrieve the number of vertex colors:

int getNumVertCol() const

To set the number of vertex color faces:

BOOL setNumVCFaces(int ct, BOOL keep=FALSE, int oldCt=0);

To use a different souce array for vertex color data (this can be either an external array or one of the mapping channels):

void setVCDisplayData(int mapChan = 0, VertColor *VCArray=NULL, TVFace *VCf=NULL);

Stripping

Stripping is the process of taking a mesh and turning it into all set of strips as shown below. Without stripping, when a triangle mesh is sent down the graphics display pipeline, three vertices plus three normals or colors must be sent for each triangle. However, if the triangles are turned into a 'strip', where one triangle points up, the next ponts down, then the next up, etc., forming parallel lines along the top and bottom (see diagram below), then for each new triangle in the strip, all that is sent down the graphics pipeline is the one new vertex. Since the communication between the CPU and the graphics card is one of the bottlenecks in the graphics pipeline this results in a significant speed increase.

image\strip1_wmf.gif

This can only happen if 3ds max knows that each new triangle is adjoining the previous one. Stripping then, is the process of taking whatever configuration the mesh is in originally, and turning it into a sequence of sequence of vertices that all correspond to strips as shown above. This speeds up the display process dramatically.

Developers who create their own mesh objects for display need to be aware of two new methods for dealing with stripping if maximum speed is to be achieved. These method are Mesh::BuildStrips() and Mesh::BuildStripsAndEdges(). These builds the strip (and edge) databases inside the mesh. The standard 3ds max primitives call BuildStripsAndEdges() after creating their meshes for instance.

Changes have been made to the 3ds max geometry pipeline that make this important. In 3ds max 1.x, an algorithm was used to build the edge database when the mesh was about to be displayed. So for example, if a mesh was built, and some modifiers acted upon it, for instance a Bend, the geometry pipeline would be evaulated and then the when the object was about to be displayed its edge list would be built. If the modifers were animated (say the Bend angle changed), the geometry pipeline would be evaluated and the edge list would be built again. This was very inefficient because the edge list would only get used for one display, and then would be thrown out, and then calculated again. Since a Bend modifier doesn't alter the mesh topology the edge list was actually still valid.

In 3ds max 2.0 this changed. The edge and new strip topology flows down the pipeline. In this way, if a primitive builds its own edges and strips by calling BuildStripsAndEdges(), when animated modifiers are applied the display is dramatically faster. This is because the strip and edge database is never rebuilt unless the topology changes. Therefore when a developer creates a mesh to be displayed they should call BuildStripsAndEdges(). If this is not done, they won't be available to flow down the pipeline, and must be build automatically at the end of the pipeline.

If a developer knows that their strips are invalid, for example they've deleted a vertex or face, or otherwise changed the topology of the mesh, then the method InvalidateStrips() and InvalidateEdgeList() should be called. A developer could also call InvalidateTopologyCache() which simply calls both of the above.

However, if a modifier is written that changes topology (ChannelsChanged() includes TOPO_CHANNEL), for example Bomb, then the invalidation happens automatically. Other objects or modifiers may have to make an explicit call to InvalidateTopologyCache(). For example, the Editable Mesh Object or the Edit Mesh Modifier can operate on a mesh at the push and pull vertex level rather than at the pipeline level. In these cases the mesh is being directly modified and thus the invalidate call is required.