Reading a 3ds Max file without 3ds Max

The .max format has no specification, no SDK you can use from a browser, and a reputation for being impossible. It is a compound file full of chunks, and it can be read directly. Here is how.

There is no specification for .max. Autodesk ships an SDK that only builds against 3ds Max itself, on Windows, with a licence. Every converter that claims to read .max either drives a copy of 3ds Max on a server or asks you to export FBX first. The format has a reputation for being impossible.

It is not impossible. It is undocumented, which is a different thing.

It is a compound document

A .max file is a Microsoft compound document, the same OLE2 container as a .doc, which Microsoft publishes as MS-CFB. Inside are a handful of streams, and the ones that matter are Scene, ClassData and ClassDirectory3. So the first layer is free: implement the FAT, the DIFAT, the mini-stream and the directory tree once and you are inside.

A detail worth flagging, because it cost real time: the directory is a red-black tree, and a storage's children hang off a child pointer while its siblings are a separate left/right walk. If you share one visited set between the sibling walk and the recursion into child storages, the sibling walk marks a storage before its own children are collected and the whole tree silently flattens to its top level. Nothing errors. You just get fewer streams than the file has, and it only shows up when you compare against a file whose contents you already know.

Chunks all the way down

The Scene stream is a tree of chunks. Each is:

u16 type
i32 size          high bit set: this chunk contains other chunks
                  size includes the six-byte header
                  size == 0: a 64-bit length follows instead

That is the entire framing. The hard part is not parsing it, it is knowing what the type numbers mean, and they are not globally meaningful: a chunk's identity depends on the class of the object it belongs to. That is what ClassDirectory3 is for. It maps the class index used in the scene to a 64-bit class id, and those ids are the stable identifiers the SDK documents.

A few that carry most of the work:

0x00000000e44f10b3   Editable Mesh
0x192f60981bf8338d   Editable Poly
0x000000005d21369a   PolyMeshObject
0x7034695c37bf3f2f   VRayMtl
0x448931dd70be6506   CoronaMtl
0xdeadc0013d6b1cec   Physical Material
0x0200               Multi/Sub-Object
0x00002005           PRS controller

Objects refer to each other through reference slots, which appear in two shapes: chunk 0x2034 is a dense array of references and 0x2035 is a sparse map. A node points at its object, its transform controller and its material through those slots, so walking them is what turns a pile of chunks into a scene.

Editable Poly is not Editable Mesh

The two mesh types store their faces differently and their UVs very differently, and a reader that handles one and guesses at the other produces geometry with scrambled texture coordinates. Editable Mesh keeps a separate texture-vertex array with its own face indices. Editable Poly keeps map channels, each with its own vertex array and its own per-face indices, and a polygon can have any number of sides, so it has to be triangulated on the way out. Both were worth implementing properly rather than approximating.

Materials, and what PBR does to them

3ds Max scenes in the wild are mostly V-Ray or Corona. Neither is a PBR material in the glTF sense, and mapping them is a judgement call rather than a lookup. V-Ray gives you a diffuse colour, a reflection colour, a glossiness, an IOR, a refraction colour and a self-illumination colour. Glossiness inverts to roughness. Reflection colour, near-neutral and bright, usually means metal. Refraction colour that is not black means transmission. None of that is exact, and the alternative is discarding the material entirely, which is worse.

Multi/Sub-Object materials need care. They address their slots by material id, which is one-based in the interface and can skip numbers, so a face whose id points past the end of the list has to be clamped rather than dropped. Drop it and holes appear in the model.

Checking it

Autodesk publishes a sample scene, MiniHouse.max, with its Forge samples. It reads as 27 named objects, 2,568 triangles and 7 materials, four of them Multi/Sub-Object. The walls sit on z = 0 and the roof starts exactly where they stop, which is the kind of check worth doing: a reader that has the transforms wrong produces a plausible triangle count and a building in pieces.

Every object matrix comes out orthonormal, which is another cheap check. A transform that has picked up shear is a sign the reader has misread the controller.

What it does not do

No animation. Max stores it as controller keyframes per track, and there are dozens of controller types. No modifier stack evaluation: what you get is the mesh as cached in the file, so a TurboSmooth that was never collapsed is not applied. No particle systems, no scene lighting, no biped rigs. For a converter, geometry and materials are the job.

Textures are referenced by the path they had on the artist's machine, which will not exist on yours. Dropping the image files in alongside the .max resolves them by filename, which is the only sane approach in a browser where there is no filesystem to search.

It runs in a tab: MAX to GLB, MAX to FBX, MAX to OBJ and MAX to USDZ all work without a licence, a server or an upload.

Sources

Primary references for the claims above. Where a specification exists, it is cited in preference to anybody's summary of it.

  1. [MS-CFB]: Compound File Binary File Format, Microsoft Open Specifications
    A .max file is a compound document; the chunk tree lives in its streams.
  2. 3ds Max SDK documentation, Autodesk
    Class identifiers and the meaning of the reference slots that link objects to their modifiers.
  3. Blender importer discussion and prior art, Blender developer forum
    Where most of the community knowledge about the chunk layout has been argued out.
  4. V-Ray for 3ds Max documentation, Chaos
    Parameter names and ranges for VRayMtl, needed to map a V-Ray material onto PBR.

Formats in this post

More from the blog