Category Archives: Max SDK

Build Mesh and Convert to MNMesh

If you want to write a plug-in object in Max, probably you are starting with the Widget example offered by Max SDK. It is a class derived from SimpleObject2, so it can only have triangular Mesh. Inside BuildMesh() method, this is howto create a simple mesh with only one quad face:

mesh.verts[0] = Point3(0.0,0.0,0.0);
 mesh.verts[1] = Point3(10.0,0.0,-0.5);
 mesh.verts[2] = Point3(10.0,10.0,0.0);
 mesh.verts[3] = Point3(0.0,10.0,1.5);
mesh.faces[0].v[0] = 0;
 mesh.faces[0].v[1] = 1;
 mesh.faces[0].v[2] = 2;
 mesh.faces[1].v[0] = 0;
 mesh.faces[1].v[1] = 2;
 mesh.faces[1].v[2] = 3;
mesh.setMapSupport(1, TRUE);
MeshMap& map = mesh.Map(1);
 map.setNumFaces(2);[0]=UVVert(0,0,0);[1]=UVVert(1,0,0);[2]=UVVert(1,1,0);[3]=UVVert(0,1,0);[0].setTVerts(0, 1, 2);[1].setTVerts(0, 2, 3);

Actually there are 2 triangles. But as long as there is:


It will appear to be a single quad face. Now turn off the object display of “Edge Only”, the hidden edges will show up as a dotted line, two overlapped dotted lines indeed.

So how to convert Mesh to MNMesh? In fact you don’t have to, Max will do that for you. If you attached an EditPoly modifier to the Mesh object, it will be converted to Poly (MNMesh), unless you bother to override the ConvertToType() method, in which you can explicitly create an MNMesh and send to the pipeline. Don’t think that is necessary if you want the mesh unchanged.

Then how Max converts Mesh to MNMesh? I suggest Max traverses through all those triangles, looking for hidden edges, and remove those hidden edges to merger triangles into polygons. So the mesh we built above will be converted to a single quad face. The catch is Max will merger any triangles happened to be meeting at the same hidden edge, regardless whether those triangles are from the same “face”. That could lead to unexpected conversion. So set hidden edges carefully.


Leave a comment

Filed under Max SDK

Group Instead Of Dummy

In Max Script, if you want to create a group, you should have something to be inside the group first. i.e.

group something
group selection

So far, I only know how to create an empty Group via Max SDK.

INode *child;
Object *obj = (Object*)CreateInstance(HELPER_CLASS_ID, Class_ID(DUMMY_CLASS_ID,0));
IDerivedObject *dobj = CreateDerivedObject(obj);
child = GetCOREInterface()->CreateObjectNode(dobj);
// attach to another INode*
// attach another INode* to it

Wait a minute. This creates a Dummy, not a Group! A Dummy is quite a different thing. It cannot be closed. It’s bounding box does not respond to stuff under it. If you delete a Dummy, stuff under it will still be there. OK, there is some trick: as long as you type:


It will be a Group! Also there is something I don’t understand: after an API mesh object (say grandChild) is created, I should attach it to an existing Group. Unless I call:


It will fail. I suggest that is due to a mesh object cannot be a group head. Just like only Transform node can have children in Maya. Because I want to put everything in the same hierarchical order as they are in Maya, a mesh cannot have any child should not bother me in this case.

Leave a comment

Filed under Max Script, Max SDK

Extracting Mesh

This is a quick note about how to extract polygonal mesh from an object in Maya API and Max SDK.

In Maya, given the MObject of something, first we check if it is a mesh by MObject::hasFn(), if it returns true, create an MFnMesh based on this MObject. i.e.

MFnMesh fmesh(some_obj);
// do something to the fmesh

MFnMesh is the mesh function set can do almost anything to the mesh. For example:

int n_poly = fmesh.numPolygons();
 int n_vert = fmesh.numVertices();

tells us how many faces or vertices the mesh has.

In Max, there are two kinds of mesh object: TriObject is strictly triangular mesh, and PolyObject is polygonal mesh. Once you have the INode, you must check if it can be converted to a specific kind of mesh object. If it is convertible, the actual mesh function and data can be accessed as a specific member belongs to the object. i.e.

char retrievePolyFrom(INode * node, MNMesh& out_mesh)
 Object *cvtobj = node->EvalWorldState(0).obj;

 if (cvtobj->CanConvertToType(Class_ID(POLYOBJ_CLASS_ID, 0)))
 PolyObject *poly = (PolyObject *)cvtobj->ConvertToType(0, Class_ID(POLYOBJ_CLASS_ID, 0));

 out_mesh = poly->mm;
 return 1;
 return 0;

This only works with (polygonal) MNMesh, which is PolyObject::mm. The (triangular) Mesh should be TriObject::mesh.

Now you can use the function set to retrieve or modify the mesh data. i.e.

int face_count = poly_mesh.F(0)->deg;

This returns how many vertices the fist face of the polygonal mesh has.

Leave a comment

Filed under Max SDK, Maya API

Finding Materials

This note is about how to find materials in Maya API and Max SDK. In Maya, we use MItDag to walk through all meshes to look for attached materials. All types of geometry, mesh, NURBS surface, SubDiv, could have material, but for simplicity, here only meshes are listed.

MItDag itdag(MItDag::kDepthFirst, MFn::kMesh);
(; !itdag.isDone();
MDagPath path;

MObject omesh = itdag.currentItem();

// find the material and do something to it


There is no direct connection between mesh and material, there are linked up via a Shading Engine (or SG). Find the SG by MItDependencyGraph go downstream:

MObject findShadingEngine(MObject &mesh)
MStatus result;
MItDependencyGraph itdep(mesh, MFn::kShadingEngine, MItDependencyGraph::kDownstream, MItDependencyGraph::kDepthFirst, MItDependencyGraph::kNodeLevel, &result);
for(; !itdep.isDone();
return itdep.currentItem();
return MObject::kNullObj;

Then go upstream from the “surfaceShader” plug of SG to find the material. Only lambert, blinn, and phong is listed here:

MObject findMaterial(MObject &mesh)
MObject osg = findShadingEngine(mesh);
if(osg == MObject::kNullObj)
return osg;
MStatus result;
MPlug plgsurface = MFnDependencyNode(osg).findPlug("surfaceShader");
MItDependencyGraph itdep(plgsurface, MFn::kInvalid, MItDependencyGraph::kUpstream, MItDependencyGraph::kDepthFirst, MItDependencyGraph::kNodeLevel, &result);
for(; !itdep.isDone();
MObject ocur = itdep.currentItem();
if(ocur.hasFn(MFn::kLambert) || ocur.hasFn(MFn::kBlinn) || ocur.hasFn(MFn::kPhong))
return ocur;
return MObject::kNullObj;

Now we have the MObject pointed to the material ready for further operation.

In Max, there is nothing like MItDag, I do it myself:

void lsChildNode( INode *parent, std::vector<INode *>& nodeList)
int objectCount = parent->NumberOfChildren();
for(int i = 0; i < objectCount; i++)
INode* cur = parent->GetChildNode(i);
lsChildNode( cur, nodeList);
void lsAllNodeInScene(std::vector<INode *>& nodeList)
Interface* ip = GetCOREInterface();
INode* parent = ip->GetRootNode();
int objectCount = parent->NumberOfChildren();
for(int i = 0; i < objectCount; i++)
INode* cur = parent->GetChildNode(i);
lsChildNode( cur, nodeList);

It starts with scene root, lists everything under it, generating a long list of INode. Material is accessed directly by INode::GetMtl()

for(int i=0; i < allNode.size(); i++)
Mtl* m =  allNode[i]->GetMtl();
if(m) do_something_to(m);

Leave a comment

Filed under Max SDK, Maya API

API Animation Curve

This document is about how to create keyframe animation by Maya API and Max SDK.

First, some basic idea. An animation curve can be regarded as Q = f(t), means at any given time t, you can calculate the value of Q by function f. Both Maya and Max uses modified cubic 2 dimensional bezier curve as f. The curve is a number of segments connected by knots call keyframes, or keys. Below is the diagram showing one segment.

Green dotted line is the curve, which is defined by 4 points, red ones are end points, blue ones are control points. You may call those yellow lines handles. Those points live in 2-dimensional space. Actually a key should have one end point and two control points, one for in segment, the other for out. Two keys defines one segment, and a curve should have at least one segment, so at least two keys.

Creating animation curve in Maya is fairly straightforward. You have the MPlug of any keyable attribute, create the curve by MFnAnimCurve::create(MPlug), then add keys by MFnAnimCurve::addKeyframe(MTime, double). So at the end point, Q is the double value, and t is MTime. For each control points, we need tangent type, angle, weight.
Tangent is the handle mentioned before. In the following image:

Tangent angle equals atan(dQ/dt), and tangent weight is length of the handle, which equals sqrt(dQ*dQ + dt*dt).
Set the tangent angle and weight by MFnAnimCurve::setTangent().
Tangent type is changed by MFnAnimCurve::setInTangentType() and MFnAnimCurve::setOutTangentType(). I think MFnAnimCurve::kTangentFixed is the most flexible and useful.
There are two kinds of animation curve, weighted and unweighted. We are talking about weighted version here, so MFnAnimCurve::setIsWeighted(true).
Not yet, tangent handles should be unlocked by calling MFnAnimCurve::setTangentsLocked() and MFnAnimCurve::setWeightsLocked() before those angles and weights can be correctly set.

The Max side of the story is a bit more confusing. In Max, keyframe animation is done via controllers. Controllers are attached to a parameter belongs to a IParamBlock2 of the INode, or just built into the INode. There are a few ways to access the controller, i.e.
Once you have the controller, get the keyframe interface by
IKeyControl *ikeys = GetKeyControlInterface(c);

Keyframe is added by IKeyControl::SetKey(). Specific controller requires specific kind of keys. We only talk about IBezFloatKey here.

It has time (t) and val (Q).

Tangent type is set by SetInTanType() and SetOutTanType(). BEZKEY_USER is similar to Maya’s Fixed type.

A Max key has intan, outtan, inLength, and outLength. The value of intan and outtan equals dQ/dt. The value of inLength and outLength equals dt/time_span. The time_span for out tangent of first key is shown below

Remember multiply any time value by TicksPerFrame.

Leave a comment

Filed under Max SDK, Maya API