Updating MAX 1.0 Plug-Ins to Work with MAX 2.x
See Also: What's New in the MAX 2.0 and 2.5 SDKs.
Overview
This section discusses some changes in the SDK APIs from 3ds max 1.0 to 2.0 or 2.5. Developers need to be aware of these changes as old plug-ins won't compile or operate properly unless they are made. The changes are broken down into specific areas that have changed. Some general notes related to all plug-ins appear at the beginning of this topic. For a brief overview of the API additions and changes for r2 see the section What's New in the MAX 2.0 and 2.5 SDKs. Note that this section does not present all the areas of change -- only those where the change will prevent the plug-in from compiling/linking/executing under 3ds max 2.0.
Recompilation
Plug-Ins that were developed for use with 3ds max 1.0 will need to be recompiled using the 3ds max 2.0 SDK in order to run. The LibVersion() function will prevent older plug-ins from running in 3ds max 2.0. These plug-ins will display the message:
DLL <full pathname> is an obsolete version - not loading.
In some cases a simple recompilation is all that is required. In other cases changes must be made. Developers need to be aware of the following changes to the API that affects their ability to run in 3ds max version 2.
Note: The LibVersion() method has been expanded to include information about the current version of MAX, the SDK, and the API. See the Advanced Topics section on DLL Functions and Class Descriptors for details.
Also Note: Generally, plug-ins developed for 3ds max 2.0 will run without recompilation on 3ds max 2.5. The only exception to this are those that use the NURBS API. Significant changes have taken place in the NURBS API and thus a recompile is required. If your plug-in #includes either SURF_API.H or TESSINT.H (and you're using them, of course) you'll need to recompile. If you don't include these files, your plug-in developed for 3ds max 2.0 will run without problems on 3ds max 2.5 without recompilation.
Development Environment
To use the 3ds max SDK you need to use the same version of the same compiler that is used to compile MAX. There are several reasons for this. The first reason is name mangling. Developers need a compiler with an identical name mangling scheme. The second reason is memory management. The plug-ins need to use the same memory manager as MAX.
Developers need to use Visual C++ Version 5.0 for developing plug-ins for 3ds max 2.0. The use of any previous version of VC++ is not supported.
Loading an existing project into VC++ 5.0 will attempt to convert it to the new format. If this process fails for some reason, or to build a new project from scratch, see the section Creating a New Plug-In Project for the proper settings.
Checking the Current Version of MAX at Runtime
Developers should be aware that they cannot call methods provided in later versions of the 3ds max API from an earlier version of MAX. This can happen, for example, if a user tries to run a 3ds max 2.5 plug-in on 3ds max 2.0. The plug-in will load just fine, but the 2.5 specific functions would not exist. Developers can do something like the following to check the running version of MAX:
DWORD v = Get3DSMAXVersion();
int r = GET_MAX_RELEASE(v);
if (r >= 2500) {
// Code that requires 2.5 API here
}
New Parameters of Existing Methods
Developers should watch out for the case where existing virtual methods have had new parameters added to them. This results in the compiler not seeing the new methods as being implemented. Rather the existing implementation of the method is just seen as a method of the sub-class. Instances of this are rare, however cases where new parameter have been added to existing methods are noted in the documentation for the methods.
Passing Along Mouse Messages in Dialog Procs
In 3ds max 2.0 a change was made in the rollup window message handling so that the right mouse button menu works in all rollups. Developers can now remove the code from their dialog procs that passes the mouse button and move messages into Interface::RollupMouseMessage(). For example, in 3ds max 1.x developers needed to pass along WM_LBUTTONDOWN, WM_LBUTTONUP, and WM_MOUSEMOVE messages to MAX. This is no longer required. So, code such as the following can be removed from any dialog procedures.
case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_MOUSEMOVE:
theUtility.ip->RollupMouseMessage(hWnd, msg, wParam, lParam);
break;
To implement the change the DWL_USER slot was used in the RollupPanel's window to store a pointer to the RollupPanel. This works fine with most plug-ins, which use GWL_USERDATA to store a pointer to their context. Developers must be aware that if they are developing any code that uses DWL_USER for a RollupPanel dialog proc to store its context, change it to use GWL_USERDATA. There is a test in place to detect this which will put up an alert when the rollup is initialized, saying "Rollup Window Procs must use GWL_USERDATA, not DWL_USER", so developers breaking this rule will be informed pretty quickly.
Working with the New Object Snap System
Developers who implement creation procedures for their plug-in that call ViewExp::SnapPoint() should add a call to ViewExp::SnapPreview() to their code where the MOUSE_FREEMOVE message is processed. This allows the user to get proper visual feedback about object snaps that are in effect. See the Advanced Topics section on Snapping, specifically the section 'Handling Snap Preview and Point Snap in Creation Procedures' for more details. Developers who don't do this will get snapping to occur in their plug-ins but won't get the visual feedback.
Bitmap Memory Management
Previously bitmaps were deleted using the delete operator. This has changed. The bitmap class now has a DeleteThis() method that must be used to delete all bitmaps. For example, previously code to create a bitmap and delete it that looked like this:
Bitmap *bmap = TheManager->Create(&bi);
// Do something…
delete bmap
This is now written as follows:
Bitmap *bmap = TheManager->Create(&bi);
// Do something…
bmap->DeleteThis();
If you attempt to do it the old way you'll get a error message during compilation similar to 'Bitmap::~Bitmap' : cannot access private member declared in class 'Bitmap'.
Space Warp Plug-Ins
It is important to note that source code examples in the 1.x SDK had the space warp helper object returning 1 from the ClassDesc::IsPublic() method. This indicated that the helper was available for use not just by the plug-in itself but by anyone. This was incorrect. However there was no user interface available to allow the user to choose and run these plug-ins independently so it was never caught as an error. With the new space warp architecture there is. Thus, the ClassDesc::IsPublic() method must return 0 instead of 1 for space warp helper objects to prevent the user from creating them without the space warp itself.
Automatically Turning On Mapping Coordinates in Procedural Objects
The 3ds max renderer and plug-in procedural objects now support the ability to automatically turn on mapping coordinates when they are needed but aren't available. In 3ds max 1.x, if the renderer needed mapping coordinates for an object, and the 'Generate Mapping Coordinates' check box wasn't on, or a mapping modifier was not applied, an error message was presented to the user. In 3ds max 2.0 the renderer can request that the object turn on mapping coordinates itself and thus avoid having to put up the error message.
Procedural object plug-ins need to implement a few methods (Object::HasUVW() and Object::SetGenUVW()) to provide this capability. See the documentation for these methods in Class Object for more details.
Supporting the Second Mapping Channel
In 3ds max 2.0 and later there are now two channels of mapping coordinates that can be assigned and carried by a mesh. This lets the user have two different sets of mapping coordinates, simultaneously, on the same face. Developers of plug-ins that provide mapping coordinate support should now also support the new second mapping channel.
The second mapping channel is stored in the mesh in the same arrays as the color per vertex information. Thus, the second mapping channel vertex array is stored in the Mesh data member VertColor *vertCol, and the face array is TVFace *vcFace. The number of texture verts is int numCVerts.
For additional details see Class Mesh.
Supporting the Drag and Drop System
Drag and drop functionality has been expanded to include all map and material buttons -- including those in plug-in materials and texmaps. As a result, a 3ds max user can drag the button over a like button to display the Swap/Copy/Cancel dialog. Developers creating plug-in materials and texmaps, as well as plug-ins with UI controls with a bitmap or material/texmap need to implement code to work with drag and drop. There are several new or revised classes to support this system. For additional details see Class DADMgr, Class TexDADMgr, Class MtlDADMgr, Class DADBitmapCarrier, Class ICustButton, Class IDADWindow.
Splines
Three methods have been removed from Class Spline3D and replaced with six others. The InVec() and OutVec() methods were giving direct access to the spline data via a reference. The way the spline vectors are stored inside the class has been revised and this could no longer be allowed. Instead, there are new GetInVec(), GetOutVec(), SetInVec() and SetOutVec() methods. Also, all the data has been moved into the private: section to prevent improper access.
Developers using the InVec() methods will either be using it on the left side of the = or the right side. The changes that must be made are as follows:
Left Side of = (lvalue)
Example:
InVec(i) = Point3(0,0,0);
Change To:
SetInVec(i, Point3(0,0,0));
Right Side of = (rvalue)
Example:
Point3 p = InVec(0);
Change To:
Point3 p = GetInVec(0);
Also, the KnotPoint() method has been replaced. This is part of a larger series of changes which have made working with spline shapes much faster. By removing the direct access of the reference-returning methods, the Spline3D class will be able to control caching of various data within itself, preventing time-wasting repeated operations computing bezier control points. The KnotPoint() method has been replaced by two new methods:
Point3 GetKnotPoint(int i);
void SetKnotPoint(int i, Point3& p);
ShapeObjects
ShapeObjects are now renderable. In order to accomplish this, they are now subclassed off of GeomObject rather than Object, as they were in previous versions. They are still SHAPE_CLASS_ID objects, though.
This has introduced a couple of important ramifications. See the remarks at the top of Class ShapeObject for the details.
PatchMesh
The PatchMesh class has been updated so that it can contain multiple texture mapping channels, like the Mesh class. Unlike the Mesh class, however, the second texture mapping channel is stored in a new, second array member of the PatchMesh members, which are now defined as:
int numTVerts[PATCH_TEXTURE_CHANNELS];
UVVert *tVerts[PATCH_TEXTURE_CHANNELS];
TVPatch *tvPatches[PATCH_TEXTURE_CHANNELS];
At present, PATCH_TEXTURE_CHANNELS is defined as 2, specifying the two texture mapping channels available. This could be extended in future versions, so keep this in mind.
Operations remapping texture patch information should perform the operations on all texture channels. See the file \MAXSDK\SAMPLES\MODIFIERS\EDITPAT.CPP for examples.
All texture-mapping-related methods within the PatchMesh class have been adapted to deal with the new mapping channel. The old methods all access channel 0, to retain backwards compatibility. To access channels other than zero, new methods with "Channel" at the end of their names have been added:
NURBS
The original 3ds max 1.x NURBS classes have been replaced in the SDK. Developers can get an overview of the new NURBS API by reading the section Working with NURBS.
Particle Systems
Several new methods have been added to class ParticleObject in 3ds max 2.0. These methods have default implementations but in order for the particle system to participate in Motion Blur when rendering these methods need to be implemented. These methods are: ParticlePosition(), ParticleVelocity(), ParticleSize(), ParticleCenter(), ParticleAge(), and ParticleLife(). See Class ParticleObject for details.
Also, the methods CollisionObject::CheckCollision() and ForceField::Force() both have a new parameter (int index), which is the index of the particle being forced or collided. See Class CollisionObject and Class ForceField.
Plug-Ins With an Interface in Track View
Plug-Ins that provide a user interface in Track View themselves will need to implement several new methods of Class Animatable. These methods are all marked ' This method is available in release 2.0 and later only', and the methods themselves discuss the changes.
Track View has changed a bit. It now remembers the open/close state of the hierarchy as well as the selected/deselected state of the animatables. This information is saved with each Animatalbe in the data members maintained by the class:
DWORD tvflags1, tvflags2;
For example, several methods now have an additional parameter that specifies which Track View the method deals with.
There are other new methods that deal with the increased copy/paste functionality in R2 as well.
Custom Creation Plug-Ins
In 3ds max 2.0 when a new object is created, if the hide by category flag for that object type is set to hidden, it is reset to visible before the object is created. In order to unhide a category (object, lights, particle systems, etc.) when an entity of that type is created, code was added to the creation routines of entities that don't use the default creation methods.
Making the required changes is simple. Just add the following statement during the first mouse down during creation:
GetCOREInterface()->SetHideByCategoryFlags(
GetCOREInterface()->GetHideByCategoryFlags() & ~HIDE_X));
Where, _X is _OBJECTS, _LIGHTS, etc. (or ~(HIDE_OBJECTS|HIDE_PARTICLES) for particle systems.
Anyone moving custom-creation plug-ins from 3ds max 1.x to 2.x should add this functionality.
Scaling Parameter Values
There is a new virtual method of ReferenceMaker named RescaleWorldUnits(float f). Its purpose is principally to allow conversion of units between different unit scales (for instance centimeters to inches) when merging files.
What it is meant to do is to multiply any parameter that is expressed in world units by the scale factor f. This applies to things like a sphere's radius, a cylinder's radius and height, a control point position, etc.
To make this method work everywhere in 3ds max is going to require all plug-in developers to make sure it is implemented for their classes. In many cases the default implementations will work without change.
To detect if an object (or modifier) is rescaling correctly is simple:
(1) Create the object and create a camera looking at it.
(2) Render the camera view of the object.
(3) In the Utility panel, bring up Rescale World Units utility, click "Rescale..." and set a scale factor to say 10. The "Affect" should be set to "Scene". Click OK. This will call RescaleWorldUnits(10.0) on the entire scene.
(4) The camera view should have remained unchanged. Render it again to check.
Developers should check if your object/modifiers/space warp/whatever is rescaling correctly. If not, follow the steps below to make them do so.
The default ReferenceMaker implementation of RescalWorldUnits() simply recursively calls RescaleWorldUnits() on the sub-references. (To avoid rescaling multiple instances more than once, a flag A_WORK1 is cleared before the whole process begins and is used to flag entities that have been rescaled.)
void ReferenceMaker::RescaleWorldUnits(float f) {
// This code should appear at the beginning of any
// RescaleWorldUnits implementation:
if (TestAFlag(A_WORK1))
return;
SetAFlag(A_WORK1);
// This code will be replaced in particular implementations
for (int i=0; i<NumRefs(); i++) {
ReferenceMaker *srm = GetReference(i);
if (srm)
srm->RescaleWorldUnits(f);
}
}
The basic controllers have the method RescaleWorldUnits() implemented.
Parameter blocks also have an implementation of RescaleWorldUnits() that will only rescale parameters for which the Param Dimension is stdWorldDim. (This is the value returned by NotifyRefChanged() in response to the REFMSG_GET_PARAM_DIM message.) So if an object's parameters are all managed by a parameter block, making sure that stdWorldDim is returned only for those parameters which represent world units is all that need to be done.
If some parameters are not handled by the parameter block, or there are some sub-references that you know need not be rescaled, you can implement RescaleWorldUnits().
In certain cases, the default implementation for ParamBlock::RedrawWorldUnits() is not sufficient, or there may be conflict between your use of stdWorldDim and world unit rescaling. In this case, you will need to implement your object's RescaleWorldUnits() and use the method:
void IParamBlk::RescaleParam(int paramNum, float f)=0;
to rescale world unit parameters one at a time. For example, here is the RescaleWorldUnits() for the Fog atmosphere:
void FogAtmos::RescaleWorldUnits(float f) {
if (TestAFlag(A_WORK1))
return;
SetAFlag(A_WORK1);
pblock->RescaleParam(PB_TOP,f);
pblock->RescaleParam(PB_BOTTOM,f);
}
In general it's pretty simple to get things scaling correctly. There can be some easy-to-miss subtleties, however. For instance, during 3ds max development there appeared some anomalies in the shadows of scenes scaled by a large factor, and the fix involved scaling the shadow bias distance, which was overlooked.
It's very important for plug-in developers to get this working across all entities so all developers should be careful in implementing this concept.
Import / Export Plug-Ins
A change was made internally in 3ds max 2.0 that causes a bug in Export plug-ins. This bug will be addressed in a maintainance release, but for now, developers will need to make the following change in order for their description string to appear properly. Usually, this string is retrieved from a string table as in:
const TCHAR * AsciiExp::Ext(int n)
{
switch(n) {
case 0:
return GetString(IDS_EXTENSION1);
}
return _T("");
}
This will be a problem in 3ds max 2.0 due to an internal change in the ImpExp code. The corrected code should just return the literal string, as in:
const TCHAR * AsciiExp::Ext(int n)
{
switch(n) {
case 0:
return _T("ASE");
}
return _T("");
}
Developers should also be aware of one other change to these plug-in types. There is a new parameter to the SceneImport::DoImport() and SceneExport::DoExport() methods. This is suppressPrompts. Developers of Import / Export plug-in may wish to respect this parameter to allow other 3ds max developers to use their plug-in for file IO (using Interface::ImportFromFile() and ExportToFile()). See Class SceneImport and Class SceneExport for details.