Object Modification

3DS Max Plug-In SDK

Object Modification

See Also: Class Modifier, Class SimpleMod, Class Object, Class Deformer, Class ModContext, Class Mesh, List of Channel Bits, List of Class_IDs, Class BitArray.

Overview

This topics presents information on modifying objects in MAX. It discusses how objects and modifiers interact to enable object modification, explains the various types of object modification, and shows key portions of sample code from modifiers of each type.

Objects, Modifiers, and the Geometry Pipeline

Modifiers operate on objects flowing down a geometry pipeline. A geometry pipeline is the system 3ds max uses to process the modification of objects. There are several methods implemented by the objects and the modifiers that allow this to happen. First we'll look at what modifiers need to do to inform 3ds max about the nature of their modificiation. Then we'll examine what procedural objects need to do to make themselves 'modifyable'.

Modifiers Request A Certain Type of Input

Modifiers may only operate on certain type of object. For example, the Edit Spline modifier only works on spline shapes. The Volume Select Modifier only works on TriObjects (triangle mesh objects). In order for the modifier to inform 3ds max what type of object it needs it implements the method Modifier::InputType(). This method returns the Class_ID of the type of object it needs for input. For example, the Edit Spline modifier implements this method as shown below:

Class_ID InputType() { return Class_ID(SPLINESHAPE_CLASS_ID,0); }

This tells 3ds max which objects the modifier can be applied to. Only those objects that are spline shapes, or are able to convert themselves to spline shapes, may be modified by Edit Spline. The same is true for Volume Select Modifier. It requires objects that are triangle meshes for it to do its work. Therefore it implements InputType() as shown below:

Class_ID InputType() { return Class_ID(TRIOBJ_CLASS_ID,0); }

When an object is selected in MAX, only those modifiers that are appropriate for modifying it are enabled in the user interface. This is done by querying the modifiers to see if their InputType() matches the Class_ID of the selected object, or the objects that it can convert itself to. If it is, the modifier is enabled, otherwise its disabled.

Modifiers Need To Indicate Which Geometry Pipeline Channels They Require and Alter

Objects flowing down the geometry pipeline are broken into separate channels. That is, the pipeline can operate on only portions of objects and not the entire thing as a whole. Some examples of channels that objects are broken into are the geometry portion (points), the topology portion (polygons or faces), the selection level of the object (object, face, vertex, etc.), and its texture coordinates. The purpose of this is basically to allow the pipeline to operate more efficiently.

Modifiers must inform 3ds max which channels they require to perform their modification. Copies of the needed channels are made and passed up the pipeline. 3ds max will only pass copies of a minimum set of needed channels up the pipeline. If a certain channel is not required by any modifiers anywhere in a pipeline it is not copied and passed along.

Modifiers inform 3ds max which channels they require by implementing the method Modifier::ChannelsUsed(). This list of channels must include the channels the modifier actually alters but may include more. Here is the implementation of ChannelsUsed() by the Volume Select modifier:

ChannelMask ChannelsUsed()

{

return OBJ_CHANNELS;

}

Note that OBJ_CHANNELS is defined as:

#define OBJ_CHANNELS

(TOPO_CHANNEL | GEOM_CHANNEL | SELECT_CHANNEL | TEXMAP_CHANNEL | MTL_CHANNEL | SUBSEL_TYPE_CHANNEL | DISP_ATTRIB_CHANNEL | VERTCOLOR_CHANNEL | GFX_DATA_CHANNEL)

This means that the pipeline must have all these channels up to date in the object that gets passed to the modifier before it can properly do its work. Technically, this is actually overkill for the Normals modifier. It wouldn't need the TEXMAP_CHANNEL and VERTCOLOR_CHANNEL since they aren't in fact used. It really only needs the GEOM_CHANNEL, TOPO_CHANNEL, and the SELECT_CHANNEL.

A modifier must also inform 3ds max which channels it will actually alter its function as a modifier. It does this by implementing the method Modifer::ChannelsChanged(). Here is the implementation of ChannelsChanged() by the Volume Select modifier:

ChannelMask ChannelsChanged()

{

return SELECT_CHANNEL|SUBSEL_TYPE_CHANNEL|GEOM_CHANNEL;

}

This indicates that this modifier alters the selection channel, the sub-object selection channel, and the geometry channel. Note that the geometry channel is modified because the vertices themselves get selected via the vertSel BitArray in the Mesh class and are thus modified.

Objects May Need to Convert Themselves

Procedural Objects don't always start out in a form suitable for a particular type of modifier. Consider the case of a 3ds max user putting a volume select modifier on a procedural sphere in order to select some vertices within it. A procedural sphere is not defined by vertices and faces. Rather it is defined by it procedural definition, i.e. it's radius, segment count, hemisphere setting, etc. But a user can put a volume select modifier on a procedural sphere. The way this is handled is that 3ds max asks the object if it can be converted to a triangle mesh object (which has vertices and faces). If the sphere responds yes, then 3ds max asks the sphere to convert itself to one. Then, once the objects is in the form of the triangle mesh, the modifier may alter it. There are two methods that procedural objects implement to allow this to happen. These are Object::CanConvertToType() and Object::ConvertToType().

The code executed by the procedural sphere implementation of CanConvertToType() is as follows:

int SphereObject::CanConvertToType(Class_ID obtype)

 {

 if (obtype==patchObjectClassID || obtype==defObjectClassID ||

  obtype==triObjectClassID || obtype==EDITABLE_SURF_CLASS_ID) {

  return 1;

 } else {

  return SimpleObject::CanConvertToType(obtype);

  }

 }

 

int SimpleObject::CanConvertToType(Class_ID obtype)

 {

 if (obtype==defObjectClassID || obtype==mapObjectClassID ||

obtype==triObjectClassID || obtype==patchObjectClassID) {

  return 1;

 } 

 return Object::CanConvertToType(obtype);

}

 

int Object::CanConvertToType(Class_ID obtype)

 {

 return obtype==ClassID();

 }

Note that the sphere answers true when asked if it can convert itself to a patch object, a deformable object (a generic object with points that can be modified), a triangle mesh object, and an editable surf object (NURBS object). In other cases it calls the base class method from SimpleObject. If SimpleObject doesn't recognize the type it calls its base class method from Object. The Object implementation returns nonzero if the object is asked to convert to itself, otherwise zero.

When 3ds max needs to actually have the object convert itself to the type required by the modifier it calls ConvertToType() on the object as passes the needed Class_ID. Below is the code from the procedural sphere's implementation.

Object* SphereObject::ConvertToType(TimeValue t, Class_ID obtype)

 {

 if (obtype == patchObjectClassID) {

  Interval valid = FOREVER;

  float radius;

  int smooth, genUVs;

  pblock->GetValue(PB_RADIUS,t,radius,valid);

  pblock->GetValue(PB_SMOOTH,t,smooth,valid); 

  pblock->GetValue(PB_GENUVS,t,genUVs,valid);

  PatchObject *ob = new PatchObject();

  BuildSpherePatch(ob->patch,radius,smooth,genUVs);

  ob->SetChannelValidity(TOPO_CHAN_NUM,valid);

  ob->SetChannelValidity(GEOM_CHAN_NUM,valid);

  ob->UnlockObject();

  return ob;

 } else if (obtype == EDITABLE_SURF_CLASS_ID) {

  Interval valid = FOREVER;

  float radius, hemi;

  int recenter, genUVs;

  pblock->GetValue(PB_RADIUS,t,radius,valid);

  pblock->GetValue(PB_HEMI,t,hemi,valid); 

  pblock->GetValue(PB_RECENTER,t,recenter,valid);

  pblock->GetValue(PB_GENUVS,t,genUVs,valid);

  Object *ob = BuildNURBSSphere(radius, hemi, recenter,genUVs);

  ob->SetChannelValidity(TOPO_CHAN_NUM,valid);

  ob->SetChannelValidity(GEOM_CHAN_NUM,valid);

  ob->UnlockObject();

  return ob;

  

 } else{

  return SimpleObject::ConvertToType(t,obtype);

  }

 }

The cases handled above cover converting to a patch or NURBS representation. The other cases are handled by the base classes.

In summary, an object must implement two methods to allow a modifier to operate upon it. These are CanConvertToType() and ConvertToType().

Types of Object Modification

This section presents information on the general types of modifiers possible in 3ds max and an overview of how they accomplish their modification. Code is presented for each showing the needed input type, channels changed and used, and the method that does the object modification.

There are many types of modifiers possible in MAX. The way they are classified is by which channel(s) of the geometry pipeline they operate on. Modifiers may operate on one or more channels. These channels are things such as geometry, topology, texture coordinates, sub-object selection level, vertex colors, as well as several others. See List of Channel Bits to review the full list of channels the pipeline is broken down into. The sections below show examples of modifiers that operate on many of these different channels. For each of these an analysis of the critical code fragment that actually handles the alteration of the objectis shown -- this is the method Modifier::ModifyObject(). This method is defined like this:

Prototype:

virtual void ModifyObject(TimeValue t, ModContext &mc, ObjectState* os, INode *node)=0;

Remarks:

This is the method that actually modifies the input object. This method is responsible for altering the object and then updating the validity interval of the object to reflect the validity of the modifier.

Parameters:

TimeValue t

The time at which the modification is being done.

ModContext &mc

A reference to the ModContext. The ModContext stores information about the space the modifier was applied in including.

ObjectState* os

The object state flowing through the pipeline. This contains a pointer to the object to modify.

INode *node

The node the world space modifier is applied to. This parameter is always NULLfor Object Space Modifiers and non-NULL for World Space Modifiers (Space Warps). This is because a given WSM is only applied to a single node at a time whereas an Object Space Modifier may be applied to several nodes. This may be used for example by particle system space warps to get the transformation matrix of the node at various times.

Below are examples of many of the modifier types.

Modifiers Which Change Topology

These modifiers alter the face or polygon structures of the objects they are applied to. Smoothing groups and materials are also part of the topology channel. Edge visibility is also part of this channels since it is an attribute of the face structure. The face normals are also part of the topology channel. The example below is from the Normals modifier which allows the user to unify and flip the face normals of a mesh. The full source code is available in \MAXSDK\SAMPLES\MODIFIERS\SURFMOD.CPP.

Below are the implementations of InputType(), ChannelsUsed(), and ChannelsChanged().

 Class_ID InputType() {return triObjectClassID;}

 ChannelMask ChannelsUsed() {return OBJ_CHANNELS;}

 ChannelMask ChannelsChanged() {return GEOM_CHANNEL|TOPO_CHANNEL;}

Note that this modifier only operates on triangle mesh objects, or those objects able to convert themselves to triangle meshes. Also note that this modifier specifies the object channels for those that it needs up to date to perform its modification. Since this modifier changes the vertex and face structure of the mesh to alter the normals it specifies it modifies the geometry channel and the topology channel in ChannelsChanged().

Here is the implementation of ModifyObject(). Note that when the modifier is done it updates the validity of the object flowing down the pipeline by calling UpdateValidity() and passing the topology channel number.

void NormalMod::ModifyObject(TimeValue t, ModContext &mc,

ObjectState *os, INode *node)

 {

 Interval valid = FOREVER;

 int flip, unify;

 pblock->GetValue(PB_FLIP,t,flip,valid); 

 pblock->GetValue(PB_UNIFY,t,unify,valid); 

 

 assert(os->obj->IsSubClassOf(triObjectClassID));

 TriObject *triOb = (TriObject *)os->obj;

 BOOL useSel = triOb->mesh.selLevel==MESH_FACE;

 

 if (unify) {

  triOb->mesh.UnifyNormals(useSel);  

  }

 

 if (flip) {

  for (int i=0; i<triOb->mesh.getNumFaces(); i++) {

   if (!useSel || triOb->mesh.faceSel[i]) {

    FlipMeshNormal(&triOb->mesh,(DWORD)i);

    }

   }

  }

   

 triOb->UpdateValidity(TOPO_CHAN_NUM,valid);  

 }

Modifiers Which Change Mapping (Texture Coordinates)

These modifiers alter the texture coordinates of the objects they modify. These can add new mapping coordinates to unmapped objects or modify the existing mapping coordinates of objects. A modifier that adds new mapping coordinates is the UVW Mapping Modifier. The full source code is available in \MAXSDK\SAMPLES\MODIFIERS\MAPMOD.CPP. Below are the implementations of InputType(), ChannelsUsed(), and ChannelsChanged().

Class_ID InputType() { return mapObjectClassID; }

ChannelMask ChannelsUsed()

{

return PART_GEOM | PART_TOPO | PART_SELECT |

PART_SUBSEL_TYPE | PART_VERTCOLOR;

}

ChannelMask ChannelsChanged()

{

return TEXMAP_CHANNEL|PART_VERTCOLOR;

}

The mapping modifier requests a modifier that is mappable by specifying the Class_ID mapObjectClassID. Objects that know how to make themselves mappable respond to this Class_ID in CanConvertToType() and return TRUE. This indicates that they may have texture coordinates assigned to them and they implement the method Object::ApplyUVWMap(). Note how the Vertex Color channel is needed and changed as well as the texmap channel. This is because 3ds max uses the second mapping channel as the vertex color channel and the user is able to specify that the second mapping channel may be used for the mapping coordinates.

Here is the implementation of ModifyObject(). Note that when the modifier is done it updates the validity of the texture map channel by calling UpdateValidity().

void MapMod::ModifyObject(TimeValue t, ModContext &mc,

ObjectState *os, INode *node)

 {

 // If it's not a mappable object then we can't help

 Object *obj = os->obj;

 if (!obj->IsMappable()) return;

 

 // Get pblock values 

 int type, uflip, vflip, wflip, cap, channel; 

 float utile, vtile, wtile;

 pblock->GetValue(PB_MAPTYPE,t,type,FOREVER);

 pblock->GetValue(PB_UTILE,t,utile,FOREVER);

 pblock->GetValue(PB_VTILE,t,vtile,FOREVER);

 pblock->GetValue(PB_WTILE,t,wtile,FOREVER);

 pblock->GetValue(PB_UFLIP,t,uflip,FOREVER);

 pblock->GetValue(PB_VFLIP,t,vflip,FOREVER);

 pblock->GetValue(PB_WFLIP,t,wflip,FOREVER);

 pblock->GetValue(PB_CAP,t,cap,FOREVER);

 pblock->GetValue(PB_CHANNEL,t,channel,FOREVER);

 

 // Prepare the controller and set up mats

 if (!tmControl || (flags&CONTROL_OP) || (flags&CONTROL_INITPARAMS))

  InitControl(mc,obj,type,t);

 Matrix3 tm; 

 tm = Inverse(CompMatrix(t,&mc,NULL));

  

 obj->ApplyUVWMap(type,utile,vtile,wtile,uflip,vflip,wflip,cap,tm,channel);

 

 // The tex mapping depends on the geom and topo so make sure the validity interval reflects this.

 Interval iv = LocalValidity(t);

 iv = iv & os->obj->ChannelValidity(t,GEOM_CHAN_NUM);

 iv = iv & os->obj->ChannelValidity(t,TOPO_CHAN_NUM);

 os->obj->UpdateValidity(TEXMAP_CHAN_NUM,iv); 

 }

To see an example of a modifier that alter existing texture coordinates take a look at the source code for the UVW XForm modifier in \MAXSDK\SAMPLES\MODIFIERS\UVWXFORM.CPP. or the UVW Unwrap modifier in \MAXSDK\SAMPLES\MODIFIERS\UNWRAP.CPP.

Modifiers Which Change Materials

These modifiers alter the material ID stored by the object. An example of this type of modifier is the Material Modifier (\MAXSDK\SAMPLES\MODIFIERS\SURFMOD.CPP). Below are the implementations of InputType(), ChannelsUsed(), and ChannelsChanged(). Note that materials are rolled into the face structure of the object so this modifier alters the topology channel.

 Class_ID InputType() {return triObjectClassID;} 

 ChannelMask ChannelsUsed() {return OBJ_CHANNELS;}

 ChannelMask ChannelsChanged() {return GEOM_CHANNEL|TOPO_CHANNEL;}

This modifier specifies it changes the geometry channel. Technically it doesn't need to since it only changes the material index at the face level and nothing at the vertex level. Again, this is a bit of overkill.

Here is the implementation of ModifyObject(). Note that when the modifier is done it updates the validity of the object flowing down the pipeline by calling UpdateValidity() and passing the topology channel number.

void MatMod::ModifyObject(TimeValue t, ModContext &mc,

ObjectState *os, INode *node)

 {

 Interval valid = FOREVER;

 int id;

 pblock->GetValue(PB_MATID,t,id,valid); 

 id--;

 if (id<0) id = 0;

 if (id>0xffff) id = 0xffff;

 

 assert(os->obj->IsSubClassOf(triObjectClassID));

 TriObject *triOb = (TriObject *)os->obj;

 BOOL useSel = triOb->mesh.selLevel==MESH_FACE;

 

 for (int i=0; i<triOb->mesh.getNumFaces(); i++) {

  if (!useSel || triOb->mesh.faceSel[i]) {

   triOb->mesh.setFaceMtlIndex(i,(MtlID)id);

   }

  }

  

 triOb->UpdateValidity(TOPO_CHAN_NUM,valid);  

 }

Modifiers Which Change Selection

These modifiers alter the selection level of objects. An example is the Volume Select modifier. The full source code is available in \MAXSDK\SAMPLES\MODIFIERS\SELMOD.CPP.

Below is the implementations of InputType(), ChannelsUsed(), and ChannelsChanged().

 Class_ID InputType() {return triObjectClassID;}

 ChannelMask ChannelsUsed() {return OBJ_CHANNELS;}  

 ChannelMask ChannelsChanged()

{

return SELECT_CHANNEL|SUBSEL_TYPE_CHANNEL|GEOM_CHANNEL;

}

Note that ChannelsChanged() for the Volume Select Modifer doesn specify DISP_ATTRIB_CHANNEL in ChannelsChanged(). Technically it really should since it changes the Mesh display flags. The reason this isn't a problem however is that this is only a single int in the Mesh and isn't dynamically allocated. So it is actually always present even without specifically specifying it.

Here is the implementation of ModifyObject(). Note that when the modifier is done it updates the validity of the object flowing down the pipeline by calling UpdateValidity() on all the appropriate channels.

void SelMod::ModifyObject(TimeValue t, ModContext &mc,

ObjectState *os, INode *node)

 {

 Interval valid = LocalValidity(t);

 int level, method, type, vol, invert;

 

 pblock->GetValue(PB_LEVEL,t,level,FOREVER);

 pblock->GetValue(PB_METHOD,t,method,FOREVER);

 pblock->GetValue(PB_TYPE,t,type,FOREVER);

 pblock->GetValue(PB_VOLUME,t,vol,FOREVER);

 pblock->GetValue(PB_INVERT,t,invert,FOREVER);

 

 assert(os->obj->IsSubClassOf(triObjectClassID));

 TriObject *obj = (TriObject*)os->obj;

 Mesh &mesh = obj->mesh; 

 

 // Prepare the controller and set up mats

 if (!tmControl || (flags&CONTROL_OP)) InitControl(mc,obj,type,t);

 Matrix3 tm; 

 tm = Inverse(CompMatrix(t,&mc,NULL,TRUE,FALSE));

 

 Box3 mcbox = *mc.box;

 FixupBox(mcbox);

 

 switch (level) {

  case SEL_OBJECT:

   obj->mesh.selLevel = MESH_OBJECT;

   obj->mesh.ClearDispFlag(DISP_VERTTICKS|DISP_SELVERTS|DISP_SELFACES);

   break;

 

  case SEL_VERTEX:

   obj->mesh.selLevel = MESH_VERTEX;

   obj->mesh.SetDispFlag(DISP_VERTTICKS|DISP_SELVERTS);

   SelectVertices(obj->mesh,method,type,vol,invert,tm,mcbox);

   break;

 

  case SEL_FACE:

   obj->mesh.selLevel = MESH_FACE;

   obj->mesh.SetDispFlag(DISP_SELFACES);

   SelectFaces(obj->mesh,method,type,vol,invert,tm,mcbox);

   break;

  }

 

 obj->UpdateValidity(SELECT_CHAN_NUM,valid);

 obj->UpdateValidity(GEOM_CHAN_NUM,valid);

 obj->UpdateValidity(TOPO_CHAN_NUM,valid);

 obj->UpdateValidity(SUBSEL_TYPE_CHAN_NUM,FOREVER);

 }

Note how UpdateValidity() is called on four channels.

Modifiers Which Change Geometry

These modifiers just alter the 'points' of the object. Many modifiers in 3ds max do this. For example, Bend, Taper, Twist, Spherify, and Wave all operate on the just the points of objects. Note that the 'points' may mean different things to different objects. For example, a Bend modifier may be applied to both a Cylinder object and a NURBS curve object. That's because they both have 'points' that can be modified, although the nature of the 'points' is quite different between them. Modifiers that operate on these generic points are said to operate on 'deformable' objects.

Modifiers that only alter the geometry channel can be sub-classed from SimpleMod rather than Modifier and have fewer methods to implement. In this case, the base class SimpleMod provides the default implementation of InputType(), ChannelsUsed() and ChannelsChanged(). Here they are:

 Class_ID InputType() {return defObjectClassID;}

 ChannelMask ChannelsUsed()

{

return PART_GEOM|PART_TOPO|SELECT_CHANNEL|SUBSEL_TYPE_CHANNEL;

}

 ChannelMask ChannelsChanged() { return PART_GEOM; }

Note that InputType() return the Class_ID of deformable objects. As noted above, these are a type of objects that have 'points' to deform. Also notice that SimpleModifiers require the geometry, topology, and selection channels. They need the topology channel up to date because these modifiers can work on the selection set. It needs to find the vertices that are selected because they are part of selected faces. To make sure the face selection is up to date the topology channel is specified. The selection channels are required for the same reason -- the modifier may operate on only the selection set if that's what the object flowing down the pipeline is at.

SimpleModifiers don't directly implement ModifyObject(). Rather they implement a method called GetDeformer(). This method returns a callback object which is an instance of the class Deformer. This callback object is really like a pointer to a function that is called to perform the alteration of a single point of an object. The Deformer callback object returned from GetDeformer() has a single virtual method that the plug-in modifier implements called Map(). This method is passed a single point of the object and its job is to modify the point and return it in altered form. To see an example of an implementation of this method see BendDeformer::Map() in \MAXSDK\SAMPLES\MODIFIERS\BEND.CPP.

The base class SimpleMod provides the implementation of ModifyObject() and calls a method of the input object called Deform() which in turn calls the deformer provided by GetDeformer(). Here is the implementation of SimpleMod::ModifyObject():

void SimpleMod::ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node)

 { 

 Interval valid = GetValidity(t);

 Matrix3 modmat,minv;

 

 // These are inverted because that's what is usually needed

// for displaying/hit testing

 minv = CompMatrix(t,mc,idTM,valid,TRUE);

 modmat = Inverse(minv);

 

 os->obj->Deform(&GetDeformer(t,mc,modmat, minv), TRUE);

 os->obj->UpdateValidity(GEOM_CHAN_NUM,valid); 

 }

Notice that the Deform() method of the input object is called passing the deformer provided by the modifier plug-in. The object that is being modified provides the implemtation of Deform() (for example a triangle mesh object that is being modified). Here is the default implementation.

void Object::Deform(Deformer *defProc,int useSel) {

 int nv = NumPoints(); 

 for (int i=0; i<nv; i++)

  SetPoint(i,defProc->Map(i,GetPoint(i)));

 PointsWereChanged();

 }

Notice how this method loops through the points of the object and calls the Map() method on each point. The GetPoint() method of the object returns the 'i-th' point of the object. This is passed to Map() which deforms and returns it. The result returned from Map() is set back into the object by calling SetPoint(). Then a method called PointsWereChanged() is called to let that object know that its points have been altered.

Some objects override the base class definition of this method to provide other ways of modifying the object. For example the PatchObject and SplineShape objects provide alternate implementations. If an object wanted to modify the selected points only for example, it would need to override Object::Deform() and respect the useSel flag that indicates it should use the currrent selection. The base class method shown above ignores that flag. In each case however, they still call the Map() method of the deformer passing it each point to modify.

Modifiers Which Change The Entire Object

There are some modiifer that don't alter one or more parts of the object, they completely replace it with a new object. These modifiers effectively convert the object from one form to another. Examples of this are the Extrude and Lathe modifiers. Both Extrude and Lathe convert a spline object into a mesh, patch or NURBS object, thus completely replacing the entire object. The full source code for Extrude is available in \MAXSDK\SAMPLES\MODIFIERS\EXTRUDE.CPP.

Below are the implementations of InputType(), ChannelsUsed(), and ChannelsChanged().

Class_ID InputType() { return genericShapeClassID; }

ChannelMask ChannelsUsed() { return PART_GEOM|PART_TOPO; }

ChannelMask ChannelsChanged() { return PART_ALL; }

Note that the modifier indicates it changes all the channels by returning PART_ALL from ChannelsChanged().

Below is a subset of the implementation of ModifyObject() (the code for the NURBS and PATCH cases are removed for brevity and simplicity). In the mesh case, the key thing to notice is that a new TriObject is created, the TriObject's mesh is created from the spline, and the new object is placed into the pipeline by assigning it's pointer to the ObjectState's object pointer.

void ExtrudeMod::ModifyObject(TimeValue t, ModContext &mc,

ObjectState *os, INode *node) { 

 

 // Get our personal validity interval...

 Interval valid = GetValidity(t);

 valid &= os->obj->ChannelValidity(t,TOPO_CHAN_NUM);

 valid &= os->obj->ChannelValidity(t,GEOM_CHAN_NUM);

 

 int output;

 pblock->GetValue(PB_OUTPUT, TimeValue(0), output, FOREVER);

 

 switch (output) {

 case NURBS_OUTPUT: {

  // ...

  break;

}

 case PATCH_OUTPUT: {

  // ...

  break;

}

 case MESH_OUTPUT: {

  // BuildMeshFromShape fills in the TriObject's mesh,

  // then we stuff the TriObj into the pipeline.

  TriObject *tri = CreateNewTriObject();

  BuildMeshFromShape(t, mc, os, tri->mesh);

 

  tri->SetChannelValidity(TOPO_CHAN_NUM, valid);

  tri->SetChannelValidity(GEOM_CHAN_NUM, valid);

  tri->SetChannelValidity(TEXMAP_CHAN_NUM, valid);

  tri->SetChannelValidity(MTL_CHAN_NUM, valid);

  tri->SetChannelValidity(SELECT_CHAN_NUM, valid);

  tri->SetChannelValidity(SUBSEL_TYPE_CHAN_NUM, valid);

  tri->SetChannelValidity(DISP_ATTRIB_CHAN_NUM, valid);

 

  os->obj = tri;

  break;

}

 }

 os->obj->UnlockObject();

}

How Long is the Modification Valid For?

This section presents information on validity intervals. These describe a range of time over which the modification preformed by the modifier is accurate. This is needed because so many things in 3ds max can be animated and thus change over time. For example, consider the case above of the procedural sphere and the volume select modifier applied to select a set of vertices. A 3ds max user might animate the number of segments in the sphere as an animated camera gets closer to it -- this keeps the silhouette of the sphere from appearing faceted. The volume select modifier applied to the sphere needs to select all the vertices in the region defined by it's gizmo which represents its volume. Since the segment count in the sphere is changing, the number of vertices in the region defined by the modifier's gizmo is changing. Therefore the selection done at one frame may not be valid at the next frame. In order to tell 3ds max how long the modification is valid, what's called a validity interval is computed and returned. This describes the range of time over which the modification is accurate and up to date.

There are two methods that a modifier needs to call or implement. These are Object::UpdateValidity() and Modifier::LocalValidty().

When a modifier is finished altering an object it needs to include its interval in the validity interval of the object. This way if an object was static, but had an animated modifier applied to it, 3ds max would know that the modifier would need to be re-evaluated if the user moves to a new time. To do this, the modifier calls the UpdateValidity() method on the object, specifying the channel and the modifier's interval. Modifiers that only affect the geometry channel would specify GEOM_CHAN_NUM. Modifiers that affect other channels would have to call this method once for each channel modified. The interval that is passed in to this method is then intersected with the interval that the object keeps for each channel. So as the object travels through the pipeline, its validity intervals are potentially getting smaller as (possibly) animated modifiers are applied to it.

Here are the calls to UpdateValidity() that the Volume Select Modifier makes to the object it modifies. These calls are made inside the modifiers ModifyObject() method.

 obj->UpdateValidity(SUBSEL_TYPE_CHAN_NUM,FOREVER);

 obj->UpdateValidity(SELECT_CHAN_NUM,valid);

 obj->UpdateValidity(GEOM_CHAN_NUM,valid);

 obj->UpdateValidity(TOPO_CHAN_NUM,valid);

The modifier must also implement a method to return to 3ds max it's own validity, independent of the object it is modifiying. This method is Modifier::LocalValidity(). This value is computed by starting an interval off at FOREVER, and intersecting the validity intervals of all the animated parameters of the modifier. In the case of the Volume Select Modifer, it has a single animated parameter -- it's gizmo which represents the actual extents of the volume it selects. Below is the implementation of this method by Volume Select.

Interval SelMod::LocalValidity(TimeValue t)

 { 

 Interval valid = FOREVER;

 if (tmControl) {

  Matrix3 tm(1);

  tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);

  }

 return valid;

 }

Note the interval is started as FOREVER. Then the gizmo's transform controller's GetValue() method is called passing it this initial interval of FOREVER. The controller's GetValue() method will update this interval to reflect the validity of the gizmo. This interval is then returned. Note that if the volume has not been animated yet, and thus there is not a controller assigned to the gizmo yet, FOREVER is returned. This means the modifier is valid at all times. To learn more about validity intervals see the Advanced Topics section Intervals.

Some modifiers are sub-classed from SimpleMod and not Modifier. To return the validity interval of the modifier to SimpleMod, the developer must implement a method named GetValidity(). SimpleMod then provides the implementation of LocalValidity() itself but calls GetValidity() on the SimpleModifier.

Summary

This section presented information about how objects and modifiers work together to accomplish object modification. Objects may need to convert themselves to another form to be modified. They do this in their ConvertToType() method. Modifers need to specify which object types they operate on (InputType()) and which pipeline channels they need and use (ChannelsUsed() and ChannelsChanged()). Modifiers also perform their modification in the method ModifyObject(). Finally, Modifiers must update the validity intervals of the objects they modify to reflect their own validity. This is done inside the method UpdateValidity().