Required Changes to MAX 3.x Plug-Ins for MAX 4.0

3DS Max Plug-In SDK

What's New in the MAX 4.0 SDK

See Also: What's New in the MAX 4.0 SDK.

Overview

This section provides general information on the changes required to all plug-ins to get them running in 3ds max 4.0. It also provides links to topics that discuss the specific changes for many affected plug-in types. Some of these changes are required while others are optional but advantageous.

Microsoft Platform SDK

It is recommended to obtain and install the Microsoft Platform SDK which many of the examples depend on and require in order to compile properly. In order to ensure proper compilation of plugins and voiding any linker errors, please make note of the following;

  • ·   When compiling plugins using the command line, ensure that the path environment variables for library and include files precede the paths set by Microsoft Visual Studio.

  • ·   When compiling plugins using the Visual Studio IDE, ensure that the path configuration (options menu-> directories) for library and include files are listed as the top entries.

Note: The Microsoft Platform SDK can be obtained through the MSDN web site.

Patches

As of R4.0 a vector can now be used by more than two patches. Previously the Class PatchVec kept an index to two patches sharing the vector as an integer array of 2 elements. This has changed to an IntTab. While this would most likely not invalidate any code it is advisable to keep in mind that there could now be more than 2 patches sharing a vector.

 

Some structural changes have been made to the Class PatchEdge that will require some changes in the plugin code. Previous to R4.0 the patches that used an edge were kept in int patch1 and int patch2. These two integers have been replaced with an integer table, IntTab patches because edges can now be used by more than two patches. The plugin code should be changed to reflect these changes.

 

In order to facilitate the new topology tracking code for the Edit Patch modifier a new class has been introduced, Class PatchTVert. This new class has been integrated with the class PatchMesh and brings with it a number of changes. The previous table of UVVerts has been replaced with a table of PatchTVert’s. And a number of methods in Class PatchMesh have been altered to take advantage of this new class. These are; mapVerts(), getTVertChannel(), getTVert(), getTVertPtrChannel(), getTVertPtr(), getMapVert(), and getMapVertPtr().

 

Slight changes have been made to the PatchObject::DoBevel() and PatchObject::DoExtrude() methods. Both now take a Timevalue as a parameter. Please make sure you adapt your code according to this new behavior if you are using these methods.

Splines

A few changes were made to the Class ShapeObject methods in order to support the new animated renderable shapes. These changes should be observed and adjusted in the plugin’s code. ShapeObject::SetThickness(float t) now accepts a TimeValue prior to the thickness parameter, and was changed to; SetThickness(TimeValue t, float thick). The ShapeObject::GetThickness() method now accepts a TimeValue and an Interval parameter, and was changed to; SetThickness(TimeValue t). Currently the previous syntax of these methods will get or set the thickness at frame 0, however Sparks Developers who are using the debug build should take note of the fact that the debug build will raise an assertion to indicate that the old syntax is obsolete and that the new syntax should be used.

User Interface Notes

Developers should no longer use the Win32 calls GetSysColor() and GetSysColorBrush(). Due to the new customizable color schemes in 3ds max 4 developers should now use the encapsulated functions provided by the SDK; GetCustSysColor() and GetCustSysColorBrush(). These will take the same parameters as the Win32 functions but will retrieve the color information from the custom color database.

KeyBoard Shortcuts

The previous keyboard shortcut system has been replace by an enhanced system. The new system uses what are called Action Tables. These tables unify all the actions that can be assigned to keyboard shortcuts, CUI buttons, right-click menus and main menu. See Class ActionTable for more information.

Some of these methods have the same name is ActionItem methods, and a more complete

Sending WM_COMMAND messages to 3ds max

If you are sending WM_COMMAND messages to 3ds max’ main window you will now need to make sure that HIWORD(wParam) is 1, not 0. If you ever get an assertion failure in MenuManager::ExecuteAction(), then it is probably because a WM_COMMAND message was sent with a 0 HIWORD.

Registering window classes for custom controls.

Previously every DLL had to initialize the window classes through a call to InitCustomControls() in DllMain. This caused a substantial hit on the available system resources in Windows 9x for every plug-in that was loaded. The classes are now globally registered for the whole process. Plug-ins no longer have to call InitCustomControls() at startup - but it doesn't hurt since once the classes has been registered they will not be registered again.

Additions to Class BaseObject

Objects and modifiers, that support subobjects have to overload the two new methods NumSubObjTypes() and GetSubObjType(). and return a class derived from ISubObjType in GetSubObjType(). Developers can use the GenSubObjType for convenience.If the parameter passed into GetSubObjType is -1, the system requests a ISubObjType, for the current SubObjectLevel that flows up the modifier stack. If the subobject selection of the modifier or base object does not affect the subobj selection that flows up the stack, the method must return NULL.

Note that this replaces the way subobjects were handled prior to this release. RegisterSubObjectLevel() is obsolete and should no longer be used. Instead the NumSubObjTypes() and GetSubObjType() methods should be used. For more information see Class ISubObjType and Class BaseObject.

Rendering

Plugin renderers should implement ShadeContext::BumpBasisVectors().

Interface::DoExclusionListDialog() accepts an ExclList instead of a NameTab. This will cause the compile to break for plugin renders, which need to be modified to use the new node lists. There is a method Interface::GetINodeByHandle(handle) which can be used to get the node from the handles in the ExclList. Additionally Interface::ConvertNameTabToExclList() allows you to convert name tables to the new exclusion lists. A number of methods now return an ExclList instead of a NameTab, these are; ObjLightDesc::GetExclList(), LightObject::GetExclList(), GenLight::GetExclusionList(), GenLight::SetExclusionList().

In Class IllumParams a few members were replaced; ULONG shFlags and mtlFlags, and Point3 N, and V. The class now contains pointers to the shader and material from which flags and other information can be retrieved. The shading normal N was a copy of the normal in the shadeContext, which is now provided to shaders and renderElements as well as IllumParams, thus sc.Normal() should be used to get the bumped shading normal.

Programatically collapsing the stack

Developers, who are collapsing the stack programmatically, should pay attention to the new Extension Channel (Class XTCObject) and the Class BaseObject methods NotifyPreCollapse() and NotifyPostCollapse(). These methods will be called by the collapse code. It will give the modifier (or BaseObject); that adds an XTC object to the stack the possibility to apply a modifier, that inserts these XTC objects onto the stack after the collapse. Through this mechanism, the XTC will survive a stack collapse. In case these method are not called, the Extension Channel Objects will by default be copied as well, since they are part of the object in the wsCache. However, they won't survive a save/load operation. Please see the Class XTCObject for more details.

Parameter change in Animatable::GetSystemNodes()

The method Animatable:GetSystemNodes() has its signature changed from previous versions and could potentially require an adjustment of plugin code. If you use the previous signature while having it overridden, the compiler will let this pass unnoticed because it assumes this is a new function (while the actual method itself is empty and hidden in the super class). As a result 3ds max will call the method with the new signature and, because it wasn’t overridden, it will call the default implementation as defined

Trivial changes to samplers

Samplers had to be generalized for use with Render Effects. All samplers had to be rewritten slightly, as will other plug-in samplers. The change is quite trivial, however.

Bump vectors in ShadeContext

A problem in the way bump basis vectors were being calculated has been dealt with.

The code was trying to deduce a single set of 3 vectors for U,V,and W, and this led to problems. Since the bump vectors are only used in pairs ( UV,VW, WU) a better approach turns out to be to compute 2 vectors for the specific pair of axes. This required some new API calls. There is a new method in Class ShadeContext, BumpBasisVectors(), which should replace DpDUVW() over time. DpDUVW() is left in so as not to break a lot of plugins.If BumpBasisVectors() returns 1, that is assumed to mean it is implemented, and it will be used instead of DpDUVW().

Systemwide Clone implementation change

A call to BaseClone(this, newob,remap) has been added to all plugins in 3ds max, that implement the Clone method. BaseClone is a virtual member function of Class ReferenceTarget. This method allows base classes to copy their data into a new object created by clone. All overwrites of BaseClone must call their base classes implementation. The implementation in Class ReferenceTarget is copying the CustAttrib objects into the newly created object.

All plugins that implement a Clone method have to call BaseClone with the old and the new object as parameters. The ordering in regards to when this method is called is unimportant. It obviously has to be called after the cloned object is created. The BaseClone method has to check for the cases of from, or to to be NULL and from and to to be equal. It is important to mention for BaseClone, that all overrides have to call their BaseClass’ implementation. With ReferenceTarget::BaseClone we have a central method, that gets called for all clone operations. This allows us to add notifications etc. for cloning.

Code Cleaning for 64 bit

INT_PTR, DWORD_PTR and other _PTR types are new types defined by Microsoft to support UDM (Unified Data Model), These are polymorphic types that are defined to be of the type they indicate but large enough to hold a pointer. For example an int is 32 bits on both Win32 and Win64, so if you cast a pointer to an int it will loose the high bits. To solve this they added the _PTR types so if you need to do pointer arithmetic (with int types) you need to have the type INT_PTR type and the type will be large enough to hold a pointer (i.e. 32 bits on Win32 and 64 bits on Win64). Mesh methods, which dealt with the geometry pipeline had their channel parameters revised from unsigned ulong to ULONG_PTR.

Changed to GetLocalTMComponents

The API of the GetLocalTMComponents() method has been changed. In the current version, the parent matrix is not given as a pointer to the parent node, but as an indirect matrix representation.

Changed support for multi-meshes in the renderer

Two important methods are added to the Class GeomObject. These are GetMultipleRenderMesh() and GetMultipleRenderMeshTM(). Please refer to the class documentation for more information.

Changes to GBufReader to allow writing and bug fix

Two new methods have been added to Class GBufReader: ModifyChannelData() and ModifyAllData().These methods allow values in the current layer to be written. This may seem strange, writing data from the reader, but developers asked for the capability of writing to the already created gbuffer, and it is much simpler to add this capability to the GBufReader than to GBufWriter, which is designed to construct gbuffers from scratch, not modify existing ones.

Transparency and opacity maps in viewports

For the purposes of implementing transparency and opacity maps in the viewports, a new flag has been defined that indicates if transparency is required in the viewport: MTLREQ_TRANSP_IN_VP. See the List of Material Requirement Flags for more details.

Removal of hidden vertices

MNMeshes used to support "hidden vertices". These would be vertices that exist "in the middle" of faces, taking part in their triangulation but considered more a feature of the face than vertices in their own right. These vertices were useful in the past, when MNMeshes were constantly being converted to and from Meshes. We needed to remove them for PolyMeshes to be an efficient pipeline object; keeping track of them was drastically increasing the size of every face, whether it had such vertices or not.

Changes in Class MeshDelta

In class MeshDelta, the data member fCreate was switched from Tab<Face> to Tab<FaceCreate>. In conjunction with this a new method FCreate() was added. More information can be found in Class MeshDelta and Class FaceCreate.

Changes in Class MNMapFace

Data members hdeg, and hvtx have been removed. The constructor MNMapFace() now only accepts one argument, SetAlloc() and SetSize() also accept only one argument, HInsert() and HDelete() have been removed, and MNDebugPrint() no longer accepts any arguments. More details can be found in Class MNMapFace.

Changes in Class MNFace

Data members hdeg and hvtx have been removed. The constructor MNFace() no longer accepts a second argument. SetAlloc() no longer accepts a second argument, HInsert() and HDelete() have been removed and MNDebugPrint() now only accepts one argument. The "tri" array was replaced by the new "diag" array. The diag array's allocated size is always (dalloc-3)*2. If dalloc==3 (triangle), this pointer is NULL. The method "TriVert" was removed. Use GetTriangles() to access this sort of information. More information can be found in Class MNFace.

Changes in Class MNMesh

The methods HVNum() and KillUnusedHiddenVerts() have been removed. There is no longer an MNM_SL_TRI selection level. Methods FindExternalTriangulation() and BestConvexTriangulation() were changed to FindDiagonals() and BestConvexDiagonals(), respectively.

The Class MNMap data member, "m", became private. The accessor method, M, remains public, and now takes values of -1 (MAP_SHADING) and -2 (MAP_ALPHA) as well as 0-99. SetMapSeamFlags(), which previously took an argument of (mp=-1), where -1 meant "set map seam flags based on all maps", had to be split into two methods as follows: void SetMapSeamFlags() and SetMapSeamFlags(int mp);

Several methods that accept a selection level as an argument were changed to take an MNMesh selection level (such as MNM_SL_VERTEX) instead of the old Mesh selection levels they used to take (such as MESH_VERTEX). This has affected the following methods in class MNMesh; GetBorder(), TargetVertsBySelection(), TargetEdgesBySelection(), TargetFacesBySelection(), Slice(), PropegateComponentFlags(), SabinDoo(), SabinDooVert(), and AndersonDo().

Change internal triangulation storage from triangles to diagonals for Class MNMesh

This is another space-saving measure. We always store a particular triangulation, or way to convert the polygon into triangles, in each MNFace. This is necessary to preserve face orientation when converting to and from regular meshes, or to allow users to explicitly edit the triangulation in R4. We used to store this info as triangles, based on the face's "internal" indices. For instance, if we're dealing with an octagon, the vertices would be numbered 0,1,…7, correpsonding to the order in the "vtx" array. However, this was very inefficient, as it required 3 int's to store the triangulation of a triangle (0,1,2), or 6 int's for a quad (0,1,2,2,3,0), even though there's only 1 way to "triangulate" a triangle, and only 2 possibilities for a quad. (The other would be (0,1,3,1,2,3).) Now we're storing diagonals.

For instance, in a quad, we really only need to know whether the diagonal goes from 0 to 2 or from 1 to 3. We can list all the diagonals of an n-sided polygon with (n-3)*2 ints - 0 for a triangle, 2 for a quad, 4 for a pentagon, etc. (The old scheme took (n-2)*3 ints - 3 for a triangle, 6 for a quad, 9 for a pentagon.) This is probably not totally optimized, but it strikes a nice balance between memory usage and ease of use.

CUI Image Lists

CUI Image lists are no longer used. Everything now goes through the icon manager and as such the GetDefaultImageList() and AddToImageList() methods were removed from the Class CUIFrameMgr.

Modifier Sets and Categories

The categories shown in the modifiers drop-down list are modifier sets which you can configure using the Configure Modifier Sets dialog. Modifier Set information is stored in the 3dsmax.ini file. In order to add your own custom Modifier Set to have your plugin showing under its own heading you can create the heading with the appropriate class ID in the 3dsmax.ini file.

Note about member alignment

Please make sure that when you save data from your plugin you save individual data members using a chunk ID instead of saving the image of a class. Saving (and loading) a class image puts you at risk of running into member alignment problems and as such could potentially corrupt saved files. File IO would be put further at risk when you keep Intel’s IA-64 architecture in mind which depends on member alignment. What you should not do is outlined in the following example when loading a class image;

iload->Read(&myclass, sizeof(MyClass), &ab);

Once you change the class in such a way that it affects the data size you run the risk of having to support different versions, file IO incompatibility, and member alignment issues.