AniMax Program Notes
V0.31 (testing)
16-Sep-2007
W. Wesley Howe

OVERVIEW:

AniMax is a program suite for Sims2 animation work. There are 4 components planned at this time, but only three are implemented. Those are the animation disassembler (AnDis.exe) the animation compiler (AniComp.exe) and the MilkShape plugin animation frame importer (msImpAnimFrames.dll).

The design objective is to be able to decompose Sims2 animation files into a readable and editable text file format, and to then recompose the (possibly altered) text files back into game ready animation files. Additionally, tools have been planned to move some of the skeletal data into and out of MilkShape.

In practice, because MilkShape does not support IK animation, and also because the Sims2 ANIM format allows frames to move as little as one axis on a bone, while MilkShape always specifies all three axes, much if the animation data from the game files for Sims does not flow into MilkShape. Typically, head, spine and shoulders may be importable, but in most files IK animation was used for the arms and legs, and when these do not import, the body just sort of wiggles around. Humorous, but not highly useful. Object animation fare much better. While there may be IK parts used (especially targets for the Sims)I have not seen any IK used on the objects themselves.

Now, none of these limitations mean the tool is useless. In the disassembly layout, the skeletal parts are usually isolated into one of several sections. The other sections deal with face and teeth and other animation movements. Because there is an Animation Exporter plugin (AniMesh) available, it is possible to disassemble a sims animation, create some new skeletal animation parts, disassemble that, replace the skeletal animation parts using a text editor, and recompose the new, altered animation.

SETUP:

Some of the components are MilkShape plugins. These are to be placed in the MilkShape program directory (usually located in C:\Program Files). If you have trouble, find or make a MilkShape desktop icon, right click on it, pick properties and look in the box labelled "Target:" to find where the program is. Put the files that end in ".dll" there.

The other files are standalone executable programs (.exe files). While these can all be run from a command window, the way I designed them allows them to readily be used from the Windows Explorer.

A discussion on file associations.
The program AnDis.exe (the dsassembler) is used on files of type .5an (extracted from SimPE).
The program AniComp.exe (the compiler) is used on files of type .anmctl, generated by AnDis.exe. .anmctl files would always have one or more files of type .anmdat referenced, again these are created by AnDis.exe (and possibly modified by you).


TEXT:

A key component of making the animation import work correctly is to have a properly matched skeleton and mesh with joint assignments. If these two components differ from what was used when the animation was created, it will not work right (without repairs).

The skeleton obtained from the UniMesh importer when the mesh is imported is what you need for body meshes. For object meshes, you need to extract the Resource Node and use the Skel Tool to replace all the joints from what was imported.

When you have a proper model built, save it as a MilkShape project.

Now, to import an animation. First, you use SimPe to extract the animation file onto your harddrive. Using Windows Explorer you locate this file and right-click on it and pick open with (or open if that is all that shows up). You the "pick the program from a list" and then would need to browse to find the AnDis program, and use that to open the .5an file. If you associate the .5an with AnDis by leaving the "always use this" box checked, in the future you will just be able to double-click, otherwise you will be able to use "open with".

When AnDis finishes, there will be one or more files in the same directory with the same first part of the name in the .5an file, but with "-apXX.anmdat" [XX are digits] as the last parts of the file name. If the animation had 5 parts in it, the XX above would be digits 00 to 04. Additional parts may be present for the extension (P5 and P6) and the Events (P7) data.

Only the first part generally contains the skeletal animation. The other parts contain face animation and such. All of these are readable text files.

To finish, you open your saved MilkShape model and using File/Import run the Frame Importer plugin. It should say how many frames were imported. Then, if I did things right, you should be able to select the ANIM button and play your animation. In theory you could then export and place this right in the game.

Besides working with this data in MilkShape, it would be easily possible to transplant parts of one animation, say some an arm wave movement, and put it in another, say, walking animation and make a wave while walking animation.


//=================================================================================//
INTERMEDIATE FILE FORMAT:

The intermediate file format is a set of ascii text files designed to be able to be compiled in a single read. As such, like many programming manguages, items are defined before use. The count of how many items is given before the items are presented. And many aspects are rigidly defined to reduce the likelyhood of manual omission errors.

It is also important to understand some about the animations themselves, because every element was designed to allow some aspect of the file format to be replicated.

The main file, the one the compiler will read first, is built from the input file name (less the .5an part) with a file extension of ".anmctl", for animation control. This contains the header data items, as well as the count and filenames of the various sub-parts.

All lines beginning with the '#' (hash) character, like those below, are comment-only lines, and are ignored by the compiler. These first two are informational and should be self-explanatory:

# AniMax TS2 AnDis V0.25 - by Wesley Howe
# Disassembly of drivetrain.5an

The next two lines are essential for future expansion:

FileFormat: TS2
FileVersion: 101

Then the header, which is an RCOL data element. Because this is generally an invariant part of the file (for the anim dataset), it is read and stored as a byte count, and then a set of packed hex bytes. These can be edited, but should not ever need to be:

HeaderBytes: 63
BYTEBLK16: 0100FFFF00000000010000001E7900FB
BYTEBLK16: 1263416E696D5265736F75726365436F
BYTEBLK16: 6E73741E7900FB060000000B63534752
BYTEBLK15: 65736F757263650000000002000000

The header name is the name of the animation, as it will be used by the game for linkage:

HeaderName: "o--computercheap-[echo-10.6.2007-16331]-drivetrain_anim"

The data length below is commented by the disassembler. It is presented for informational purposes, and represents how many bytes of data remain in the file. The compiler does not need to be told this, it keeps track and then sets the data filed in the output file to the proper value:

# Data Length: 0x000003F0

The TotalLength field is a time field, rather than bytes. It is how many milliseconds the entire animation should play over:

TotalLength: 9000

The AnimCount value is the number of animation parts that will be used. This will correspond with the "AnmPartCt:" value seen later:

AnimCount: 1

The EventCount value is the number of "events" (the P7 section) in the last part of the animation file:

EventCount: 0

The ObjectModifierLength is the number of characters that are in the ObjectModString itself, which is some 16 or so elements further on. The intermediate format closely follows the binary format, and retains the general ordering of all the parts;

ObjectModifierLength: 0

This Version values is that used by the game itself:

Version: 6

The AnimationType value appears to be used more for classification purposes than for use at runtime:

AnimationType: 5

The purpose of the next byte is not known, but appears to allow the compiler to place the correct value, and to enable this value to be changed in case someone figures out what it is for:

UnknownByte3: 2

The LocomotionType appears to be another classification item:

LocomotionType: 0

The ObjectNameLength is the number of characters expected in the ObjectName string, which is still some number of items away:

ObjectNameLength: 6

The HeadFloats are a set of floating point values, whose purpose is not definitively known. All told, there are nine values, and they are passed as found in the original file. The names are generated and parsed algorithmically, so they need to stay in the proper order, as shown:

HeadFloat0: 0.000000
HeadFloat1: 0.000000
HeadFloat2: 0.500000
HeadFloat3: 0.000000
HeadFloat4: 0.000000
HeadFloat5: 0.000000
HeadFloat6: 0.000000
HeadFloat7: 1.000000
HeadFloat8: 1.000000

The ObjectName is usually the name of the root bone of the mesh being animated. Remember, we saw the length for this previously:

ObjectName: "auskel"

In this file, since the ObjectModifierLength is zero, the line ofr the ObjectModString will not be expected by the compiler. Here, the disasssembler placed an empty string definition in a comment line as a placeholder and reminder for editing purposes:

# ObjectModString: ""

The next line was created by the disassembler as a visual section divider:

# -----------------------

Now we begin the animation parts. The first item is the number of animation parts, and should be the same as the item Count1:

AnmPartCt: 1

The tags on the next lines are generated algorithmically. If AnmPartCt was zero, none of them would be here. Then they come in sets, starting the count with "00". The first in teh set is a path and filename where all the animation frame and bone data will be found (to be documented further on):

AnmPart00: "C:\Documents and Settings\HP_Owner\My Documents\SimsMisc\AnimTest\AniMaxTest\FrameImpTests\drivetrain-ap00.anmdat"

While this may wrap, it is one long line. The next line is not readily apparent what this data item is for, but the disassembler and compiler will pass it along:

P100Word1: 2

The last line of the "set" is the number or "P5" sections associated with this AnmPart, plus 256. I am unsure why the 256 is there, but if not present the game will crash when it tries to load the animation. In this example, there are zero P5 parts. By example, a value of 258 would indicate there were two P5 parts. In the case of multiple sets, these will all add up to some total P5 part count:

P100NumP5: 256

So, if there is more than one section (Count1 and AnmPartCt are 2 or more) then the next line would be an "AnmPart01:" tagged filename line, with P101Word1: and P101NumP5: lines following it, until the number of parts specified has been used.

The number of Part5 elements needed is totalled from the P1XXNumP5: tagged lines. In this example, it is zero. If it is non-zero, a tagged line "Part5Name:" with a file path/name for a repository of all the necessary values will come afterwards. Regardless of how many P5 items there are, they are all contained in one file:

TotalPart5Ct: 0

Parts of each Part5 item is a value of how many Part6 items are associated with each Part5. Since the purpose of the Part5 items is undetermined, what the Part6 is for is also undetermined. Nonetheless, the number required is totalled up and presented. If it is non-zero, it will be followed by a "Part6Name:" tagged line with the filename:

TotalPart6Ct: 0

The final section is the events, or Part7. The general layout is like the P5 and P6, a count and possible a filename. But the TotalPart7Ct: value has to agree with the Count2 value used early on in the file:

TotalPart7Ct: 0

This last line is a comment, and is not needed by the compiler, but is created by the disassembler as a visual indicator that the file was not truncated:

# End of file


Now we will detail each of the subpart files. The first is the AnmPart files. The names are created algorithmically from the base name of the .anmctl file, with teh extension stripped and then "-ap00.anmdat" appended. The digits "00" will increment through each additional part. This is the main part of the actual animation data, and will contain the bone names, types and frame data for translations, rotations and morphs.

The first line is a comment form the disassembler, specifying the version number:

# AniMax TS2 AnDis V0.25 - by Wesley Howe

The next line is really a repeat of the TotalLength: item in the animation control file: 

TotalTime: 2441

The HighFrameCt: item is generated by the disassembler, and expected by the compiler, to allow for the proper allocation of space for frames. Not all animations need have the same number of keyframes for each bone, or even for each axis, so this is the largest number of frames that should be expected:

HighFrameCt: 23

The next one is the number of joint definitions that are in this animation part. The joints definitions may be either a "BoneDef:" type or a "JointData:" type, depending on the data definition needs.

P1JointCount: 51

The next two items are the length and the name for this section of the animation:

P1NameLength: 6
P1Name: "auskel"

Next come P1JointCount number of sets of data. There is a compact form for the bone and frames, tagged as "BoneDef:" when the type and frame count for all three axes are the same. First, and example of a BoneDef:

BoneDef: auskel 192 34768
FrameSetTypes: 1 1 1
FrameSetCt: 53
0 32768 0.000000 0.000000 0.000000

The first line would be one count element of the P1JointCount: in this file. The second item on the line is the bone name, the third is the joint animation method (translation, rotation or morphing) and the last item is a timecode with bit15 set.

The FrameSetTypes: tagged line has values obtained from the bone type and elements format (shorts per frame) formatted like it was in the original ANIM file. While there are three values per line, they should all be the same, or the joint would have been expressed as a JointData: type.

The FrameSetCount: is the number of keyframes that will be expected.

Then there will be FrameSetCount: lines of frame data. They are organized as a count (starting at zero) a timecode (which may have bit15 set) and three floating point numbers that are the translated XYZ animation values from the ANIM file (either parent relative movement or parent relaive rotation values). The original count is checked to make sure that accidental omissions did not occur in editing. In the above example section, there would have been 53 lines, numbered from 0 through 52, each with the relevant timecode (which would normally always be ascending, after masking with 0x7FFF) and three axes of movement data.

When the animation uses differing numbers of frames per axis or has a different number of data elements per axis, the disassembler uses the JointData: type to express the bone. An example forst:

JointData: r_foot_ikctr 192 34843
DataSetCount: 3
FrameSetType: 2
FrameItemCt: 13 4
0 75  55808  0  51770 

Other than the tag, the JointData: line is used like the BoneDef: line, and the values mean the same thing. However, the next line, DataSetCount: will have 1 to 3, indicating how many frame sets will be included. Some anim types, like morphs, have only one 'dimension', while joint that have all three may be moved or rotated on only one axis. The way the game engine operates is that any joint axis that hasn't been expressly moved will remain where it was.

The FrameSetType: is the same value that would have been used for the FrameSetTypes: in the BoneDef: section, except it only expresses the one current value for this axis.

The FrameItemCt: line has the number of frames and the number of data elements per frame on it. In the above example, there are 13 frames expected with four data elements per line.

The data elements line starts with a count, and in the above example these counts would range from 0 to 12. This is not one of the items per line listed in the FrameItemCt: tag line. In the above example, there are five numbers on each line, one line count value and four data elements. Data elements significance vary. IK-based animations may have one, with no timecode. A typical joint would have a timecode, like above, in the first data element slot (second number), usually another value in the second data element slot (third number) and any additional values are purpose unknown. Note that these are not floating point numbers, but instead raw values from teh anim file (range 0 to 65535, or 0x0000 to 0xFFFF).

In the above example, there would be two more sets of FrameSetType:, FrameItemCt: and data lines, because DataSetCount has a value of three. For example purposes, only the first set, and one data line were shown.

There is only a limited amount of information beyond what I listed in the above paragraphs on what happens when what values are changed.

The Part5 section contains data extracted from the disassembled file. Each AnimCount section will have specified some number using the P100NumP5: tag (the 00 digits may be larger in multisection files). This line is 256 plus the number of P5 sections needed.

Here is an example of lines from in the Part5 file:

TotalP5Ct: 2
P5Blk00ItemCt: 2
P5Blk00Word00: 0
P5Blk00P6Ct00: 1
BYTEBLK16: 005E9B78490059748DDE00417AEE4833
BYTEBLK16: C215A100C8236C930012FB89AF00F575
BYTEBLK16: 789A00FA43BDD0000000000000000000
BYTEBLK07: 00000000000000

The TotalP5Ct: should agree with the TotalPart5Ct: element in the .anmctl file, and also should agree with the sum of the values obtained from all the P100NumP5: tags (excluding the 256 from each value). The disassembler will keep these all straight, but if you add or delete any you will need to keep all these values aligned, or you will get a warning or error.

The tag P5Blk00ItemCt: is generated via code. There is one line like this for every element of AnimCount: and the two digits indicate that the P5 elements are associated with a certain index. The digit on the line (in this case 2) indicates how many P5 sets belong to this file, in this example it would have come from a line like P100NumP5: 258.

The purpose behind the value on the P5Blk00Word00: line is unknown. However, in this tag note there are two seperate sets of digits. The forst is associated with the AnimCount: section, while the second is the index for the P5Blk00ItemCt: being expressed.

The P5Blk00P6Ct00: line indicates how many P6 items will be associated with THIS P5 element. The total number of P6 items needed will come from summing all these values for each P5 element. Again, note that this has two counts, one for the AnimCount: index and one for the ItemCount index.

The lines beginning with BYTEBLK are an easily parseable hex dump of the data in the P5 element, for whatever purpose it may have.

If the AnimCount: line is greater than one, then there will need to be an item count line for each one. An example is below:

P5Blk01ItemCt: 0

In this line, associated with the second animation section (index 01), it shows there are no P5 elements (and would correspond with a P101NumP5: 256 line in the .anmctl file).

Overall, the Part6 file is simpler than P5, although you would have to be very careful to make sure the order stays aligned with the P5 that called for it, assuming that it someone ever figures out what it is for.

TotalP6Ct: 2
P6BlkData: 0
BYTEBLK16: 000000000000000000000000000000F2

The TotalP6Ct: should agree with the TotalPart6Ct: line in the .anmctl file and the sum of all the P5Blk00P6Ct00: line items. The reason for the repetition is to provide error checking in the case of hand editing files. If the .anmctl file is pointed at the wrong file, or the file content gets overwritten, the discrepancy in the count will catch some of the error conditions, especially where there is too many or too few elements.

The P6BlkData: line has a single value on it, which tracks the block index (count starting from zero), and then the BYTEBLK lines express the data in the P6 block. P6BlkData: and the associated BYTEBLK lines will be repeated until TotalP6Ct: items have been expressed.

The last file, Part7, contains the events associated with the animation. These are often sounds, but may also be other items. Example lines:

TotalP7Ct: 4
Event00Data: 841 1014 64
Event00Str: "sound_name=foot_shuffle\nsound_source=center_of_object\nbone_tag=\n"

The TotalP7Ct: should be the same as specified in the EventCount: line in the .anmctl file. And there needs to be that many sets of EventXXData: and EventXXStr: lines (XX is the index digits).

The EventData line elements are the timecode, event type and string length values. The compiler gives a warning when the string length in this line does not agree with the actual length of the EventStr string, but compiles the actual data size. Again, another edit check.

The EventStr string itself has quotes at both ends, and between them may have some unprintable characters, such as the newline 'escaped', that is, represented by a sequence of \ and a letter, \n for the linefeed, \t for tab, \q for an embedded double quote and \r for a return. When counting the numbe rof characters, the beginning and ending quotation marks are not counted, and an escaped character such as \n only counts as one character (because when compiled, that is what it converts to).


//================================================================================//
HISTORY:

Changes from V0.21 include revamping the timecode and frame layout and addtiions necessary to support the new compiler (AniComp.exe).

Changes from V0.26 include raising some structure limits and support for some quotes and newlines in parms. Also an optional third parameter allows AniComp to have a specified output filename, and when that happens any error strings get printed to stdout.

V0.31 (16-sep-2007) has minor changes in string escapes, keyword changes and more documentation. Keyword changes mean previously disassembled intermediate files will not compile without some edits.
