Thursday, October 6, 2011

Working with the 3DS Max SDK

I haven't had too much time to work on ProjectFPS specifically lately, due to work required by other projects. I did actually get around to writing a 3DS Max exporter for meshes and animations, however. I've taken the approach of writing the mesh data + vertex bone weights out to one file, with extension .msh, and skeleton data + animations out to another file, with extension .skl. 3DS Max, like a lot of 3D modeling programs, exports only one animation at a time, so I can't use the .skl file directly. Instead, I concatenate all animations for the skeleton into one, export that to .skl, and then put that file through an animation splitter I have to write, which will break it up into smaller animations and write it out to a separate file, which I will give extension .pfs. The mesh file, when loaded, will reference a .pfs skeleton that will be loaded along with it. This allows me to use different skins on the same skeleton, and to only have to do the animation splitting process once for every .skl file I create. Making assets isn't really my thing, so having a system like this is a big boon.

Now for a bit of a rant, if I may.

Once I figured it out (with a lot of help from video tutorials) 3DS Max itself was easy enough to use to create a rigged test mesh. The SDK, however, was much more painful to work with. It seemed like I had to grapple with it to get the data I needed. I guess, to an extent, that's unavoidable when working with a system that's written to support anything under the sun (the feature set, level of abstraction, and modularity is mind-bogglingly extensive). So it's natural that my needs are very specific, and represent only a small subset of what 3DS Max needs to do. However, considering how many games require writing an exporter (if you're writing your own asset pipeline, you HAVE to write an exporter for your modeler of choice), and the establishment of 3DS Max as an industry standard for game 3D modeling, I would expect more effort to be put into supporting developers who have to write them.

The main issue I had was lack of documentation. Yes, every function declaration was commented, but I would read the description for a function and still have no idea what it did. I punch some of the words I didn't understand into the documentation. Sometimes I'd get results, sometimes I'd just get reverted back to the C++ file-generated documentation. When I did get results, I often didn't understand what THAT page was talking about, either, or it didn't contain information relevant to my purposes. So I'm being lead on a wild goose chase all over the documentation just to find out what the heck that one function even did. My Google-fu also failed me in this regard, which puzzles me; you'd think that help would be all over the internet for something like this.

Then there were the gotchas and surprises. Surprises are never good when you're trying to work with an API. Often times I would write a function only to find that the API worked completely differently than I expected, and then I had to significantly rework my exporter to accommodate (I'm looking at you, keyframe exporting). Other times, it did things that I didn't expect with my data (I was stumped as to why my mesh, which consisted of two boxes, had so many faces and vertices until I found out it was converting my bones to triangle meshes too).

Then the random crashes and unexpected behavior. My program kept crashing whenever I tried to allocate memory, and crashed during random fwrite calls (which I later found allocate memory). The program cited heap corruption, but nothing else. Turns out the problem is that when building in Debug, you're using the debug C library while 3DS Max still uses the release version; these two allocate and free memory differently. The solution is to use a special Hybrid build, which uses the release library. The only reason I know that is because it was in a PDF found through some very lucky Google-fu, and that PDF was not written by Autodesk. For something as critical and hard to trace down as this, the SDK should have made this gotcha abundantly clear to me, but it didn't. Working with the Win32 interface in 3DS Max is also causing all sorts of problems for me, and to this day I have not been able to fix them. My money's on the existence of a similar gotcha. Sure, all the stuff in this and the previous paragraph make sense now, but to the programmer just getting familiar with the API, these kinds of things need to be properly explained.

The main saving grace I had in this was the two DevTV: Introduction to 3DS Max Programming videos. These videos walked you through the steps of creating a basic ASCII exporter. These were invaluable in the creation of my exporter, and I don't think I would have been able to do it without them. My final exporter design closely mimics the one in these videos. They walk you through step-by-step the extraction of the needed data, the concepts underlying the SDK, and any associated gotchas. The problems I had with coding my exporter started only once I wanted to do things the ASCII one didn't. I think the SDK's usability would benefit greatly from a larger pool of well-documented example plugins. XNA took this approach with its documentation, and it shows - it's very simple to figure out and adapt to your specific needs. The SDK's samples folder does contain a more comprehensive ASCII exporter than the one in the videos, but it's sparsely documented and hard to follow.

In short, the SDK is too hard to figure out for me to consider working extensively with it. I don't think the problem is due to bad design decisions or hack-and-slashed functionality; far from it. I simply feel that the developer is left hanging to figure things out on his own far too often.

1 comment: