It is currently Thu May 17, 2012 12:24 am

All times are UTC



Welcome
Welcome to sadxforum

You are currently viewing our boards as a guest, which gives you limited access to view most discussions and access our other features. By joining our free community, you will have access to post topics, communicate privately with other members (PM), respond to polls, upload content, and access many other special features. Registration is fast, simple, and absolutely free, so please, join our community today!

The site administrators are GohanSN and Fiamonder10.





 Page 1 of 1 [ 7 posts ] 
Author Message
 Post subject: SANiK's notes, Issue 3/PRIMUS (SA2 Chunk Format)
Posted: Fri Jul 09, 2010 6:31 pm 
User avatar

Joined: Tue Feb 02, 2010 12:34 am
Posts: 327
Location: Fincastle, Virginia
<font size=7><b>ISSUE 3</b></font>

Due to the sheer size of the 3rd version of the SEGA model format,
I'm just going to just publish snippets from my e-mails with Kryslin.
Kryslin has made a document outlining the format,
and the link to his document can be found at the end.
Kryslin's doc should remain the primary source,
while this collection of e-mails (might contain old/uncorrected errors) should be treated as a secondary source


Here we go!

[quote name='1']Variable types from here on:
byte=unsigned char
sbyte=signed char
word=unsigned short
sword=signed short
dword=unsigned int/long
sdword=signed int/long

The SEG struct I would like to rename to Object
And the ATTACHED struct I would like to rename to Model
And some other things were renamed as well

typedef struct Object{
dword flags; //See the section titled "Flags"
dword model; //Pointer to Model struct in RAM/file
float pos[3];
dword rot[3]; //To convert to degrees: (rot[...]/65536.0) * 360.0
float scale[3];
dword child; //Pointer to child Object struct (think of it as a sub-directory)
dword sibling; //Pointer to sibling Object struct (think of it as a dir next to a dir)
};

typedef struct Model{
dword vertex; //Pointer to the vertex buffer in RAM/file
dword poly; //Pointer to the poly buffer in RAM/file
float center[3]; //Sphere collision - center
float radius; //Sphere collision - warning: might be "diameter" instead of "radius"
};

Flags:
bit 0 = Don't translate model
bit 1 = Don't rotate model
bit 2 = Don't scale model
bit 3 = Don't draw model
bit 4 = Don't draw children of current Object, but move onto siblings
bit 5 = Use ZYX rotation order instead (if on, flip order of steps C to E in bottom table)

Connecting the Object structs together to render a correctly structured model:
A) Translate using the current Object's pos[0], pos[1], and pos[2] values
B) Scale using the current Object's scale[0], scale[1], scale[2] values
C) Rotate the X axis by converting rot[0] to degrees first
D) Rotate the Y axis by converting rot[1] to degrees first
E) Rotate the Z axis by converting rot[2] to degrees first
F) Draw the Object
G) Draw the Object's children (Children use the matrix of their parents)
H) Draw the Object's siblings (Siblings use the matrix of the current Object's parent ... if there is no parent to the current object than the identity matrix is used)[/quote]

[quote name='2']How the SEGA model format works:
Models are actually stripped C programs (no code, but the .text section's stripped into a file)
With that said, models are just structure files that get read directly to RAM
Other file formats have a header detailing the # of objects, and misc. stuff
This format doesn't

Now, since the models are loaded directly to RAM, the models use pointers to point to other body parts.
This is where the idea of a "KEY" comes in. The key is the value that gets subtracted from the pointers in the model to convert the pointers from giving the RAM location to the file location ... whew, said a mouthful!

The KEY generation is a funny process really because it works like this:
1) SEGA models 99% of the time use a model scale of 1.0f, 1.0f, 1.0f in the scale settings
2) 1.0f, 1.0f, 1.0f = 0000803F0000803F0000803F in hex
3) I use this to find where the SEG structs are to be able to rip the body parts
4) To 'generate' the KEY, I use another common 'fact'
I noticed that the ATTACHED struct is usually before the SEG struct
5) So the formula I use to generate a KEY
KEY=SEG.attach - (address of SEG struct in file - sizeof(ATTACHED struct))
6) I then scan again for keys, and if the same KEY is found 2 more times, I stick to it (bad idea I know, but the model format is a fuck like that =/)
7) When the key is found, the file is re-examined and the SEGs are scanned for again
This time, the SEG.attach - KEY gives the location of the ATTACHED struct ... unless the SEG struct is just incorrect data =O[/quote]

[quote name='3']Part 1 of the bigger lesson begins here:
Ok, now to cover the most important part of this document.

Polygons and Verteces come in buffers.
Each buffer has data that comes in sections.
I'll be referring to these sections/section headers as "chunks" from now on.

IMPORTANT: A POLY chunk can actually appear in the 'vertex' buffer
As well as a VERTEX chunk can appear in the 'poly' buffer.
Confusing? If so, don't worry, it'll make sense later.

How a chunk header looks:
typedef struct Chunk{
byte type;
byte flags;

//Note, if it's a 16 bit chunk, the following variables don't exist (will be explained in detail later)
//Thus they shall not be read
word size;
byte *data;
};

The "byte type" is what tells out what vertex type to use. (e.g., float x, float y, float z, OR to use float x, float y, float z, dword RGBA, etc.)

Not to confuse you, but what's even more interesting is that when "byte type" is used in the POLY buffer,
it tells us what poly type to use.[/quote]

[quote name='4']There are many chunk types associated with verteces.
One has X Y Z NX NY NZ
Then another type has just X Y Z, etc.

The 'size' is not the total in bytes

If it's a vertex chunk, then the size in bytes is based on SIZE * 4
For all other chunks, the size is based on SIZE * 2
To get the exact location of the next chunk, one does:
address right after the size variable + X * size
X being either 2 or 4

How to identify vertex chunks from all other chunks?
The "byte type" is your answer.

Types from == 32 (dec) to < 56 (dec) are vertex chunks

Now you'll be able to skip chunks that you do not know by using the size variable! Well... not really =/

There are a few chunks that don't have a size variable at all, so these you must be aware of:

The NULL chunk:
Only consists of byte type and byte flags.
Its "byte type" or TYPE ID is 0
Actually has no flags (An example of a 16bit chunk - something that was mentioned above)
Usage: to realign data to a 32 bit boundary for optomized reads

The END chunk:
Only consists of byte type and byte flags.
Its TYPE ID is 255
Actually has no flags
Usage: marks the end of the vertex buffer or the poly buffer

The FLAGS chunk:
Only consists of byte type and byte flags
It has multiple types (like the VERTEX CHUNK)
Type IDs are from == 1 (dec) to == 5 (dec)
Usage: the flags variable is used for setting the SRC alpha and DST alpha
The FLAGS chunk allows one to change these settings.
For now I won't bother with the flags byte.

The TVAR chunk:
Consists of a byte type, byte flags, and WORD value
It has multiple types (like the VERTEX CHUNK)
Type IDs are from == 8 (dec) to == 9 (dec)
Usage: the FLAGS chunk changes things such as SRC alpha, etc.
The TVAR chunk changes things such as UV max (0 to 255) or (0 to 1024).

Ok, that's all of the 'tricky' chunks. For now we'll ignore them, but do note that the TVAR and FLAGS chunks change ENVIRONMENT variables for chunks that follow (e.g., uv size, 0 to 255 or 0 to 1024). So you'll need to keep global variables and change them as you traverse the chunks, such as the UV max which I've mentioned before.

Chunks with IDs from == 16 (dec) to < 32 (dec) just describe the textures
We'll leave that for later as well (like alpha, diffuse material, etc.)

The VERTEX chunk (has many different types, but the same header applies)
It has the usual chunk header
But then in the data portion of the chunk, here's what's included - a subHeader:
word indexBase;
word vertexTotal;
That's then followed by the index data.

The indexBase works like this, if it's 0 then that means that the first three verteces are called 0, 1, 2 by the poly list when the polygons are being drawn
If the indexBase is 200, then that means that the first three verteces are "actually" 200, 201, and 202.

Anyways, here are the different types of VERTEX chunks with the TYPE IDs along with how the vertex data looks:

(Before I said types from == 32 (dec) to < 56 (dec) are vertex chunks, but do note that SEGA doesn't always use the full range! There are unused chunks put there for future vertex types)

32 = float x, float y, float z, 1.0f //1.0f = constant
33 = float x, float y, float z, 1.0f, float nx, float ny, float nz, 0.0f //1.0f & 0.0f = constant
34 = float x, float y, float z
35 = float x, float y, float z, dword ARGB8888 //Diffuse color
36 = float x, float y, float z, dword FLAGS //Allows programmers to put custom flags per vertex
37 = float x, float y, float z, dword EFLAGS //Allows programmers to put custom extended flags per vertex
38 = float x, float y, float z, dword [Diffuse RGB565 (31-16) | Specular RGB565 (15-0)] //Color
39 = float x, float y, float z, dword [Diffuse ARGB4444 (31-16) | Specular RGB565 (15-0)] //Color
40 = float x, float y, float z, dword [Diffuse (31-16) | Specular (15-0)] //Allows prorammer to put custom Color format
41 = float x, float y, float z, float nx, float ny, float nz
42 = float x, float y, float z, float nx, float ny, float nz, dword ARGB8888
43 = float x, float y, float z, float nx, float ny, float nz, dword FLAGS
44 = float x, float y, float z, float nx, float ny, float nz, dword EFLAGS
45 = float x, float y, float z, float nx, float ny, float nz, dword [Diffuse RGB565 (31-16) | Specular RGB565 (15-0)]
46 = float x, float y, float z, float nx, float ny, float nz, dword [Diffuse ARGB4444 (31-16) | Specular RGB565 (15-0)]
47 = float x, float y, float z, float nx, float ny, float nz, dword [Diffuse (31-16) | Specular (15-0)]
48 = float x, float y, float z, dword unknown
49 = float x, float y, float z, dword unknown, dword ARGB8888
50 = float x, float y, float z, dword unknown, dword FLAGS
51 = float x, float y, float z, dword unknown, dword EFLAGS
52 = float x, float y, float z, dword unknown, dword [Diffuse RGB565 (31-16) | Specular RGB565 (15-0)]
53 = float x, float y, float z, dword unknown, dword [Diffuse ARGB4444 (31-16) | Specular RGB565 (15-0)]
54 = float x, float y, float z, dword unknown, dword [Diffuse (31-16) | Specular (15-0)]

Best use a SWITCH statement or something to handle the verteces like opcodes =P[/quote]

[quote name='5']These are misc chunk types that I hadn't mentioned before.
They do use a "word size" variable so you can skip them freely if you come across them:

MATTER Chunk, from == 56 (dec) to < 64 (dec)
Format of chunk is unknown
It deals with collision info
Hopefully you won't need it =/

STRIP Chunk, from == 64 (dec) to < 255 (dec)
It contains the poly data
There are 10 or so chunk types I think
The rest are left for future STRIP chunks, but were never used in the long run
So it'll be something like 64 to 75 instead

After the normal chunk header in STRIP chunk, a subHeader follows:
word stripTotal;

Use 0x3FFF when reading the actual stripTotal
You see, the upper 2 bits (0xC000) are used for adding custom info per poly (allowing games to store info such as "FURRY POLY or NONLIT POLY etc.)
If the upper two bits are:
0=Polys have NO FLAGS
1=Polys have 2 bytes for FLAGS
2=Polys have 4 bytes for FLAGS
3=Polys have 6 bytes for FLAGS
In the upcoming sections, wherever you see the word "FLAGS", do remember that the size of that section is based on these 2 upper bits

Back to the strip total... STRIP chunks have poly data spread into strips, simple as that =O

So here it is, the STRIP chunk list (All this is after the "word stripTotal" variable!)
If there's an S by the number, the UVs range from 0-255,
If there's an L by the number, the UVs range from 0-1024
If there's no letter then that means no UVs exist
*wonders then, what the fuck are the FLAG chunks for? Some kind of UV override or something?*

64=word polyTotal, word indexA, word indexB, word indexC, FLAGS
65S=word polyTotal, word indexA, word UA, word VA, word indexB, word UB, word VB, word indexC, word UC, word VC, FLAGS
66L=word polyTotal, word indexA, word UA, word VA, word indexB, word UB, word VB, word indexC, word UC, word VC, FLAGS

67=word polyTotal, word indexA, word unknownA[3], word indexB, word unknownB[3], word indexC, word unknownC[3], FLAGS
68S=word polyTotal, word indexA, word UA, word VA, word unknownA[3], word indexB, word UB, word VB, word unknownB[3], word indexC, word UC, word VC, word unknownC[3], FLAGS
69L=word polyTotal, word indexA, word UA, word VA, word unknownA[3], word indexB, word UB, word VB, word unknownB[3], word indexC, word UC, word VC, word unknownC[3], FLAGS

70=word polyTotal, word indexA, dword ARGB_A, word indexB, dword ARGB_B, word indexC, dword ARGB_C, FLAGS
71S=word polyTotal, word indexA, word UA, word VA, dword ARGB_A, word indexB, word UB, word VB, dword ARGB_B, word indexC, word UC, word VC, dword ARGB_C, FLAGS
72L=word polyTotal, word indexA, word UA, word VA, dword ARGB_A, word indexB, word UB, word VB, dword ARGB_B, word indexC, word UC, word VC, dword ARGB_C, FLAGS

73=UNKNOWN
74=UNKNOWN
75=UNKNOWN

Do note, for word polyTotal:
It's not exactly a 'poly' total ... more like 'poly index total'
Also, beware that polyTotal can be negative
If it is, that means that one starts with a clockwise drawing order.
Also, when drawing polygons, one needs to constantly interchange between drawing orders or polys will be missing/black due to poly clipping.

Also note that strips are not lists of indeces in forms of 3, 3, 3...
Instead, take this strip for example:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

0, 1, 2 make a poly, so expect a FLAGS section after index 2 (if it has UVs, then it's after the UVs ... common sense really)
Then
1, 2, 3 make a poly, so expect a FLAGS section after index 3
But if the size of the FLAGS section is 0, then you don't have to worry about that[/quote]

[quote name='6']How the index Base variable works:
Let's say this is the vertex buffer:
Vertex Chunk with index Base of 0, and 3 verteces:
0, 0, 0,
1, 1, 1,
2, 2, 2,
Vertex Chunk with index Base of 80, and 3 verteces:
3, 3, 3,
4, 4, 4,
5, 5, 5,

Poly Chunk with 2 strips:
Strip #1 has 1 triangle:
0, 1, 2
Strip #2 has 1 triangle:
80, 81, 82 //<< Did you see what happened?

To address verteces (3,3,3), (4,4,4), and (5, 5, 5), one uses 80, 81, and 82

The index Base offset's the vertex's index reference number[/quote]

[quote name='Letter from Kryslin 1']Ok, I think I've got it....!

Unless being cached, strips are processed immediately.

If they are being assigned to a cache, they are held until a command to draw them is issued.

Caching of Polys is accomplished by using the following chunk:

0x04 0xnn - Begin Caching Polygons in cache nn

To draw them ...

0x05 0xnn - Draw cached polygons in cache nn

I suspect there are no more than 2 caches for polygons, maybe 4 tops, given the dreamcast's
memory limitations.

So... I pile up the vertices & normals, as usual.
Unless being cached, Triangles are drawn immediately.
If being cached, the triangles (and their data) are saved.
They are then processed when a draw command is issued.

Very neat and tidy.

With this in place, your method of traversing the model - child, then sibling - should work perfectly.[/quote]

[quote name='Letter from Kryslin 2']Still working on the mangled geometry.

At least, it's an equal opportunity problem (which means it happens all
the time, meaning I should be able to stomp the thing flat).

On Cache/Draw commands; These work like the old basic "GOSUB" commands;

0xnn refers to the value of the Chunk.Flags byte.

Cache 0xnn stores the next chunk position in the file in an array; 0xnn is
the index.

Draw (0xnn) re-enters the file at the position stored in 0xnn and proceeds to
process that chunk, returning to the calling chunk.

There can be a maximum of 255 'Caches'. This amounts to 1024 bytes set
aside for 'Caching'. If you aren't 'Caching' polygons, processing proceeds
directly (load the vertices, then the strips). It works rather well.

It took me a bit, but I'm finally looking through the file using the method you
originally detailed. I'm cheating a bit, because I am pre-loading my headers,
generating my matrices, then linking my list. Everything links up, it's just some
groups that are mangled...

Here's a picture... (I'm a RAcaseal fan, can you tell...?)

Image[/quote]

[quote name='Meep']Kryslin solves the above bug *somehow*... need to ask him how - was away on vacation in Europe[/quote]

[quote name='Kryslin']Image[/quote]

[quote name='Kryslin']Here it is, Kryslin's full guide on the model format:
http://sanik.hacking-cult.org/Issue%203_PRIMUS.txt[/quote]



_________________
My youtube account: http://www.youtube.com/user/darkspinessonic35
My little update blog: http://itseasyactually.blogspot.com

Keep it clean, or I will.
Offline
 Profile  
 
 Post subject: Re: SANiK's notes, Issue 3/PRIMUS (SA2 Chunk Format)
Posted: Fri Jul 09, 2010 6:32 pm 
User avatar

Joined: Tue Feb 02, 2010 12:34 am
Posts: 327
Location: Fincastle, Virginia
Ninja Chunk Model Breakdown, Version 1.1
by Steve Pettit (Kryslin)

This has been a long time coming, working through the PAGES of notes and scribblings,and the documentation provided by varying parties. This includes some minor revisions.

To properly load and process a Ninja Chunk Model (NJCM) is actually quite simple...it was figuring out what the data does that was the hard part...

0) Initialize your variables
1) Find the start of the model information
2) Process an Object_Node
a) Load the first Object_Node
b) Process the Local Transformation Matrix
c) Process the vertex information, if present
d) Process the triangle strip information, if present
e) Using this Object_Node's matrix as a parent, process it's child node, if
present.
f) Using this Object_Node's Parent matrix as a parent, process it's sibling
node, if present.

When you finally return from 2, you've processed the entire model...

That's all there is to it.

I'll be breaking it down by these steps.

Step 0: Initialize your variables

Here's where all those neat data structures get set up.

First, a few conventions...
BYTE unsigned char
WORD unsigned 2 byte integer
DWORD unsigned 4 byte integer
SINGLE 4 byte float
short 2 byte signed integer
integer 4 byte signed integer

These should be familiar. Just wanted to make sure.

First, some foundation structures...

CONST PI = 3.14159267

//* A simple XYZ Triple for Vertices and Normals
XYZCoord
X as Single
Y as Single
Z as Single

//* BAMS = Binary Angle Measurment System. Every 0x4000 = 90 degrees.
BAMS
A as DWORD
B as DWORD
C as DWORD

//* Useful Functions
//* Converts BAMS to Degrees
Function BAMStoDegrees (Q as DWORD) as Single
Return (((Q and &hFFFF)/65536) * 360)

//* Converts BAMS to Radians
Function BAMStoRadians (Q as DWORD) as Single
Return (((Q and &HFFF)/65536) * (2 * PI))

//* Useful if you plan to walk your way through the files, to find the start
//* of the model info, instead of brute force computing it.
NJ_Header
ID as char(4)
Next as DWORD

//* Chunk Header for Model Data.
CM_Header
ID as byte
Flags as byte

//* Node Header for each model node. Contains Local Transformation Data
Object_Node
Flags as DWORD
Model_Data as DWORD
Translate as XYZCoord
Rotate as BAMS
Scale as XYZCoord
Child_Node as DWORD
Sibling_Node as DWORD

//* Contains Data that points to geometry information.
Model_Data
Vertex as DWORD
Poly as DWORD
Center as XYZCoord
Radius as Single

//*This is what I used for my vertex buffer. Yours may differ, because
Vertex
V as XYZCoord
N as XYZCoord

Vertex_Buffer(4096) as Vertex
(The NJCM renderer uses a constantly overwritten vertex buffer. Granted, 4096 is an arbitrary number, but it has handled just about everything I've tried to export. The most vertices you can have are 32,767). The

Reentry_List(256) as DWORD
(This array will store your 'cached' triangle strips. I'll explain this later)

You will also need some basic matrix and vector functions, along with a matrix stack that you can read the top matrix on without destroying it.

Step 1: Find the Start of Your Model Information

This is the tricky part, or not so tricky. It all depends.

If the file adheres to the NJCM specification put out by SEGA, then it's actually quite simple; Look for NJCM, followed by a DWORD. The next byte is the start of a model. Some model files contain more than one model. (The NJCM specification is IFF Header Based; There is a DWORD containing a Section Header, followed by a DWORD containing an offset to the next section).

If the file doesn't exactly conform to the NJCM specification then it gets problematic; You have to look for the signature of each Object_Node. This is simpler than it sounds, since they all (most of the time) use a scale of 1.0,1.0,1.0 (0000803F0000803F0000803F). Which Node is the first one is still a matter of trial and error. I suspect a little bit of analysis could yield some results - build a list of the addresses of all the nodes in a file. Use the Child and Sibling fields to determine how they are linked together, you should be able to find out which node is the first node in a model.

You will want to save this position, because all the offsets in the model will need this added to them to get the location in the file. If you decide to allocate a block of memory for your model, you don't need it. I call this value the Key.

My exporter reads the entire file into memory, not just the model, so I still need the Key value.

You will want to push an Identity Matrix on to your matrix stack.

Step 2: Processing an Object_Node
(2a)
After you set the position of your filestream to point to the Key value, you can read in your first Object_Node.

(2b)
You can use the Node.Translate and Node.Scale numbers directly. However, you need to convert the Node.Rotate numbers to something usable. They are stored in an old system called BAMS (Binary Angle Measurement System), and really, only the first 2 bytes of the Dword apply. To convert a BAMS angle to degrees, multiply by 0.0054931640625. To convert a BAMS angle to Radians, multiply by (6.283185/65536). These will give you angles you can use in standard floating point functions. (BAMS is quite simple, really... 0x0000 = 0 degrees; 0x4000 = 90 degrees/1.57 Radians; 0x8000 = 180 degrees/3.14 Radians, and 0xC000 = 270 degrees/4.71 Radians)

.Flags controls how the values in .Translate, Rotate, and .Scale are applied to the transformation matrix.

bit 0 = Don't translate model
bit 1 = Don't rotate model
bit 2 = Don't scale model
bit 3 = Don't draw model
bit 4 = Don't draw children of current Object, but move onto siblings
bit 5 = Use ZYX rotation order instead of XYZ (Models made by Lightwave 3D require this).
bits 6 and 7 do something as well, but are not needed for processing the model geometry.

If Bit 4 is set, you don't process the children of this node (skipping Step 2e)
If Bit 3 is set, you don't process the geometry at all.
If Bit 2 is set, you don't apply the .scale values
If Bit 1 is set, you don't apply the .rotate values
If Bit 1 is not set, and Bit 5 is set, then the .rotate values are applied ZYX.
If Bit 1 is not set, and Bit 5 is not set, then the .rotate values are applied XYZ.
If Bit 0 is set, you don't apply the .translate values

Once the local transformation matrix has been generated, you multiply it and it's parent (the parent should be the matrix at the top of the stack). The result is the transformation to be applied to all vertices in the model.

If a value for Node.Model_Data, Node.Sibling, Node.Child = 0, there is nothing to process.

(2c)(2d)
If a value for Model.Vertex or Model.Poly = 0, there is no data present.
Also, if Node.Flags(Bit 3) is set, you don't process any geometry.

Now, we get into the fun stuff - processing the data within the nodes.

What each sequence of bytes is depends on it's chunk type. Use the CM_Header structure to read the first to bytes of each section...

Chunk Types:
0x00 NULL Used to align data to 32 bit boundaries for optimized
reads.
Flags : Not Used

0xFF END Marks the end of the vertex or poly list.
Flags : Not Used

0x01 ALPHA_BLEND Controls Alpha Blending
Flags : bits 0-2 : Destination Alpha Instruction
bits 3-5 : Source Alpha Instruction

0x02 D_ADJUST MipMap 'D' Adjust
Flags : Bits 0-3 - 'D' Adjust value (0x0 - 0xF)

0x03 EXP Specular Exponent
Flags : Bits 0-4 - Exponent Value (0x00 - 0x10)

0x04 CACHE Saves the current position in the file, to be recalled
later. Ends the processing of the current chunk.
Flags : Which save position to use (0x00 - 0xFF)

0x05 CALL Loads a previously saved position, and file reading
begins from there. Processing continues at the saved
value.
Flags : Which save position to reload (0x00 - 0xFF)

0x08 TEX_ID1 Texture ID 1
Flags : Bits 0-3 - 'D' Adjust value (0x0 - 0xF)
Bit 4 Clamp V coordinates
Bit 5 Clamp U coordinates
Bit 6 Flip V coordinates
Bit 7 Flip U coordinates
WORD : Bits 0-12 Texture GBIX number.
Bit 13 Super Sample
Bit 14-15 Filter Mode

0x09 TEX_ID2 Texture ID 2
(Flags and Data as TEX_ID1)

0x10 MATERIAL Material Definition
This is where things start getting interesting
The first three bits of CM_Header.ID will tell you what
color data is present in the chunk

Bit 0 means the diffuse color is present
(ARGB, DWORD Length)
Bit 1 means the ambient color is present
(xRGB, DWORD Length. x will always be 255 (0xFF)
Bit 2 means the specular color is present
(ERGB, DWORD Length) E is the Exponent Value, from
0-16 (0x00 - 0x10)

Bits 0-2 of the CM_Header.Flags are the Destination Alpha
Functions
Bits 3-5 of the CM_Header.Flags are the Source Alpha
Functions

WORD : Length of Data
I've always skipped this, I've found it easier to use the
flag values to determine how much I need to read. Each
Color is 32 bit ARGB.

You will never find a 0x10 chunk.
It will always be 0x11 - 0x17.

Default your materials to the following :

Diffuse R = .8, G = .8, B = .8, A = Opaque (1.0)
Ambient R = .2, G = .2, B = .2
Specular R= 0, G=0, B=0, E=0

0x20 VERTEX Vertex Data Chunk
Flags will contain a value from 1 to 3. In my
experience, Flags = 1 is the actual vertex data.
Streams 2 and 3 are unknown.

Flags Bit 8 is an unknown - it may ocassionally be set.
It didn't affect any of the models I've exported.

WORD : Size of Data in Chunk, in DWORDs
(Size * 4 = Length of Data)

SHORT IndexBase - This allows ranges of the Vertex Buffer
to be overwritten; The actual index in the vertex buffer
is IndexBase + Vertex # in block (from 0 to VertexCount
-1)

SHORT VertexCount - Self Explanatory. This is how many
vertices there are in this chunk.

There are several vertex chunk formats...

Type Length Contents
0x20 0x10 X, Y, Z, 1.0
0x21 0x20 X, Y, Z, 1.0, nX, nY, nZ, 0.0
0x22 0x0C X, Y, Z
0x23 0x10 X, Y, Z, DWORD A8R8G8B8
0x24 0x10 X, Y, Z, DWORD Flags
0x25 0x10 X, Y, Z, DWORD UserFlags
0x26 0x10 X, Y, Z, WORD Diffuse R5G6B5, WORD Specular R5G6B5
0x27 0x10 X, Y, Z, WORD Diffuse A4R4G4B4, Word Specular R5G6B5
0x28 0x10 X, Y, Z, WORD Custom Diffuse, WORD Custom Specular
0x29 0x18 X, Y, Z, nX, nY, nZ
0x2A 0x1C X, Y, Z, nX, nY, nZ, DWORD A8R8G8B8
0x2B 0x1C X, Y, Z, nX, nY, nZ, DWORD Flags
0x2C 0x1C X, Y, Z, nX, nY, nZ, DWORD User Flags
0x2D 0x1C X, Y, Z, nX, nY, nZ, Diffuse R5G6B5, Specular R5G6B5
0x2E 0x1C X, Y, Z, nX, nY, nZ, Diffuse A4R4G4B4, Specular R5G6B5
0x2F 0x1C X, Y, Z, nX, nY, nZ, Custom Diffuse, Custom Specular
0x30 0x10 X, Y, Z, DWORD Special
0x31 0x10 X, Y, Z, DWORD Special,DWORD A8R8G8B8
0x32 0x10 X, Y, Z, DWORD Special, DWORD Flags
0x33 0x10 X, Y, Z, DWORD Special, DWORD UserFlags
0x34 0x10 X, Y, Z, DWORD Special, Diffuse R5G6B5, Specular R5G6B5
0x35 0x10 X, Y, Z, DWORD Special, Diffuse A4R4G4B4, Specular R5G6B5
0x36 0x10 X, Y, Z, DWORD Special, Custom Diffuse, Custom Specular

X,Y,Z are X,Y,Z Coordinates. nX, nY, nZ are vertex normals. All are
singles.

A8R8G8B8, A4R4G4B4, and R5G6B5 are color formats and a DWORD, WORD and
WORD lengths, respectively. Custom Diffuse and Specular are for Custom
Color Formats.

The "Special" DWORD is actually a packed vertex Normal, 10 bits for the
X, Y, Z. I don't know how to unpack it, as I've never run across it.


0x38 VOLUME Volume Data Chunk
I have not come across these during while processing
Skies of Arcadia or PSO models.

WORD : Size of Chunk, in WORDS. (Size * 2 = Bytes)

0x40 STRIP Triangle Strip Data
This is the good stuff - Geometry!

Flags : Bits 0-1
0x00 = No User Flags Present
0x01 = 1 User Flag WORD present
0x02 = 2 User Flag WORDs present
0x03 = 3 User Flag WORDs Present

WORD : Size of Chunk in WORDS (Size * 2 = Bytes)

WORD : Bits 14-15 User Offset (I have no clue what this
does)
Bits 0-13 Number of Strips in Chunk

-STRIP DATA STARTS HERE-

SHORT : Number of points in Strip
Number of Triangles = Number of Points - 2
If Value is positive, then strip winds CCW.
If Value is Negative, them strip winds CW.
Use the Absolute Value of this for the number of

There are 9 types of Strips...
Data is stored as SHORT type

0x40 STRIP_CS I0, I1, I2, UserFlags ( x Flags)
I3, UserFlags ( x FLags)...

0x41 STRIP_CS_UVN I0, U0, V0,
I1, U1, V1,
I2, U2, V2, WORD UserFLags (x FLags)
I3, U3, V3, UserFlags (x Flags)...

0x42 STRIP_CS_UVH I0, U0, V0,
I1, U1, V1,
I2, U2, V2, WORD UserFLags (x FLags)
I3, U3, V3, UserFlags (x Flags)

0x43 STRIP_CS_VN I0, nx0, ny0, nz0,
I1, nx1, ny1, nz1,
I2, nx2, ny2, nz2, UserFlags (x Flags)
I3, nx3, ny3, nz3, UserFlags (x Flags)...

0x44 STRIP_CS_UVN_VN I0, U0, V0, nx0, ny0, nz0
I1, U1, V1, nx1, ny1, nz1
I2, U2, V2, nx2, ny2, nz2, UserFlags ( x Flags)
I3, U3, V3, nx3, ny3, nz3, UserFlags ( x Flags)

0x45 STRIP_CS_UVH_VN I0, U0, V0, nx0, ny0, nz0
I1, U1, V1, nx1, ny1, nz1
I2, U2, V2, nx2, ny2, nz2, UserFlags ( x Flags)
I3, U3, V3, nx3, ny3, nz3, UserFlags ( x Flags)

0x46 STRIP_CS_DS I0, AR0, GB0
I1, AR1, GB1
I2, AR2, GB2, UserFlags( x Flags)
I3, AR3, GB3, UserFlags( x Flags) ...
0x47 STRIP_CS_UVN_DS I0, U0, V0, AR0, GB0
I1, U1, V1, AR1, GB1
I2, U2, V2, AR2, GB2, UserFlags( x Flags)
I3, U3, V3, AR3, GB3, UserFlags( x Flags) ...

0x48 STRIP_CS_UVH_DS I0, U0, V0, AR0, GB0
I1, U1, V1, AR1, GB1
I2, U2, V2, AR2, GB2, UserFlags( x Flags)
I3, U3, V3, AR3, GB3, UserFlags( x Flags) ...

I(n) is a Vertex Index - this is the Index of the
Vertex in the vertex buffer.
U(n),V(n) are the UV coordinates.
There are 2 formats, UVN, and UVH.
UVN Coordinates are stored 0x00 - 0xFF. Divide by
255 to get the Floating point value.
UVH Coordinates are Stored 0x0000 - 0x03FF. Divide
by 1023 to get the floating point value.
AR(n),GB(n) Are halves of a ARGB8888 Color Value.

Section 2 - Notes and What-Not

Here's the general process I used for my model exporter...

1) Find the Key Value (I had looked at several SoA files, along with PSO files, So I knew where the values would be found and had my program parse them automatically). SANiK of Sonic Adventure Hacking Note says that Dreamcast Files vary wildly in thier structure, though the models always follow the same format.

2)Push an Identity Matrix onto my Matrix Stack - this is the parent matrix for the entire model.

3)Call ProcessNode, passing the Stream and Key Values to it.

ProcessNode:
4a) Get the flag bits
4b) Convert the Rotation Values into actual angle values.
4c) Set Up an Identity Matrix
4d) If the flags allow it, make a scaling matrix, and multiply it into
4c)
4e) If the flags allow it, make the correct rotation matrix, and multiply
it into 4d)
4f) If the flags allow it, make a translation matrix, and multiply it into
4e)
4g) Multiply the result of 4d)-4f) into the matrix on the top of the matrix
stack. This is your local transformation matrix, and will be used on
all vertex data in this node of the model, or used to transform other
nodes farther down the tree. You will also use it on Vertex Normals as
well.
5) If there is a value in Object_Node.Model_Data, then load the Model_Data
structure there.
5a) Process the Vertex Section, if present.
5b) Process the Poly section, if present.
6) Push the local transformation matrix onto the matrix stack
7) If there is a value in Object_Node.Child, recursively call ProcessNode,
using Object_Node.Child as the Key value.
8) Pop the top matrix off the matrix stack
9) If there is a value in Object_Node.Sibling, recursively call
ProcessNode, using Object_Node.Sibling as the Key value.

When ProcessNode Returns, the whole model has been processed.

Processing Strips:

The Direction Flag for Triangle strips confused the heck out of me, and I could never get it to work right; Since most of the models I worked with have the vertex normals with them, I cheated. For each triangle, I averaged the three vertex normals to make a face normal. I then computed a face normal, and multiplied the 2 together, as if I was doing flat shading for lighting. If the value was positive, I had it right. If it was not positive, I switch the direction of the points in the triangle, correcting it.

Feel Free to ask questions...

Steve



_________________
My youtube account: http://www.youtube.com/user/darkspinessonic35
My little update blog: http://itseasyactually.blogspot.com

Keep it clean, or I will.
Offline
 Profile  
 
 Post subject: Re: SANiK's notes, Issue 3/PRIMUS (SA2 Chunk Format)
Posted: Sat Jul 10, 2010 12:11 am 
Member
Member
User avatar

Joined: Wed Apr 28, 2010 3:25 am
Posts: 345
Location: Western United States
I don't get this. What does this mean? If it would take to long to explain, just forget it.



_________________
Image
Image
Offline
 Profile  
 
 Post subject: Re: SANiK's notes, Issue 3/PRIMUS (SA2 Chunk Format)
Posted: Sat Jul 10, 2010 9:33 pm 
User avatar

Joined: Tue Feb 02, 2010 12:34 am
Posts: 327
Location: Fincastle, Virginia
It's the entire SA2 model format doc'd by SANiK and Kryslin. It's been around for a long time and I just randomly decided to post this yesterday.



_________________
My youtube account: http://www.youtube.com/user/darkspinessonic35
My little update blog: http://itseasyactually.blogspot.com

Keep it clean, or I will.
Offline
 Profile  
 
 Post subject: Re: SANiK's notes, Issue 3/PRIMUS (SA2 Chunk Format)
Posted: Sun Jul 11, 2010 4:17 am 
Member
Member
User avatar

Joined: Wed Apr 28, 2010 3:25 am
Posts: 345
Location: Western United States
Yeah, lol, I know what it is, I mean, how do you read and understand what it means?



_________________
Image
Image
Offline
 Profile  
 
 Post subject: Re: SANiK's notes, Issue 3/PRIMUS (SA2 Chunk Format)
Posted: Sun Jul 11, 2010 4:47 am 
User avatar

Joined: Tue Feb 02, 2010 12:34 am
Posts: 327
Location: Fincastle, Virginia
Just gotta learn the code I suppose. The 12+ months I've been looking at hex code for sadx allowed me to understand all of it from any format.



_________________
My youtube account: http://www.youtube.com/user/darkspinessonic35
My little update blog: http://itseasyactually.blogspot.com

Keep it clean, or I will.
Offline
 Profile  
 
 Post subject: Re: SANiK's notes, Issue 3/PRIMUS (SA2 Chunk Format)
Posted: Sun Jul 11, 2010 9:07 pm 
Member
Member
User avatar

Joined: Wed Apr 28, 2010 3:25 am
Posts: 345
Location: Western United States
Alright. Well, I suppose some day I'll get it. I'm currently still fairly new to Hex.



_________________
Image
Image
Offline
 Profile  
 
Display posts from previous:  Sort by  
 Page 1 of 1 [ 7 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 0 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  

cron

suspicion-preferred