Katana SDK
Dreamcast SDK
Katana SDK (Dreamcast SDK for SEGA Library) was the official SEGA software development kit that came with Dreamcast development units. The Dreamcast SDK had a number of libraries and APIs that many developers used in their games. The SDK evolved together with the console, and earlier versions of the SDK had fewer features or targeted different hardware. Updates to the SDK continued after the Dreamcast's release, and some releases were as late as 2001.
The Dreamcast SDK consisted of several subsystems:
- The Shinobi library set interfaced with the CPU, cache and memory.
- The Kamui API (low level) and the Ninja library (high level) provided access to graphics.
- The Manatee and Audio64 subsystems interfaced with sound hardware.
Over the years there have been multiple SDK leaks, including parts of source code for older revisions. For modding purposes, we should focus on the Ninja library, which is used by the Sonic Adventure games.
Common Types
The following types are used in Shinobi and Ninja headers:
| Ninja type | C type | Size | Description |
|---|---|---|---|
| Sint8 | char | 1 | Signed 8-bit integer |
| Uint8 | unsigned char | 1 | Unsigned 8-bit integer |
| Sint16 | __int16 | 2 | Signed 16-bit integer |
| Uint16 | unsigned __int16 | 2 | Unsigned 16-bit integer |
| Sint32 | int | 4 | Signed 32-bit integer |
| Uint32 | unsigned int | 4 | Unsigned 32-bit integer |
| Bool | unsigned int | 4 | Unsigned 32-bit integer that is either TRUE (1) or FALSE (0) |
| Float and Float32 | float | 4 | 32-bit floating point |
| Float64 | double | 8 | 64-bit floating point |
| Void | void | 4 | Pointer |
Ninja Model Formats
The Ninja library can use two model formats, the Basic model and the Chunk model. Basic models were introduced early on, while Chunk models were added later as a format that is optimized for better performance on Dreamcast hardware. The Basic format is easier to read as text.
Ninja Basic Model
The Basic model consists of the following structs:
Material struct (NJS_MATERIAL) and Material Array
The material affects how the model's surface looks. It defines which texture to use, whether to use environment mapping etc.
| Field | Offset | Type | Size | Description | Notes |
|---|---|---|---|---|---|
| diffuse | 0 | NJS_COLOR | 4 | Diffuse color. | In SA1 with palette lighting, only alpha is used. |
| specular | 0x4 | NJS_COLOR | 4 | Specular color. | In SA1 with palette lighting, only alpha is used. The alpha value of 0 is treated as the "ignore specular" flag. |
| exponent | 0x8 | Float | 4 | Strength of specular light. | Can range from 0 to 300. Usage of it in Sonic Adventure games is not confirmed. |
| attr_texId | 0xC | Uint32 | 4 | Texture ID. | |
| attrflags | 0x10 | Uint32 | 4 | Material flags. | Bits 31-29 and 28-26 are used for source and destination alpha blending instructions respectively. |
Size: 20 bytes (0x14).
NJS_COLOR is a union that can be either NJS_BGRA, NJS_TEX or Uint32 (4 bytes).
NJS_BGRAis a 4-byte struct that consists of blue, green, red and alpha values ranging from 0 to 255. For example, the color A255 R178 G178 B178 would be 0xFFB2B2B2.
NJS_TEX (4 bytes) is a struct commonly used in UVs. It consists of two Sint16 values: one for U (horizontal) and one for V (vertical). UVs used in SA1 range from 0 to 255.
The following flags can be used in attrflags:
| Flag name | Flag bit | Description | Comments |
|---|---|---|---|
| NJD_FLAG_IGNORE_LIGHT | BIT_25 | Ignore lighting. | |
| NJD_FLAG_USE_FLAT | BIT_24 | Flat lighting. | Has no effect when palette-based lighting in SA1 is used. |
| NJD_FLAG_DOUBLE_SIDE | BIT_23 | Double-sided mesh. | Also used for collision. |
| NJD_FLAG_USE_ENV | BIT_22 | Enable environment mapping. | |
| NJD_FLAG_USE_TEXTURE | BIT_21 | Enable texture. | |
| NJD_FLAG_USE_ALPHA | BIT_20 | Enable transparency. | |
| NJD_FLAG_IGNORE_SPECULAR | BIT_19 | Disable specular lighting. | Used for specular palette selection in palette-based lighting in SA1. |
| NJD_FLAG_FLIP_U | BIT_18 | Flip Us. | |
| NJD_FLAG_FLIP_V | BIT_17 | Flip Vs. | |
| NJD_FLAG_CLAMP_U | BIT_16 | Clamp Us. | |
| NJD_FLAG_CLAMP_V | BIT_15 | Clamp Vs. | |
| NJD_FLAG_USE_ANISOTROPIC | BIT_12 | Enable anisotropic filtering, | Unused. |
| NJD_FLAG_PICK | BIT_7 | "Pick status" | Unused. |
Alpha modes can be as follows:
Source alpha
|
Destination alpha
|
|---|
Basic models' materials are stored in arrays of NJS_MATERIAL.
NJS_MATERIAL matlist_004451A8[] = {
{ { 0xFFFFFFFF }, { 0xFFFFFFFF }, 11, 14, NJD_D_100 | NJD_FILTER_BILINEAR | NJD_FLAG_CLAMP_V | NJD_FLAG_CLAMP_U | NJD_FLAG_IGNORE_SPECULAR | NJD_FLAG_USE_TEXTURE | NJD_DA_INV_SRC | NJD_SA_SRC },
{ { 0xFFB2B2B2 }, { 0xFFFFFFFF }, 11, 13, NJD_D_100 | NJD_FILTER_BILINEAR | NJD_FLAG_CLAMP_V | NJD_FLAG_CLAMP_U | NJD_FLAG_IGNORE_SPECULAR | NJD_FLAG_USE_ALPHA | NJD_FLAG_USE_TEXTURE | NJD_DA_INV_SRC | NJD_SA_SRC },
{ { 0xFFB2B2B2 }, { 0xFFFFFFFF }, 11, 15, NJD_D_100 | NJD_FILTER_BILINEAR | NJD_FLAG_CLAMP_V | NJD_FLAG_CLAMP_U | NJD_FLAG_IGNORE_SPECULAR | NJD_FLAG_USE_TEXTURE | NJD_DA_INV_SRC | NJD_SA_SRC }
};
Poly Array
The poly array in an array of Sint16 that lists the model's primitives (polygons). It is commonly formatted with the number of vertices (points) followed by vertex indices. The indices are used to pick the specific vertices from the model's vertices array.
Here is an example of a polys array. The 4 in each new line indicates the number of vertices in the surface, and the following four values are vertex indices.
Sint16 poly_0000002C[] = {
4, 13, 12, 5, 4,
4, 9, 8, 1, 0,
4, 11, 10, 3, 2,
4, 15, 14, 7, 6
};
The direction of listing vertices can be clockwise or counter-clockwise. To use the clockwise winding direction, add the 0x8000 flag to the number of vertices:
Sint16 poly_00000044[] = {
0x8000u | 5, 2, 1, 0, 7, 6,
// And so on...
};
UV Array
The UV array is an array of NJS_TEX. UVs determine how the texture is applied to the model's surface. For exampled, bigger UV values will make the texture "zoomed in", adding values to U will make the texture offset horizontally etc. UVs are per-polygon.
NJS_TEX uv_000003A4[] = {
{ 130, 145 },
{ 212, 225 },
{ 124, 230 },
// And so on...
};
Vertex Color Array
This is an array of NJS_COLOR that determines the colors of the model's vertices. Vertex colors are used per-polygon, not per-vertex.
NJS_COLOR vcolor_0000003C[] = {
{ 0xFFFFFFDF },
{ 0xFFFFFFFF },
{ 0xFFFFDFBF },
{ 0xFFFFFFFF },
{ 0xFFFFDFBF },
// And so on...
};
Note: In SA1 Dreamcast, vertex colors are ignored and the pointer to the vertex colors array is null in all models.
Polynormal Array
Normals determine the model's lighting and environment mapping distortion. Normals can be specified per-vertex or por-polygon. Almost no models in SA1 use per-poly normals (polynormals), with the exception of Tails' tails and Sonic's stretchy shoes. When polynormals are used, this is the array they are expected to be in.
Normals and polynormals are defined as arrays of NJS_VECTOR. NJS_VECTOR consists of three floats for X, Y and Z.
NJS_VECTOR polynormal_007383D8[] = {
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 }
};
Polygon Attribute Array
This is an array of Uint32 containing polygon-specific attributes. It is not used in Sonic Adventure games.
Meshset Struct (NJS_MESHSET) and Meshset Array
Multiple connected polygons form meshes. The NJS_MESHSET struct defines a single mesh.
| Field | Offset | Type | Size | Description | Notes |
|---|---|---|---|---|---|
| type_matId | 0 | Uint16 | 2 | Diffuse color. | Bits 0-13 are material id (0-4095) in the material array.
Bits 14-15 are meshset type bits (see below). |
| nbMesh | 0x2 | Uint16 | 2 | Number of meshes. | |
| *meshes | 0x4 | Sint16* | 4 | Pointer to the poly array. | |
| *attrs | 0x8 | Uint32* | 4 | Pointer to the poly attribute array. | Unused in SA games. |
| *normals | 0xC | NJS_VECTOR* | 4 | Pointer to the polynormal array. | Almost never used. |
| *vertcolor | 0x10 | NJS_COLOR* | 4 | Pointer to the vertex color array. | Unused in SA1 DC, used in SADX. |
| *vertuv | 0x14 | NJS_TEX* | 4 | Pointer to the UV array. | |
| buffer | 0x18 | void | 4 | Pointer to the Direct3D mesh buffer. | Always null.
SADX PC 2004, SADX X360 and SADX PS3 only. |
In the 2004 PC port of SADX this struct contains an extra field for the mesh buffer (buffer). This version of the struct is used in the PC, X360 and PS3 versions.
SA2 and Gamecube versions of SADX use the original struct without the extra field.
In SADX Mod Loader headers the original Ninja version is NJS_MESHSET, and the one with the extra field is NJS_MESHSET_SADX.
In the SADX X360 prototype with symbols, the one with the extra field is NJS_MESHSET, and the original Ninja version is NJS_MESHSET_OLD.
The size of the original NJS_MESHSET is 24 (0x18) bytes, and the size of NJS_MESHSET_SADX is 28 (0x1C) bytes.
Meshset type bits are as follows:
| Meshset type | Value | Description |
|---|---|---|
| NJD_MESHSET_3 | 0x0000 | Triangle. |
| NJD_MESHSET_4 | 0x4000 | Quad (four vertices). |
| NJD_MESHSET_N | 0x8000 | N-Gon (custom number of vertices). |
| NJD_MESHSET_TRIMESH | 0xC000 | Trimesh (strips of connected triangles), the most common meshset type. |
| NJS_MESHSET_MASK | 0xC000 | Meshset type mask. |
To get the material ID from the type_matId field in your code, use type_matId & ~NJD_MESHSET_MASK. To get the mesh type, use type_matId >> 0xE.
Meshes in Basic models are stored in arrays of NJS_MESHSET:
NJS_MESHSET_SADX meshlist_00000294[] = {
{ NJD_MESHSET_TRIMESH | 0, 9, poly_00000044, NULL, NULL, NULL, uv_00000128, NULL },
{ NJD_MESHSET_TRIMESH | 1, 10, poly_000000A4, NULL, NULL, NULL, uv_000001C4, NULL },
{ NJD_MESHSET_TRIMESH | 2, 3, poly_00000108, NULL, NULL, NULL, uv_00000264, NULL }
};
Vertex Array
The model's vertices are in an array of NJS_POINT3 (NJS_VECTOR in Mod Loader headers).
NJS_VECTOR vertex_00000ADC[] = {
{ 0, 26.04398f, 0 },
{ 3.602116f, 18.50802f, 0 },
{ 1.801057f, 18.50802f, -3.119523f },
{ -1.801059f, 18.50802f, -3.119523f },
// And so on...
};
Normal Array
The model's normals are in an array of NJS_VECTOR.
NJS_VECTOR normal_00000D28[] = {
{ 0, 1, 0 },
{ 0.902229f, 0.431257f, 0 },
{ 0.451115f, 0.431256f, -0.781354f },
{ -0.451115f, 0.431256f, -0.781354f },
// And so on...
}
Model Struct (NJS_MODEL)
The Model struct combines all the things mentioned above and adds model center coordinates and radius.
| Field | Offset | Type | Size | Description | Notes |
|---|---|---|---|---|---|
| *points | 0 | NJS_POINT3* | 4 | Pointer to the vertex array. | |
| *normals | 0x4 | NJS_VECTOR* | 4 | Pointer to the normal array. | |
| nbPoint | 0x8 | Sint32 | 4 | Number of vertices. | |
| *meshsets | 0xC | NJS_MESHSET* | 4 | Pointer to the meshset array. | |
| *mats | 0x10 | NJS_MATERIAL* | 4 | Pointer to the material array. | |
| nbMeshset | 0x14 | Uint16 | 2 | Number of meshsets. | |
| nbMat | 0x16 | Uint16 | 2 | Number of materials. | |
| center | 0x18 | NJS_POINT3 | 12 | Model center. | |
| r | 0x24 | Float | 4 | Model radius. | |
| *buffer | 0x28 | void | 4 | Pointer to the Direct3D mesh buffer. | Always null.
SADX PC 2004, SADX X360 and SADX PS3 only. |
In the 2004 PC port of SADX this struct contains an extra field for the mesh buffer (buffer). This version of the struct is used in the PC, X360 and PS3 versions.
SA2 and Gamecube versions of SADX use the original struct without the extra field.
In SADX Mod Loader headers the original Ninja version is NJS_MODEL, and the one with the extra field is NJS_MODEL_SADX.
In the SADX X360 prototype with symbols, the one with the extra field is NJS_MODEL, and the original Ninja version is NJS_MODEL_OLD.
The size of the original NJS_MODEL is 40 (0x28) bytes, and the size of NJS_MODEL_SADX is 44 (0x2C) bytes.
In old modding terminology, the Model struct was referred to as the "attach" struct. You can still see this label in SA Tools' C exports.
Object Struct (NJS_OBJECT)
Ninja models use a node-like hierarchy, and the Object struct represents a single node. The size of NJS_OBJECT is 52 (0x34) bytes.
| Field | Offset | Type | Size | Description | Notes |
|---|---|---|---|---|---|
| evalflags | 0 | Uint32 | 4 | Model evaluation flags (see below). | |
| *model | 0x4 | NJS_MODEL* | 4 | Pointer to the NJS_MODEL.
|
|
| pos | 0x8 | Float[3] | 12 | Model position (X, Y, Z). | |
| ang | 0x14 | Angle[3] | 12 | Model rotation (X, Y, Z). | Angle is the same as Sint32. |
| scl | 0x20 | Float[3] | 12 | Model scale (X, Y, Z). | |
| *child | 0x2C | NJS_OBJECT* | 4 | Pointer to the child model in the hierarchy. | |
| *sibling | 0x30 | NJS_OBJECT* | 4 | Pointer to the sibling model in the hierarchy. |
The following evaluation flags can be used:
| Name | Flag bits | Description |
|---|---|---|
| NJD_EVAL_UNIT_POS | BIT_0 | Ignore the model's position. |
| NJD_EVAL_UNIT_ANG | BIT_1 | Ignore the model's rotation. |
| NJD_EVAL_UNIT_SCL | BIT_2 | Ignore the model's scale. |
| NJD_EVAL_HIDE | BIT_3 | Do not render the model. |
| NJD_EVAL_BREAK | BIT_4 | Node is the end of the hierarchy (do not check children). |
| NJD_EVAL_ZXY_ANG | BIT_5 | Inverted order of rotation. |
| NJD_EVAL_SKIP | BIT_6 | Do not animate (ignore) this node. |
| NJD_EVAL_SHAPE_SKIP | BIT_7 | Do not animate (ignore) this node in shape animations. |
| NJD_EVAL_CLIP | BIT_8 | If this node is clipped, do not clip child nodes. |
| NJD_EVAL_MODIFIER | BIT_9 | The node has a modifier volume. |
Ninja Chunk Model
The chunk model format is a sequence of instructions called chunks. A chunk can modify the rendering context or supply vertex and strip data. This is more efficient as vertex data is preformatted and the rendering context is less often modified. However, it is a bit more involving to parse.
Chunk Model Structure
| Field | Offset | Type | Size | Description | Notes |
|---|---|---|---|---|---|
| vlist | 0 | Sint32* | 4 | "Vertex" chunk list | Only for vertex chunks, separate for 32bit access + easier preprocessing |
| plist | 4 | Sint16* | 4 | "Polygon" chunk list | Remaining chunk types |
| center | 8 | NJS_VECTOR | 12 | Model center | |
| r | 20 | Float | 4 | Model radius |
The chunk lists shall be parsed continuously until the "end" chunk (255) is reached.
Vertex chunk list
The library first parses the vertex chunk list. A vertex chunk contains a list of vertices. Each vertex will be placed in an intermediate vertex buffer that will be used for strip indices.
A vertex chunk is composed of a header, followed by data:
| Header (32 bits) | Vertex data information (32 bits) | Data | ||
|---|---|---|---|---|
| Type (0-15) | Next chunk (16-31) | Vertex count (0-15) | Index offset (16-31) | |
| Vertex type | Chunk size - 1 | Number of vertices | Start position in intermediate vbuffer | Depends on type |
The data depends on the vertex chunk type:
| Vertex chunk | Components | Vertex stream | Comment |
|---|---|---|---|
| NJD_CV_SH | Position | x, y, z,1.0f, ... | SH4 optimized (aligned) |
| NJD_CV_VN_SH | Position + Normal | x, y, z,1.0f, nx, ny, ny, 0.0f, ... | SH4 optimized (aligned) |
| NJD_CV | Position | x, y, z, ... | |
| NJD_CV_D8 | Position + Diffuse | x, y, z, D8888, ... | |
| NJD_CV_UF | Position + UserFlag | x, y, z, UF32 | |
| NJD_CV_NF | Position + NinjaFlag | x, y, z, NF32 | Used for weights |
| NJD_CV_S5 | Position + Diffuse + Specular | x, y, z, D565, S565, ... | |
| NJD_CV_S4 | Position + Diffuse + Specular | x, y, z, D4444, S565, ... | |
| NJD_CV_IN | Position + Diffuse + Specular | x, y, z, D16, S16, ... | |
| NJD_CV_VN | Position + Normal | x, y, z, nx, ny, nz, ... | |
| NJD_CV_VN_D8 | Position + Normal + Diffuse | x, y, z, nx, ny, nz, D8888, ... | |
| NJD_CV_VN_UF | Position + Normal + Diffuse + UserFlag | x, y, z, nx, ny, nz, D8888, UF32, ... | |
| NJD_CV_VN_NF | Position + Normal + Diffuse + NinjaFlag | x, y, z, nx, ny, nz, D8888, NF32, ... | Used for weights |
| NJD_CV_VN_S5 | Position + Normal + Diffuse + Specular | x, y, z, nx, ny, nz, D565, S565, ... | |
| NJD_CV_VN_S4 | Position + Normal + Diffuse + Specular | x, y, z, nx, ny, nz, D4444, S565, ... | |
| NJD_CV_VN_IN | Position + Normal + Diffuse + Specular | x, y, z, nx, ny, nz, D16, S16, ... | |
| NJD_CV_VNX | Position + Normal | x, y, z, nx10, ny10, nz(10), pad, ... | |
| NJD_CV_VNX_D8 | Position + Normal + Diffuse | x, y, z, nx10, ny10, nz(10), pad, D8888, ... | |
| NJD_CV_VNX_UF | Position + Normal + UserFlag | x, y, z, nx10, ny10, nz(10), pad, UF32, ... |
Note that the library only supports one chunk type per model.
The data is continuous until the end chunk is reached (255).
Polygon chunk list
The library then processes the polygon chunk list, which consists of chunks that specify how to render the vertices.
These chunks follow the following structure:
| Header (16 bits) | Next chunk, optional | Data, optional | |
|---|---|---|---|
| Type (0-7) | Headbits (8-15) | ||
| Vertex type | Depends on type | Chunk size - 2 | Depends on type |
Chunks are continuous until the end chunk is reached (255).
There are 5 categories of polygon chunks:
- Bits chunk (fixed 16 bit size) : some rendering attributes
- Tiny chunks (fixed 32 bit size) : texture ID
- Material chunks : diffuse color, ambient color, specular color, blending flags
- Volume chunks : collision/modifier volumes
- Strip chunks : strip data
Bits chunks
| Header (16 bits) | |
|---|---|
| Type (0-7) | Headbits (8-15) |
The next chunk is directly after.
...
Tiny chunks
| Header (16 bits) | Data (16 bits) | |
|---|---|---|
| Type (0-7) | Headbits (8-15) | |
The next chunk is directly after.
...
Material chunks
| Header (16 bits) | Next chunk | Data | |
|---|---|---|---|
| Type (0-7) | Headbits (8-15) | ||
...
Volume chunks
...
Strip chunks
A strip chunk renders a triangle strip using vertices from the intermediate buffer generated by the vertex chunk list.
It can contain additional information, such as UV coordinates, vertex color or user data.
The chunk structure is as follows:
| Header (16 bits) | Next chunk | Strip data information | Data, optional | ||
|---|---|---|---|---|---|
| Type (0-7) | Headbits (8-15) | Strip count (0-13) | User area size (14-15) | ||
| Strip type | Rendering flags | Chunk size - 2 | Number of strips | NJD_UFO_0 = 0 bits
NJD_UFO_1 = 16 bits NJD_UFO_2 = 32 bits |
Depends on type |
The data itself follows this structure:
| Strip header | Strip stream | |||||||
|---|---|---|---|---|---|---|---|---|
| Length (0-14) | Winding flag (15) | First element | Second element | Element N .... | ||||
| Number of vertices | If set, reverse order | Index | Data | Index | Data | Index | Data | User area (optional) |
* The user area is per-triangle, so it only starts at the third element, from there every other vertex is another triangle.
The data stream depends on the strip type:
| Strip chunk | Components | Data stream |
|---|---|---|
| NJD_CS | None | index |
| NJD_CS_UVN | UV (255 = 1.0) | index, u, v |
| NJD_CS_UVH | UV (1023 = 1.0) | index, u, v |
| NJD_CS_VN | Normal | index, nx, ny, nz |
| NJD_CS_UVN_VN | UV (255), normal | index, u, v, nx, ny, nz |
| NJD_CS_UVH_VN | UV (1023), normal | index, u, v, nx, ny, nz |
| NJD_CS_D8 | Diffuse | index, ar, gb |
| NJD_CS_UVN_D8 | UV (255), diffuse | index, u, v, ar, gb |
| NJD_CS_UVH_D8 | UV (1023), diffuse | index, u, v, ar, gb |
| NJD_CS_2 | None | index |
| NJD_CS_UVN2 | UV (255) | index, u, v |
| NJD_CS_UVH2 | UV (1023) | index, u, v |
Ninja Animation Format

Ninja models use a node-like hierarchy of NJS_OBJECTs that are linked together using parent-child-sibling relationships. In the SAMDL screenshot, the object object_g_g0000_eggman_eggman is the root node, which has a child node object_g_g0000_eggman_body (selected). The child node has a sibling node object_g_g0000_eggman_ketu. Both the child and the sibling nodes have their own children hierarchies (collapsed in the screenshot).
The root node makes the top of the hierarchy and is the only node without a parent. Although uncommon, the root node can sometimes have a sibling node.
The relationships between the nodes in the hierarchy are important for both rendering and animations. In rendering, a child node inherits its parent node's transform matrix (position, rotation and scale) before applying its own, while a sibling node does not inherit those from the previous sibling.
The animation format contains various transformation data for specific nodes in the hierarchy. The most common animations used in Sonic Adventure games are position, rotation and scale animations.
Ninja Motion
The animation structure is NJS_MOTION. It consists of a pointer to an array of animation data, the number of frames in the animation, the type of the animation, the number of nodes and interpolation settings.
| Field | Offset | Type | Size | Description |
|---|---|---|---|---|
| *mdata | 0 | void | 4 | Pointer to the animation data. |
| nbFrame | 0x4 | Uint32 | 4 | Number of animation frames. |
| type | 0x8 | Uint16 | 2 | Motion type. |
| inp_fn | 0xA | Uint16 | 2 | Interpolation method and number of elements. |
Motion Types
The type field is a value that holds a combination of several flags. A motion using these types of animation will have the flags below.
| Motion type | Value | Description |
|---|---|---|
| NJD_MTYPE_POS_0 | 0x1 | Position. |
| NJD_MTYPE_ANG_1 | 0x2 | Rotation. |
| NJD_MTYPE_SCL_2 | 0x4 | Scale. |
| NJD_MTYPE_VEC_3 | 0x8 | Vector (light, camera). |
| NJD_MTYPE_VERT_4 | 0x10 | Vertex (shape). |
| NJD_MTYPE_NORM_5 | 0x20 | Normal (shape). |
| NJD_MTYPE_TARGET_3 | 0x40 | Target (camera). |
| NJD_MTYPE_ROLL_6 | 0x80 | Roll (camera). |
| NJD_MTYPE_ANGLE_7 | 0x100 | Angle (camera). |
| NJD_MTYPE_RGB_8 | 0x200 | Color (light). |
| NJD_MTYPE_INTENSITY_9 | 0x400 | Intensity (light). |
| NJD_MTYPE_SPOT_10 | 0x800 | Spot (light). |
| NJD_MTYPE_POINT_10 | 0x1000 | Point (light). |
| NJD_MTYPE_QUAT_1 | 0x2000 | Quaternion. |
Animations that change vertices or normals in a node are called shape motions, or shapes. They work by replacing the node's vertices and/or normals for different animation frames and interpolating between them.
Motion Interpolation Algorithms and Number of Elements
An interpolation algorithm determines how the animation progresses from one frame to the next one (i.e. what happens between frames). There are three possible types of interpolation:
| Interpolation type | Flag | Description |
|---|---|---|
| NJD_MTYPE_LINER | 0x0000 | Linear interpolation (used in the overwhelming majority of cases). |
| NJD_MTYPE_SPLINE | 0x0040 | Spline-based interpolation. |
| NJD_MTYPE_USER | 0x0080 | Interpolation using a custom function (unused). |
The interpolation type flag is combined with the number of animation elements. To put it simply, this number means how many different types of animations can be used simultaneously. For example, if you wanted position+animation+scale, that number would be 3. A typical motion has from 1 to 3 elements, with 2 and 3 being the most common. For camera or light motions, there can be 4 or 5 elements.
Animation Data
The animation data (motion data) structure can be NJS_MDATA1, NJS_MDATA2, NJS_MDATA3 etc. depending on the number of animation elements. it consists of pointers to keyframe arrays and numbers of keyframes. The size of the MDATA structure is calculated as 8 * number of elements.
Support for MDATA1 varies between different games and tools. It is common for motions with a single element to use the MDATA2 type instead, leaving the second element empty.
|
|
|---|
Basically, the only difference between the MDATA structures is the size of the arrays.
Animation Keyframes
A key frame defines the animation state at a specific moment. The animation is achieved by interpolating between keyframes.
Different animation types make use of different data. For example, a position animation needs three floats for X, Y and Z, and a rotation animation needs X, Y and Z rotation. There are several keyframe structures that store different data. All keyframe structures include the keyframe ID (Uint32).
NJS_MKEY_F contains an array of three Float values. It is used for position and scale animations.
NJS_MKEY_A contains an array of three Angle (Sint32) values. It is used for rotation animations.
NJS_MKEY_SA contains an array of three SAngle (Sint16) values. It is used in some Chao animations in place of MKEY_A to store rotations in two bytes instead of four.
NJS_MKEY_P contains a pointer to an array of vertices (NJS_POINT3) or normals. It is used in shape motions. The array contains all vertices or normals for a given node.
NJS_MKEY_UI32 contains a color value (Uint32). It is meant to be used for light motions.
NJS_MKEY_A1 contains a single Angle (Sint32) value. It is used in camera roll and angle animations.
NJS_MKEY_SA1 contains a single SAngle (Sint16) value. It can be used in camera roll and angle animations.
NJS_MKEY_F1 contains a single Float value. It is meant to be used for light intensity and angle animations.
NJS_MKEY_F2 contains an array of two Float values. It is meant to be used point light animations.
NJS_MKEY_SPOT contains two Float values for near and far ranges, and two Angle values for inner and outer angle limits. It is meant to be used in spotlight animations.
NJS_MKEY_QUAT contains an array of four Float values. It is used in quaternion animations.
Here is a full table of motion types and their required keyframe types:
| Motion type | Keyframe type |
|---|---|
| NJD_MTYPE_POS_0 | NJS_MKEY_F |
| NJD_MTYPE_ANG_1 | NJS_MKEY_A or NJS_MKEY_SA |
| NJD_MTYPE_SCL_2 | NJS_MKEY_F |
| NJD_MTYPE_VEC_3 | NJS_MKEY_F |
| NJD_MTYPE_VERT_4 | NJS_MKEY_P |
| NJD_MTYPE_NORM_5 | NJS_MKEY_P |
| NJD_MTYPE_TARGET_3 | NJS_MKEY_F |
| NJD_MTYPE_ROLL_6 | NJS_MKEY_A1 or NJS_MKEY_SA1 |
| NJD_MTYPE_ANGLE_7 | NJS_MKEY_A1 |
| NJD_MTYPE_RGB_8 | NJS_MKEY_UI32 |
| NJD_MTYPE_INTENSITY_9 | NJS_MKEY_F1 |
| NJD_MTYPE_SPOT_10 | NJS_MKEY_SPOT |
| NJD_MTYPE_POINT_10 | NJS_MKEY_F2 |
| NJD_MTYPE_QUAT_1 | NJS_MKEY_QUAT |
Example Motion
Here is a simple motion exported from SAMDL:
NJS_MKEY_F pos_yasi_mi_mi[] = {
{ 0, 1.811949f, 28.301275f, 2.174971f },
{ 1, 1.811949f, 28.391066f, 1.982089f },
{ 2, 1.811949f, 28.492111f, 1.625827f },
{ 3, 1.811949f, 28.638508f, 1.283709f },
{ 4, 1.811949f, 28.754591f, 1.049127f },
{ 5, 1.811949f, 28.840328f, 0.964872f },
{ 6, 1.811949f, 28.886026f, 0.99825f },
{ 7, 1.811949f, 28.892193f, 1.160306f },
{ 8, 1.811949f, 28.87126f, 1.435133f },
{ 9, 1.811949f, 28.797846f, 1.792463f },
{ 10, 1.811949f, 28.601675f, 2.383217f },
{ 11, 1.811949f, 28.347603f, 3.039044f },
{ 12, 1.811949f, 28.1429f, 3.376662f },
{ 13, 1.811949f, 28.046597f, 3.34584f },
{ 14, 1.811949f, 28.013111f, 3.123897f },
{ 15, 1.811949f, 28.020987f, 2.95091f },
{ 16, 1.811949f, 28.0742f, 2.786237f },
{ 17, 1.811949f, 28.128613f, 2.62165f },
{ 18, 1.811949f, 28.199284f, 2.448923f },
{ 19, 1.811949f, 28.301275f, 2.174971f }
};
NJS_MKEY_A rot_yasi_mi_mi[] = {
{ 0, 0xFFFFFEF1, 0, 0 },
{ 7, 0xFFFFFEF1, 0, 0 },
{ 8, 0xFFFFFE3F, 0, 0 },
{ 9, 0xFFFFFD98, 0, 0 },
{ 19, 0xFFFFFEF1, 0, 0 }
};
NJS_MDATA2 mdata_yasi_mi[] = {
{ pos_yasi_mi_mi, rot_yasi_mi_mi, LengthOfArray<Uint32>(pos_yasi_mi_mi), LengthOfArray<Uint32>(rot_yasi_mi_mi) }
};
NJS_MOTION motion_yasi_mi = { mdata_yasi_mi, 20, NJD_MTYPE_POS_0 | NJD_MTYPE_ANG_1, NJD_MTYPE_LINER | 2 };
The motion has 20 frames and uses position and rotation animation data (two elements), so MDATA2 is used. The rotation data has five keyframes, and the position data has 20 keyframes.
Motions and Actions
The Ninja library has functions to draw animated models that use the Action structure (NJS_ACTION).
An NJS_ACTION consists of two pointers: one to the NJS_OBJECT and the other to the NJS_MOTION.
NJS_ACTION myaction = { &myobject, &mymotion };
Ninja Texture List
Rendering textured models in Ninja requires the so-called texture list (texlist). It needs to be set in code before rendering the model. A loaded texture list contains data corresponding to texture IDs specified in models' materials.
The texture list stucture is NJS_TEXLIST. It consists of a pointer to an array of texture data (NJS_TEXNAME) and the number of textures.
In most cases, you can simply define an empty texture list like this:
NJS_TEXNAME textures_att1[7];
NJS_TEXLIST texlist_att1 = { arrayptrandlength(textures_att1) };
This defines an NJS_TEXLIST called texlist_att1, which has space for data for seven textures.
NJS_TEXNAME can contain texture filenames, but that is optional.
Ninja ASCII and Ninja Binary
In all of the examples above, Ninja data types are exported as C structs. However, the SDK has its own specification for storing models as easily readable text (Ninja ASCII, or NJA) that is still compatible with the raw C representation and can be included in compilation. It is defined using macros in the njdef.h header.
Here's what a typical NJS_OBJECT would look in its Ninja ASCII representation:
OBJECT_START
OBJECT object_ecb_s_pltb__s_pltb_[]
START
EvalFlags ( 0x00000012 ),
Model model_ecb_s_pltb_s_pltb,
OPosition ( 0.000000F, 792.175476F, 2493.007080F ),
OAngle ( 0.000000F, 0.000000F, 0.000000F ),
OScale ( -1.000000F, 1.000000F, 1.000000F ),
Child NULL,
Sibling NULL,
END
OBJECT_END
For modding purposes it is completely optional. SA Tools support NJA export for models, animations and texture lists.
In Dreamcast SDK samples you can find a lot of NJA exported models, animations and texture lists. They are meant to be #include'd in source files and compiled directly into the executable file. This is also how the Sonic Adventure games were built (for the most part).
In addition to the ASCII specification, the SDK allows to read models, animations and texlists from binary files. A single binary file can pack multiple models, motions and texlists. SA Tools can read and write Ninja Binary formats, provided that the model and animation data can be recognized.
Ports of Ninja to Other Platforms
Various adaptations of Ninja formats were used in Dreamcast game ports to other platforms. Below is some basic information on known Ninja ports.
Ninja for PC (Sonic Adventure DX, Phantasy Star Online)
SADX PC and its derivatives have an extra field in the NJS_MESHSET and NJS_MODEL structs. This change carried over to the later ports, including the Steam version and the PS3 and X360 ports. This format is known as Ninja Basic (SADX), BasicDX, or, more recently, Basic+.
Interestingly enough, the Basic models used for collision in the PC port of Sonic Adventure 2 are in the original format without the extra fields.
SA Tools have full support for both Basic and Basic+ formats.
Ninja for Gamecube
The byte values for material and vertex colors are stored in the order RGBA instead of ABGR in regular Ninja.
Ninja Basic differences
In the Gamecube version of SADX, the order of fields in NJS_TEX is swapped: V comes before U.
Ninja Chunk (Big Endian)
Sonic Adventure 2 Battle, Skies of Arcadia Legends and Phantasy Star Online on the Gamecube use a Big Endian variation of Ninja Chunk models. No major changes were made to the Ninja Chunk model specification as far as we could tell. The byte order for Ninja Binary is inconsistent between different games (the binary chunk size value is stored in the Little Endian format in PSO but not in Skies). SA Tools can open and save both variations (for the most part).
Some specific chunk models in SA2B Gamecube and SA2 PC (such as Chao Garden trees) can store vertex colors as either RGBA (SA2B GC) or ARGB (SA2 PC). The PC version has different rendering functions for different color byte orders.
Ginja
In addition to Ninja Chunk (Big Endian), some Gamecube games and their ports to PC use a proprietary model format that is a mix of Ninja Chunk and Nintendo's model formats. This format, also known as "GC models" or "SA2B format", is used in Phantasy Star Online (Gamecube and PC, alongside with Ninja Chunk Big Endian), Sonic Adventure 2: Battle (Gamecube and PC) and Billy Hatcher (Gamecube and PC). The Ginja format has variations between games. SA Tools support the format used in SA2 and may be able to open models from other games, although not guaranteed.
Xinja
Xbox variation of Ginja used in the Xbox port of Phantasy Star Online. SA Tools have some limited support for this format.
Common Extensions and Magic Numbers
The table below lists some of the known extensions and magic numbers commonly used by Ninja formats and their derivatives.
| Format | Extension (binary) | Extension (ASCII) | Magic number |
|---|---|---|---|
| Ninja Basic Model | .njd | .nja | NJBM |
| Ninja Chunk Model | .njd | .nja | NJCM |
| Ninja Motion | .njm | .nam | NMDM |
| Ninja Shape Motion | .njs | .nas | NSSM |
| Ninja Texlist | .njt | .nat | NJTL |
All of the above can be packed into a single .nj file in binary format.
For Ginja and Xinja variations of the extensions and magic codes, replace .nj with .gj or .xj, and NJ with GJ or XJ.