<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 sourceHere 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...?)

[/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']

[/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]