Conitec 3D GAMESTUDIO-SOURCE DEVELOPMENT User Manual

3D Gamestudio Programmer's Manual
3D GameStudio
Source Development Kit
Programmer's Manual
© Conitec July 2002
1
for A5 Engine 5.230
Johann C. Lotter / Conitec July 2002
3D Gamestudio Programmer's Manual
© Conitec July 2002
2
3D Gamestudio Programmer's Manual
© Conitec July 2002
3
Contents
The A5 DLL interface.....................................................................................................4
Getting started with the SDK...............................................................................................4
Implementing new C-Script functions................................................................................5
Writing to the screen buffer.................................................................................................5
Using Direct3D functions.....................................................................................................6
Particle functions..................................................................................................................7
Programming a game in C++...............................................................................................7
C-Script object and DLL interface structures.....................................................................8
DLL functions........................................................................................................................9
The A5 Client/Server Protocol....................................................................................12
Client Messages..................................................................................................................12
Server Messages................................................................................................................13
The MDL5 model format..............................................................................................15
MDL file header...................................................................................................................15
MDL skin format..................................................................................................................16
MDL skin vertices...............................................................................................................16
MDL mesh triangles............................................................................................................17
MDL frames.........................................................................................................................17
MDL bones..........................................................................................................................19
The HMP5 terrain format.............................................................................................20
HMP file header...................................................................................................................20
HMP texture format.............................................................................................................20
HMP height values..............................................................................................................21
3D Gamestudio Programmer's Manual
© Conitec July 2002
4
The A5 DLL interface
DLLs can be used as extensions (plugins) to the engine and to the C-Script language, as well as for programming a game totally in C++ or Delphi, instead of C-Script. The DLL interface is available on all A5 editions. For creating an A5 DLL, the SDK (source development kit) is required. SDK owners can create DLLs for adding new effects, actor AI or C-Script instructions, and distribute or sell them to other 3D GameStudio users.
The Microsoft Visual C++™ development (versions 6.0 or .NET) system normally is used for creating DLL plugins. Borland C++™, C++ Builder™,orDelphi™ can also be used. The DLL SDK contains an interface library that must be linked to any DLL. An example project with a DLL template is also provided, which makes it easy to create extensions even for not-so­experienced C or Delphi programmers who have never used DLLs before. The SDK license includes the right to freely distribute DLLs created with it, as long as the DLL functions only provide application functionality. It is not allowed to distribute DLLs that work as a 'wrapper' for the library by providing functions that allow direct access of the interface structures or the library functions from outside the DLL.
DLL extensions work bidirectionally: C-Script instructions can access C++ or Pascal DLL functions, and DLL functions can access engine and C-Script functions and variables. So you can – if you want - arbitrarily mix C-Script, C++ and Pascal in your application. On opening a DLL, the engine transfers the pointer to an internal interface structure to the interface library. The interface contains pointers to engine variables and functions, like the frame buffer, the DirectX interface, the network interface, the DirectInput interface, the level, the C-Script functions and so on. Theoretically everything - MP3 or MOD players, a physics engine, another 3D engine or even another scripting language - could be added to the engine this way.
Note that the following documentation contains just the description of the DLL interface to the C-Script API, and some examples. The API functions are described in the GameStudio Manual. The same C-Script functions are used for the DLL as well as for C-Scripts, so you'll need both manuals for creating DLL plugins.
Getting started with the SDK
You'll find the SDK either as a ZIP file on your key disk,oronaseparateSDK disk.Unzipor copy the SDK into a directory of your choice, and open it as a VC++ 6.0 project. The code for Borland C++ oder Delphi can be found in the BCPP and Delphi subfolders – read the readme text files for more information about how to create Borland and Delphi projects. The SDK comes with a DLL source,
ackdll.cpp
and
ackdll.pas,
that contains some typical examples for DLL functions. Examine the examples carefully – it's the best way to see how to program DLL functions and access engine parameters! You can use it as a 'template' for your own DLL.
On accessing system resources like sound, video, joystick and so on, the DLL must take care of possible resource conflicts. The engine shares its resources and expects the same from the code inside the DLL. For instance, code that requires exclusive access to the sound device (like some old MOD players) won't work. Some resources (like the midi player) can't be shared - if midi music is played by the DLL, the engine must not play a midi file at the same time and vice versa. Also care must be taken that for writing something into the frame buffer it must be locked before, and unlocked afterwards. The interface library provides functions for that.
3D Gamestudio Programmer's Manual
© Conitec July 2002
5
We are using VC++ for our following examples - you'll find the corresponding Pascal versions in the Delphi folders. For testing one of those DLL functions, compile the DLL, copy it into your work folder, and insert the following C-Script instructions into a function assigned to a key:
dllfunction PaintScreenWhite(x); // dll function declaration
... dll_open("ackdll.dll"); PaintScreenWhite(0); // call a DLL function dll_close(dll_handle);
All exported DLL functions must be of type
DLLFUNC fixed function(...)
, while
fixed
is a long integer interpreted by C-Script as 22.10 fixed point value. Conversion functions to/from float and integer are available in the
a5dll.h
header. The engine structs and functions accessible from DLL functions are described at the end of this chapter. All DLL functions can be declared and called in scripts just like each other C-Script function, after having activated the DLL through the
dll_open
and
dll_close
instructions that are described in the script manual.
Implementing new C-Script functions
A DLL can contain a library of new arithmetic or other functions that can be accessed by C­Script. The following example implements an Ldexp function (which is already available in C­Script) just for demonstration purpose:
// returns the value of x * 2
DLLFUNC fixed ldexp(fixed x,fixed n) {
return (FLOAT2FIX(FIX2FLOAT(x)*pow(2.0,FIX2FLOAT(n))));
}
Don'tforgetthe
FLOAT2FIX()
n
- all C-Script functions expect and return fixed point numbers.
This function can be added to the set of C-Script functions by declaring it in the script:
dllfunction ldexp(x,n); // declaration of a DLL function
After having openend the DLL, the new function can be used like each other function:
x = Ldexp(y,n); // calculates x = y * 2
n
Writing to the screen buffer
The following simple example shows how to lock the screen buffer, write into it and unlock it again. It paints the screen all white for one frame. This works in D3D as well as in 8-bit mode. From C-Script, activate this function through declaring it, and then executing
PaintScreenWhite(0)
call it in a
DLLFUNC fixed PaintScreenWhite (long unused) {
// retrieve the pointer to the screen buffer
FRAME_INTERFACE *a5fb = a5->fb;
// lock the screen buffer to get direct access to it
(*a5fb->Lock)();
// paint it all white; note the use of a5fb->pitch here
wait(1)
. You'll see a short white flash when you call this function once. If you
-loop, the screen will become all white.
3D Gamestudio Programmer's Manual
for (int j=0; j<a5fb->height; j++) {
byte *buffer = a5fb->bytes + j*a5fb->pitch; for (int i=0; i<a5fb->width*a5fb->bpp; i++)
*buffer++ = 255;
}
// unlock it so that A5 can use it again
(*a5fb->Unlock)();
return 0;
}
© Conitec July 2002
6
Using Direct3D functions
The following example shows how easy it is to use Direct3D functions for creating some effects on the screen. As all initialization is done by the engine, it is sufficient just to call the draw functions. All Direct3D functions are accessed through a
IDirect3DDevice7
pointer that is available through the DLL. For details refer to the DirectX documentation that is available, along with the DirectX 7 SDK, from the Microsoft site.
The example paints a multicolored triangle onto the screen. You'll see the triangle briefly flashing in the upper left corner when you call this function once. If you call it in a
wait(1)
loop, the triangle will be permanently on the screen. This code only works in 16- or 32-bit mode when Direct3D is activated.
-
#include <d3d.h> // from the DIRECTX7 sdk
DLLFUNC fixed PaintD3DTriangle (long unused) {
// get the active D3D device
FRAME_INTERFACE *a5fb = a5->fb; IDirect3DDevice7 *pd3ddev = (IDirect3DDevice7 *) a5fb->pd3ddev; if (!pd3ddev) return 0; // no D3D device in 8 bit mode
// define three corner vertices
D3DTLVERTEX v[3];
v[0].sx = 10.0; v[0].sy = 10.0; v[0].color = 0xFFFF0000; // the red corner v[1].sx = 310.0; v[1].sy = 10.0; v[1].color = 0xFF0000FF; // the blue corner v[2].sx = 10.0; v[2].sy = 310.0; v[2].color = 0xFF00FF00; // the green corner
v[0].sz = v[1].sz = v[2].sz = 0.0; // z buffer - paint over everything v[0].rhw = v[1].rhw = v[2].rhw = 1.0; // no perspective
// begin a scene - needed before any D3D draw operations
pd3ddev->BeginScene();
// set some render and stage states (you have to set some more for more complicated operations)
pd3ddev->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE,FALSE); pd3ddev->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_DIFFUSE); pd3ddev->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_SELECTARG2);
// now draw the triangle
pd3ddev->DrawPrimitive(D3DPT_TRIANGLEFAN,D3DFVF_TLVERTEX,(LPVOID)v,3,0);
// Normally we had to store the old render and texture states before, and // set them back here... but the simple states above do no harm
// do not forget to do a clean close of the scene
pd3ddev->EndScene(); return 0;
}
3D Gamestudio Programmer's Manual
Particle functions
© Conitec July 2002
7
DLL functions can also be used for particles, using the
A4_PARTICLE
struct defined in a5dll.h. They can be used the same way as C-Script defined particle functions. A pointer to the particle is the sole argument of a DLL particle function. Example:
// examples for a particle effect function // dllfunction DLLEffect_Explo(particle); // dllfunction DLLPart_Alphafade(particle); // start the particle effect by // effect(DLLEffect_Explo,1000,my.x,nullvector);
fixed *var_time = NULL; long func_alphafade = 0;
// helper function: fades out a particle
DLLFUNC fixed DLLPart_Alphafade(long particle) { if (!var_time || !particle) return 0; A4_PARTICLE* p = (A4_PARTICLE*) particle; p->alpha -= *var_time * 2; if (p->alpha <= 0) p->lifespan = 0; return 0; }
// helper function: return a random float
float random(float max) { return (float)(rand()*max)/(float)RAND_MAX; }
// particle effect: generate a blue explosion DLLFUNC fixed DLLEffect_Explo(long particle)
{ if (!particle) return 0;
// initialize time var and alphafade function (must only be done once)
if (!var_time) var_time = (fixed *)a5dll_getwdlobj("time"); if (!func_alphafade) func_alphafade = a5dll_getscript("DLLPart_Alphafade"); A4_PARTICLE* p = (A4_PARTICLE*) particle;
// initialize particle parameters
p->flags |= EPF_STREAK|EPF_MOVE|ENF_FLARE|ENF_BRIGHT; p->vel_x = FLOAT2FIX(random(10) - 5); p->vel_y = FLOAT2FIX(random(10) - 5); p->vel_z = FLOAT2FIX(random(10) - 5); p->red = 0; p->green = 0; p->blue = INT2FIX(255); p->alpha = FLOAT2FIX(50 + random(50)); p->function = func_alphafade; return 0; }
Programming a game in C++
Using the
A4_ENTITY
object (see below), a DLL can implement complex AI functions that would be harder to code in C-Script. Even the whole gameplay could be written in a DLL. The following example shows how to change entity parameters through a DLL function.
3D Gamestudio Programmer's Manual
// rolls the given entity by 180 degrees
DLLFUNC fixed FlipUpsideDown(long entity) {
if (!entity) return 0;
// retrieve the pointer to the given entity
A4_ENTITY *ent = (A4_ENTITY *)entity;
// set the entity's roll angle to 180 degrees
ent->roll = FLOAT2FIX(180);
return 0;
}
© Conitec July 2002
8
This would be called by C-Script through
FlipUpsideDown(my)
. For controlling entities totally through a DLL – for instance, when you intend to write your whole game in C++ or Delphi, instead of C-Script – C-Script dummy actions can be assigned to the entity, like this:
var appdll_handle; dllfunction dll_entmain(entity); dllfunction dll_entevent(entity);
function main() { // open the application DLL
appdll_handle = dll_open("myapp.dll"); ...
}
action myent_event {
dll_handle = appdll_handle; dll_entevent(my); // this DLL function handles all entity events
}
action myentity {
my.event = myent_event; while(1) {
dll_handle = appdll_handle; dll_entmain(my); // this DLL function controls the entity wait(1);
}
}
C-Script object and DLL interface structures
Pointers to C-Script objects can be transferred to DLL functions, thus allowing object manipulation. The internal engine format of the basic C-Script objects (
A4_PARTICLE, A4_BMAP, A4_TEX
etc.) is defined in the
a5dll.h
file.
A4_ENTITY
Interface structs are handed over to the DLL for accessing engine variables and pointers, which are also defined in the
typedef struct {
long dll_version; // The version is automatically tested against A5DLL_VERSION // on opening the DLL. DLLs work with engines with the same or a higher // version number, but not with a lower version engine.
WDL_INTERFACE *wdl; // access to MY and YOU pointers
FRAME_INTERFACE *fb; // access to the frame buffer and the Direct3D Device
DX_INTERFACE *dx; // access to directx pointers
ENGINE_INTERFACE *eng; // access to engine functions } A5_INTERFACE;
a5dll.h
:
,
3D Gamestudio Programmer's Manual
An
A5_INTERFACE
object nameda5is initialized on startup of each DLL and can be used for
© Conitec July 2002
accessing the screen buffer, the Direct3D Device and the accessing any C-Script object from a DLL, the
a5dll.lib
following chapter.
DLL functions
9
MY
and
YOU
entity pointers. For directly
can be used in a way described in the
Some functions in the
a5dll.lib
provide DLL access to internal C-Script variables, objects and functions. This way, entity AI can be implemented in a DLL plugin, and can use most C-Script instructions.
long a5dll_getwdlobj(char *name);
This function returns the address of the C-Script object or variable with the given name. It can be used to read or write any defined C-Script object from inside a DLL plugin. If the object does
NULL
not exist,
is returned and an error message will pop up. Examples for DLL functions that
access C-Script objects:
// adds the given value to the sky speed fixed AddToSkySpeed(fixed value) { // get the address of the variable fixed *skyspeed = (fixed *)a5dll_getwdlobj("sky_speed"); if (!skyspeed) return 0;
// add the value to both the x and y components skyspeed[0] += value; // skyspeed X value skyspeed[1] += value; // skyspeed y value
return INT2FIX(1); }
// zooms the camera view fixed ZoomIn(fixed value) { A4_VIEW *camera = (A4_VIEW *)a5dll_getwdlobj("camera"); if (!camera) return 0; return (camera->arc -= value); // change the FOV and return it }
long a5dll_getwdlfunc(char *name);
This function returns the address of the C-Script function with the given name. It can be used to call predefined C-Script functions from inside a DLL plugin. Not all C-Script functions are available for DLLs. If the function is not available (as can be the case for some special C-Script functions, like
wait()
or
inkey()),NULL
is returned and an error message will pop up. Example for an entity AI DLL function that uses C-Script functions for scanning the environment of an entity:
// returns free distance in front of MY entity until next obstacle
fixed DistAhead(long my) { if (!my) return 0;
// retrieve the pointer to the given entity
A4_ENTITY *ent = (A4_ENTITY *)my;
3D Gamestudio Programmer's Manual
// get the address of some script variables and functions fixed *tracemode = (fixed *)a5dll_getwdlobj("trace_mode"); wdlfunc2 vecrotate = (wdlfunc2)a5dll_getwdlfunc("vec_rotate"); wdlfunc2 trace = (wdlfunc2)a5dll_getwdlfunc("trace"); if (!tracemode || !trace || !vecrotate) return 0;
fixed target[3] = { FLOAT2FIX(1000.0),0,0 }; // trace target vector
// rotate vector by entity engles, just as in C-Script (*vecrotate)((long)target,(long)&(ent->pan));
// add entity position to target target[0] += ent->x; target[1] += ent->y; target[2] += ent->z;
// set trace_mode, then trace a line between entity and target, // and return the result *tracemode = INT2FIX(TRM_IGNORE_ME + TRM_IGNORE_PASSABLE + TRM_USE_BOX); return (*trace)((long)&(ent->x),(long)target); }
© Conitec July 2002
10
Some special C-Script functions, like keyboard entry, can not be called directly from a DLL. However they can be executed indirectly by calling a script that executes that function. Scripts can be called from a DLL through the following functions:
long a5dll_getscript(char *name);
This function returns a handle of the user-defined script function with the given name. It can be used to call user defined C-Script actions or functions from inside a DLL plugin. If the function
NULL
is not found,
is returned and an error message will pop up. Example for a DLL function
that calls a function that must be defined in the C-Script code:
function beeptwice() { beep; beep; } // in the script
DLLFUNC fixed WDLBeep(fixed value) {
// get the function
long beeptwice = a5dll_getscript("beeptwice"); if (!beeptwice) return 0; // "beeptwice" doesn't exist
// call it
(*a5->wdl->Call)(beeptwice); return INT2FIX(1); }
fixed a5dll_callscript(long script,long p1=0,long p2=0,long p3=0,long p4=0); fixed a5dll_callname(char *name,long p1=0,long p2=0,long p3=0,long p4=0);
These functions call a user-defined script function with given address or given name. The 4 parameters can be a fixed point number, an array, or a pointer to a C-Script object.
A4_TEX *a5dll_tex4ent(A4_ENTITY *entity,int frame,int texnum=0);
This function returns the texture of a sprite, model or terrain entity. It can be used to directly access D3D textures (see example below).
Frame
is the frame or skin number,
texnum
the
subtexture number if it is split into several subtextures.
3D Gamestudio Programmer's Manual
© Conitec July 2002
11
A4_ENTITY *a5dll_entnext(A4_ENTITY *entity);
This function enumerates local entities, and can be used to access all entities in a level. If called with NULL, it returns a pointer to the first entity in the level. If called with a level entity pointer, it returns a pointer to the next level entity. If called with a pointer to the last entity or no entity at all, it returns NULL.
Example for a function that uses DirectX for painting all local model, sprite and terrain entities red:
DLLFUNC fixed PaintEntitiesRed(void) {
// find the first entity in the level
A4_ENTITY *ent = a5dll_entnext(NULL);
while (ent) {
A4_TEX *tex = a5dll_tex4ent(ent,0,0); if (tex) {
// lock the surface and retrieve a pointer to it
DDSURFACEDESC2 ddsd2; ddsd2.dwSize = sizeof(DDSURFACEDESC2); IDirectDrawSurface7 *psurf = (IDirectDrawSurface7 *) tex->pd3dsurf; if (psurf->Lock(NULL,&ddsd2,DDLOCK_WAIT,NULL) == DD_OK) {
// get a pointer to surface memory (assume 16 bit)
WORD *dest = (WORD *)ddsd2.lpSurface; if (dest) {
// paint all non-transparent pixels red (assume pitch == width)
const WORD red = 0xF800; for (DWORD i=0; i<ddsd2.dwHeight*ddsd2.dwWidth; i++,dest++)
if (*dest) *dest = red;
}
// Unlock the surface again
psurf->Unlock(NULL);
}
}
// find the next entity
ent = a5dll_entnext(ent); } a5dll_errormessage("Entities are now red!"); return 0;
}
void a5dll_errormessage(char *text);
This function pops up an Error #1527 message requester with the given text. It can be used to display diagnostic messages, or notify the user of wrong DLL calls, like with an invalid entity pointer.
3D Gamestudio Programmer's Manual
© Conitec July 2002
12
The A5 Client/Server Protocol
The protocol is optimized for using as less bandwidth as possible. Only parameters that have changed are sent over the network. Sending a player's changed XYZ coordinate from the server to the client, for instance, needs only 12 bytes (including header). A dead reckoning mechanism is used for extrapolating positions and angles between cycles.
The structure of the messages is a single-byte code, followed by code-dependant informations. When describing the content of messages, we use the following conventions:
Byte = Short = Long = Float = Fixed = String = Angle = Position = CPosition = Scale(x) =
an unsigned integer, on one byte. a signed integer, on two bytes, Big Endian order (Intel order). a signed integer, on four bytes, Big Endian order (Intel order). a floating point number, on four bytes, Big Endian order (Intel order). a fixed point number in 22.10 format, on four bytes, Big Endian order (Intel order). a sequence of characters, terminated by 0 ('\0') a short, to be multiplied by 360.0/65535.0 to convert it to degrees.
a coordinate packed in three bytes by dividing it by 8
either
Position
or
Fixed,
depending on the
pos_resolution
variable
a value packed into one byte to be multiplied by x/255.0.
Client Messages
The following commands are used for transferring information from a client to the server.
Command Bytecode Arguments Description
cls_fill 0x01
Filler byte for inflating UDP messages to a minimum length.
cls_join 0x02 Long Player_DPID
String Player_Name
Request for joining the session (TCP).
cls_create 0x03 Long Player_DPID
String File_Name Position Start[3] Short Action_Index Short Identifier
cls_remove 0x04 Long Entity_DPID
cls_ping 0x07 Long Player_DPID
cls_level 0x09 Long Player_DPID
String Level_Name
cls_var 0x0a Short Var_Index
Short Var_Length Fixed Var[Var_Length]
cls_string 0x0b Short String_Index
String Text
Request creating an entity with given model name, and link the client to it (TCP).
Request removing entity on the server (TCP).
Sent after each client frame (UDP). If a client does not send anything for more than 5 seconds, it is automatically disconnected by the server.
Inform server that client has loaded a level (TCP).
Send a variable or an array (TCP).
Send a string (TCP).
3D Gamestudio Programmer's Manual
© Conitec July 2002
Command Bytecode Arguments Description
13
cls_skill 0x0e Long Entity_DPID
Short Struct_Offset Fixed Skill
cls_skill3 0x0f Long Entity_DPID
Short Struct_Offset Fixed Skill[3]
Send an entity skill (TCP).
Struct_Offset
of the skill in the
gives the byte offset
A4_ENTITY
struct.
Send an entity vector skill (TCP).
Server Messages
The following commands are used for transferring information from the server to either a specific client, or to all clients connected. Server-client communication uses the reliable TCP protocol for important messages, and the unreliable UDP protocol for unimportant messages.
Command Bytecode Arguments Description
svc_fill 0x01
Filler byte for inflating TCP and UDP messages to at least 8 bytes.
svc_create 0x03 Short Entity_Index
Short Identifier
svc_remove 0x04 Short Entity_Index
Created entity with given index (TCP).
Removed entity from server (TCP).
svc_entsound 0x05 Short Entity_Index
Short Sound_Index Scale(2000) Volume Long Sound_Handle
svc_effect 0x06 Short Action_Index
Short Number Position Start[3] Fixed Vel[3]
svc_info 0x07 Long 0x11191218
Byte Protocol_Version Float Server_Time Float Frame_Time
svc_var 0x0a Short Var_Index
Short Var_Length Fixed Var[Var_Length]
svc_string 0x0b Short String_Index
String Text
svc_skill 0x0e Short Entity_Index
Short Struct_Offset Fixed Skill
svc_skill3 0x0f Short Entity_Index
Short Struct_Offset Fixed Skill[3]
Play an entity sound on the clients (UDP).
Generate a particle or beam effect on the clients (UDP).
Send a sync value and the server time to the clients (TCP). This is sent once a frame.
Send a variable to all clients (TCP).
Send a string to all clients (TCP).
Send an entity skill particularly to the entity's client (TCP).
Struct_Offset
of the skill in the
gives the byte offset
A4_ENTITY
struct.
Send an entity vector skill particularly to the entity's client (TCP).
svc_update1 0x40..0x7f Short Entity_Index
(Parameters see below)
svc_update2 0x80..0xbf Short Entity_Index
(Parameters see below)
Update entity parameters 1 (UDP).
Update entity parameters 2 (UDP).
3D Gamestudio Programmer's Manual
© Conitec July 2002
Command Bytecode Arguments Description
14
svc_update3 0xc0..0xff Short Entity_Index
Update entity parameters 3 (UDP).
(Parameters see below)
For the 3 entity parameter update messages, bits 0..5 of the
svc_update
bytecode give the parameter combination to be sent or received, in the order given below. All parameters are sent through the UDP protocol.
Parameter Update.Bit Arguments Remarks
position 1.0 CPosition Pos[3] pan 1.1 Angle Pan scale 1.2 Short Scale[3] type 1.3 String File_Name frame 1.4 Short Frame_Int
Scale(1) Frame_Frc
flags1 1.5 Byte Flags ambient 2.0 Scale(100) Ambient albedo 2.1 Scale(255) Albedo skin 2.2 Byte Skin
XYZ position
XYZ scale*0.25
Name of the mdl, wmb, pcx, etc. file
Frame number, and tweening factor
Skin number
tilt 2.3 Angle Tilt roll 2.4 Angle Roll nextframe 2.5 Short Nextframe lightrange 3.0 Scale(2000) Lightrange color 3.1 Scale(255)
Blue,Green,Red
alpha 3.2 Scale(100) Alpha uv 3.3 Short U,V flags2 3.4 Byte Flags
Frame number for tweening target
RBG colour packed in 3 bytes
UV offset/16 for map entities
For instance, the code sequence
0x43 0x07 0x00 0x80 0x00 0x00 0x00 0x01 0x00 0x80 0x01 0x00 0x80 0x00
0x43
updates position and pan angle (
has bits 0 and 1 set) of entity 7 (
uses the packed format and is set at coordinates x=1 (
0x00
) and z=3 (
0x80 0x01 0x00
), and the pan angle is set at 180 degrees (
0x80 0x00 0x00)
0x07 0x00
,y=2(
0x80 0x00
). The position
0x00 0x01
).
3D Gamestudio Programmer's Manual
© Conitec July 2002
15
The MDL5 model format
Despite the engine uses model files with .MDL extension, it's internal MDL5 format differs from the Quake .MDL format. A wireframe mesh, made of triangles, gives the general shape of a model. 3D vertices define the position of triangles. For each triangle in the wireframe, there will be a corresponding triangle cut from the skin picture. Or, in other words, for each 3D vertex of a triangle that describes a XYZ position, there will be a corresponding 2D vertex positioned that describes a UV position on the skin picture.
It is not necessary that the triangle in 3D space and the triangle on the skin have the same shape (in fact, it is normally not possible for all triangles), but they should have shapes roughly similar, to limit distortion and aliasing. Several animation frames of a model are just several sets of 3D vertex positions. The 2D vertex positions always remain the same.
A MDL file contains:
- A list of skin textures in 8-bit palettized, 16-bit 565 RGB or 16 bit 4444 ARGB format.
- A list of skin vertices, that are just the UV position of vertices on the skin texture.
- A list of triangles, which describe the general shape of the model.
- A list of animation frames. Each frame holds a list of 3D vertices.
- A list of bone vertices, which are used for creating the animation frames.
MDL file header
Once the file header is read, all the other model parts can be found just by calculating their position in the file. Here is the format of the .MDL file header:
typedef float vec3[3];
typedef struct { char version[4]; // "MDL3", "MDL4", or "MDL5" long unused1; // not used vec3 scale; // 3D position scale factors. vec3 offset; // 3D position offset. long unused2; // not used vec3 unused3; // not used long numskins ; // number of skin textures long skinwidth; // width of skin texture, for MDL3 and MDL4; long skinheight; // height of skin texture, for MDL3 and MDL4; long numverts; // number of 3d wireframe vertices long numtris; // number of triangles surfaces long numframes; // number of frames long numskinverts; // number of 2D skin vertices long flags; // always 0 long numbones; // number of bone vertices (not used yet) } mdl_header;
The size of this header is 0x54 bytes (84).
The MDL3 format is used by the A4 engine, while the newer MDL4 and MDL5 formats are used by the A5 engine, the latter supporting mipmaps. After the file header follow the skins, the skin vertices, the triangles, the frames, and finally the bones (in future versions).
3D Gamestudio Programmer's Manual
© Conitec July 2002
16
MDL skin format
The model skins are flat pictures that represent the texture that should be applied on the model. There can be more than one skin. You will find the first skin just after the model header, at offset
baseskin = 0x54
in 8-bit palettized (
). The skin structure in the MDL3 and MDL4 format is:
3
typedef byte unsigned char; typedef struct { int skintype; // 0 for 8 bit (bpp == 1), 2 for 565 RGB, 3 for 4444 ARGB (bpp == 2) byte skin[skinwidth*skinheight*bpp]; // the skin picture } mdl_skin_t;
.Thereare
type == 0
numskins
skins to read. Each of these model skins is either
), in 16-bit 565 format (
type == 2
) or 16-bit 4444 format (
type ==
In the MDL5 format the skin is a little different, because now mipmaps can be stored and the
8
model skins have not necessarily the same size. If the skin contains mipmaps,
skintype
. In that case the 3 additional mipmap images follow immediately after the skin
is added to the
image. The texture width and height must be divisible by 8. 8 bit skins are not possible anymore in combination with mipmaps.
typedef word unsigned short; typedef struct {
,
long skintype; // 2 for 565 RGB, 3 for 4444 ARGB, 10 for 565 mipmapped long width,height; // size of the texture word skin[skinwidth*skinheight]; // the texture image word skin1[skinwidth/2*skinheight/2]; // the 1st mipmap (if any) word skin2[skinwidth/4*skinheight/4]; // the 2nd mipmap (if any) word skin3[skinwidth/8*skinheight/8]; // the 3rd mipmap (if any) } mdl5_skin_t;
11 for 444 mipmapped
8 bit skins are a table of bytes, which represent an index in the level palette. If the model is
565
rendered in overlay mode, index 0x00 indicates transparency. 16 bit skins in
format are a table of unsigned shorts, which represent a true colour with the upper 5 bits for the red, the middle 6 bits for the green, and the lower 5 bits for the blue component. Green has one bit more because the human eye is more sensitive to green than to other colours. If the model is rendered in overlay mode, colour value 0x0000 indicates transparency. 16 bit alpha channel skins in
4444
format are represented as a table of unsigned shorts with 4 bits for each of the alpha, red, green, and blue component.
The width and heights of skins should be a multiple of 4, to ensure long word alignement. When using mipmaps, they must be a multiple of 8. The skin pictures are usually made of as many pieces as there are independent parts in the model. For instance, for the a player, there may be two pieces that defines the body, and two others that define the gun.
MDL skin vertices
The list of skin vertices indicates only the position on texture picture, not the 3D position. That's because for a given vertex, the position on skin is constant, while the position in 3D space varies with the animation. The list of skin vertices is made of these structures:
typedef struct { short u; // position, horizontally in range 0..skinwidth-1 short v; // position, vertically in range 0..skinheight-1 } mdl_uvvert_t;
mdl_uvvert_t skinverts[numskinverts];
3D Gamestudio Programmer's Manual
© Conitec July 2002
17
u and v are the pixel position on the skin picture. The skin vertices are stored in a list, that is stored at offset
basestverts = baseskin + skinsize.skinsize
skin pictures. If they are all 8-bit skins, then
numskins skinheight * 2) * numskins
. If they are 16-bit skins without mipmaps, then
.
skinsize = (4 + skinwidth * skinheight) *
is the sum of the size of all
skinsize =
(4 + skinwidth *
MDL mesh triangles
The model wireframe mesh is made of a set of triangle facets, with vertices at the boundaries. Triangles should all be valid triangles, not degenerates (like points or lines). The triangle face must be pointing to the outside of the model. Only vertex indexes are stored in triangles. Here is the structure of triangles:
typedef struct {
short index_xyz[3]; // Index of 3 3D vertices in range 0..numverts short index_uv[3]; // Index of 3 skin vertices in range 0..numskinverts
} mdl_triangle_t;
mdl_triangle_t triangles[numtris];
At offset
basetri = baseverts + numskinverts * sizeof(uvvert_t)
in the .MDL file
you will find the triangle list.
MDL frames
A model contains a set of animation frames, which can be used in relation with the behavior of the modeled entity, so as to display it in various postures (walking, attacking, spreading its guts all over the place, etc). Basically the frame contains of vertex positions and normals. Because models can have ten thousands of vertices and hundreds of animation frames, vertex posistion are packed, and vertex normals are indicated by an index in a fixed table, to save disk and memory space.
Each frame vertex is defined by a 3D position and a normal for each of the 3D vertices in the model. In the MDL3 format, the vertices are always packed as bytes; in the MDL4 format that is used by the A5 engine they can also be packed as words (unsigned shorts). Therefore the MDL4 format allows more precise animation of huge models, and inbetweening with less distortion.
typedef struct { byte rawposition[3]; // X,Y,Z coordinate, packed on 0..255 byte lightnormalindex; // index of the vertex normal } mdl_trivertxb_t;
typedef struct { unsigned short rawposition[3]; // X,Y,Z coordinate, packed on 0..65536 byte lightnormalindex; // index of the vertex normal byte unused; } mdl_trivertxs_t;
To get the real X coordinate from the packed coordinates, multiply the X coordinate by the X scaling factor, and add the X offset. Both the scaling factor and the offset for all vertices can be found in the
float position[i] = (scale[i] * rawposition[i] ) + offset[i];
mdl_header
struct. The formula for calculating the real vertex positions is:
3D Gamestudio Programmer's Manual
© Conitec July 2002
18
The lightnormalindex field is an index to the actual vertex normal vector. This vector is the average of the normal vectors of all the faces that contain this vertex. The normal is necessary to calculate the Gouraud shading of the faces, but actually a crude estimation of the actual vertex normal is sufficient. That's why, to save space and to reduce the number of computations needed, it has been chosen to approximate each vertex normal. The ordinary values of lightnormalindex are comprised between 0 and 161, and directly map into the index of one of the 162 precalculated normal vectors:
float lightnormals[162][3] = {
{-0.525725, 0.000000, 0.850650}, {-0.442863, 0.238856, 0.864188}, {-0.295242, 0.000000, 0.955423}, {-0.309017, 0.500000, 0.809017}, {-0.162460, 0.262866, 0.951056}, {0.000000, 0.000000, 1.000000}, {0.000000, 0.850651, 0.525731}, {-0.147621, 0.716567, 0.681718}, {0.147621, 0.716567, 0.681718}, {0.000000, 0.525731, 0.850651}, {0.309017, 0.500000, 0.809017}, {0.525731, 0.000000, 0.850651}, {0.295242, 0.000000, 0.955423}, {0.442863, 0.238856, 0.864188}, {0.162460, 0.262866, 0.951056}, {-0.681718, 0.147621, 0.716567}, {-0.809017, 0.309017, 0.500000}, {-0.587785, 0.425325, 0.688191}, {-0.850651, 0.525731, 0.000000}, {-0.864188, 0.442863, 0.238856}, {-0.716567, 0.681718, 0.147621}, {-0.688191, 0.587785, 0.425325}, {-0.500000, 0.809017, 0.309017}, {-0.238856, 0.864188, 0.442863}, {-0.425325, 0.688191, 0.587785}, {-0.716567, 0.681718, -0.147621}, {-0.500000, 0.809017, -0.309017}, {-0.525731, 0.850651, 0.000000}, {0.000000, 0.850651, -0.525731}, {-0.238856, 0.864188, -0.442863}, {0.000000, 0.955423, -0.295242}, {-0.262866, 0.951056, -0.162460}, {0.000000, 1.000000, 0.000000}, {0.000000, 0.955423, 0.295242}, {-0.262866, 0.951056, 0.162460}, {0.238856, 0.864188, 0.442863}, {0.262866, 0.951056, 0.162460}, {0.500000, 0.809017, 0.309017}, {0.238856, 0.864188, -0.442863}, {0.262866, 0.951056, -0.162460}, {0.500000, 0.809017, -0.309017}, {0.850651, 0.525731, 0.000000}, {0.716567, 0.681718, 0.147621}, {0.716567, 0.681718, -0.147621}, {0.525731, 0.850651, 0.000000}, {0.425325, 0.688191, 0.587785}, {0.864188, 0.442863, 0.238856}, {0.688191, 0.587785, 0.425325}, {0.809017, 0.309017, 0.500000}, {0.681718, 0.147621, 0.716567}, {0.587785, 0.425325, 0.688191}, {0.955423, 0.295242, 0.000000}, {1.000000, 0.000000, 0.000000}, {0.951056, 0.162460, 0.262866}, {0.850651, -0.525731, 0.000000}, {0.955423, -0.295242, 0.000000}, {0.864188, -0.442863, 0.238856}, {0.951056, -0.162460, 0.262866}, {0.809017, -0.309017, 0.500000}, {0.681718, -0.147621, 0.716567}, {0.850651, 0.000000, 0.525731}, {0.864188, 0.442863, -0.238856}, {0.809017, 0.309017, -0.500000}, {0.951056, 0.162460, -0.262866}, {0.525731, 0.000000, -0.850651}, {0.681718, 0.147621, -0.716567}, {0.681718, -0.147621, -0.716567}, {0.850651, 0.000000, -0.525731}, {0.809017, -0.309017, -0.500000}, {0.864188, -0.442863, -0.238856}, {0.951056, -0.162460, -0.262866}, {0.147621, 0.716567, -0.681718}, {0.309017, 0.500000, -0.809017}, {0.425325, 0.688191, -0.587785}, {0.442863, 0.238856, -0.864188}, {0.587785, 0.425325, -0.688191}, {0.688197, 0.587780, -0.425327}, {-0.147621, 0.716567, -0.681718}, {-0.309017, 0.500000, -0.809017}, {0.000000, 0.525731, -0.850651}, {-0.525731, 0.000000, -0.850651}, {-0.442863, 0.238856, -0.864188}, {-0.295242, 0.000000, -0.955423}, {-0.162460, 0.262866, -0.951056}, {0.000000, 0.000000, -1.000000}, {0.295242, 0.000000, -0.955423}, {0.162460, 0.262866, -0.951056}, {-0.442863,-0.238856, -0.864188}, {-0.309017,-0.500000, -0.809017}, {-0.162460, -0.262866, -0.951056}, {0.000000, -0.850651, -0.525731}, {-0.147621, -0.716567, -0.681718}, {0.147621, -0.716567, -0.681718}, {0.000000, -0.525731, -0.850651}, {0.309017, -0.500000, -0.809017}, {0.442863, -0.238856, -0.864188}, {0.162460, -0.262866, -0.951056}, {0.238856, -0.864188, -0.442863}, {0.500000, -0.809017, -0.309017}, {0.425325, -0.688191, -0.587785}, {0.716567, -0.681718, -0.147621}, {0.688191, -0.587785, -0.425325}, {0.587785, -0.425325, -0.688191}, {0.000000, -0.955423, -0.295242}, {0.000000, -1.000000, 0.000000}, {0.262866, -0.951056, -0.162460}, {0.000000, -0.850651, 0.525731}, {0.000000, -0.955423, 0.295242}, {0.238856, -0.864188, 0.442863}, {0.262866, -0.951056, 0.162460}, {0.500000, -0.809017, 0.309017}, {0.716567, -0.681718, 0.147621}, {0.525731, -0.850651, 0.000000}, {-0.238856, -0.864188, -0.442863}, {-0.500000, -0.809017, -0.309017}, {-0.262866, -0.951056, -0.162460}, {-0.850651, -0.525731, 0.000000}, {-0.716567, -0.681718, -0.147621}, {-0.716567, -0.681718, 0.147621}, {-0.525731, -0.850651, 0.000000}, {-0.500000, -0.809017, 0.309017}, {-0.238856, -0.864188, 0.442863}, {-0.262866, -0.951056, 0.162460}, {-0.864188, -0.442863, 0.238856}, {-0.809017, -0.309017, 0.500000}, {-0.688191, -0.587785, 0.425325}, {-0.681718, -0.147621, 0.716567}, {-0.442863, -0.238856, 0.864188}, {-0.587785, -0.425325, 0.688191}, {-0.309017, -0.500000, 0.809017}, {-0.147621, -0.716567, 0.681718}, {-0.425325, -0.688191, 0.587785}, {-0.162460, -0.262866, 0.951056}, {0.442863, -0.238856, 0.864188}, {0.162460, -0.262866, 0.951056}, {0.309017, -0.500000, 0.809017}, {0.147621, -0.716567, 0.681718}, {0.000000, -0.525731, 0.850651}, {0.425325, -0.688191, 0.587785}, {0.587785, -0.425325, 0.688191}, {0.688191, -0.587785, 0.425325}, {-0.955423, 0.295242, 0.000000}, {-0.951056, 0.162460, 0.262866}, {-1.000000, 0.000000, 0.000000}, {-0.850651, 0.000000, 0.525731}, {-0.955423, -0.295242, 0.000000}, {-0.951056, -0.162460, 0.262866}, {-0.864188, 0.442863, -0.238856}, {-0.951056, 0.162460, -0.262866}, {-0.809017, 0.309017, -0.500000}, {-0.864188,-0.442863, -0.238856}, {-0.951056,-0.162460, -0.262866}, {-0.809017, -0.309017, -0.500000}, {-0.681718, 0.147621, -0.716567}, {-0.681718, -0.147621, -0.716567}, {-0.850651, 0.000000, -0.525731}, {-0.688191, 0.587785, -0.425325}, {-0.587785, 0.425325, -0.688191}, {-0.425325, 0.688191, -0.587785}, {-0.425325,-0.688191, -0.587785}, {-0.587785,-0.425325, -0.688191}, {-0.688197,-0.587780, -0.425327}
};
A whole frame has the following structure:
3D Gamestudio Programmer's Manual
typedef struct { long type; // 0 for byte-packed positions, and 2 for word-packed positions mdl_trivertx_t bboxmin,bboxmax; // bounding box of the frame char name[16]; // name of frame, used for animation mdl_trivertx_t vertex[numverts]; // array of vertices, either byte or short packed } mdl_frame_t;
© Conitec July 2002
19
Thesizeofeachframeis while
mdl_trivertx_t
sizeframe = 20 + (numverts+2) * sizeof(mdl_trivertx_t)
is either
mdl_trivertxb_t
or
mdl_trivertxs_t
, depending on whether the type is 0 or 2. In the MDL3 format the type is always 0. The beginning of the frames can be found in the .MDL file at offset
sizeof(mdl_triangle_t)
.
baseframes = basetri + numtris *
MDL bones
This is for future expansion of the MDL format, and not supported yet.
Bones are a linked list of 3D vertices that are used for animation in the MDL5 format. Each bone vertex can have a parent, and several childs. If a bone vertex is moved, the childs move with it. If on moving a bone vertex the connection line to his parent rotates, it's childs are rotated likewise about the parent position. If the distance of the bone vertex to its parent changes, the change is added onto the distance between childs and parent. So the movement of the childs is done in a spherical coordinate system, it is a combination of a rotation and a radius change. Each bone vertex has an influence on one or more mesh vertices. The mesh vertices influenced by a bone vertex move the same way as it's childs. If a mesh vertex is influenced by several bone vertices, it is moved by the average of the bone's movement.
,
3D Gamestudio Programmer's Manual
© Conitec July 2002
20
The HMP5 terrain format
A terrain is basically a rectangular mesh of height values with one or several surface textures. It is a simplified version of the GameStudio Model format, without all the data structures that are unnecessary for terrain.
HMP file header
Once the file header is read, all the other terrain parts can be found just by calculating their position in the file. Here is the format of the .HMP file header:
typedef float vec3[3];
typedef struct { char version[4]; // "HMP4" or "HMP5"; only the newer HMP5 format is described here long nu1,nu2,nu3; // not used float scale_z; // height scale factor. float nu4,nu5; // not used float offset_z; // height offset. long nu6; // not used float ftrisize_x; // triangle X size float ftrisize_y; // triangle Y size float fnumverts_x; // number of mesh coordinates in X direction long numskins ; // number of textures long nu8,nu9; // not used long numverts; // total number of mesh coordinates long nu10; // not used long numframes; // number of frames long nu11; // not used long flags; // always 0 long nu12; // not used } hmp_header;
The size of this header is 0x54 bytes (84).
The "HMP4" format is used by the A5 engine prior to 5.230, while the new "HMP5" format is used by the A5 engine since version 5.230. The number of vertices in the rectangular mesh can be determined by
int numverts_x = (int) fnumverts_x; int numverts_y = numverts/numverts_x;
After the file header follow the textures and then the array of height values.
HMP texture format
The terrain surface textures are flat pictures. There can be more than one texture. By default, the first texture is the terrain skin, and the second texture is the detail map. Further textures are not used yet. You will find the first texture just after the model header, at offset There are
numskins
or 16-bit 4444 format (
textures to read. Each of these textures is in 16-bit 565 format (
type == 3
). If the texture contains mipmaps,8is added to the
baseskin = 0x54
type == 2
type
.In that case the 3 additional mipmap images follow immediately after the texture image. Detail textures must always contain mipmaps. The texture width and height must be divisible by 8.
typedef word unsigned short; typedef struct { long skintype; // 2 for 565 RGB, 3 for 4444 ARGB, 10 for 565 RGB with 3 mipmaps long width,height; // size of the texture word skin[skinwidth*skinheight]; // the texture image
.
)
3D Gamestudio Programmer's Manual
word skin1[skinwidth/2*skinheight/2]; // the 1st mipmap (if any) word skin2[skinwidth/4*skinheight/4]; // the 2nd mipmap (if any) word skin3[skinwidth/8*skinheight/8]; // the 3rd mipmap (if any) } mdl5_skin_t;
© Conitec July 2002
21
The 565 and 4444 pixel formats are described in the mdl format description.
HMP height values
A terrain contains a set of animation frames, which each is a set of height values. Normally only the first frame is used, because terrain does not animate. Each mesh vertex is defined by a height value and a normal.
typedef byte unsigned char; typedef struct { word z; // height value, packed on 0..65536 byte lightnormalindex; // index of the vertex normal byte unused; // not used } hmp_trivertx_t;
To get the real Z coordinate from the packed coordinates, multiply it by the Z scaling factor, and add the Z offset. Both the scaling factor and the offset can be found in the
mdl_header
struct.
Thus the formula for calculating the real height positions is:
float height = (scale_z * z) + offset_z;
The X and Y position of the vertes results of the number of the vertex in the mesh, and thus must not be stored. The lightnormalindex field is an index to the actual vertex normal vector, just like in the MDL format description. A whole frame has the following structure:
typedef struct { long type; // always 2 mdl_trivertx_t bboxmin,bboxmax; // bounding box of the frame – see mdl description char name[16]; // name of the frame, used for animation hmp_trivertx_t height[numverts]; // array of height values } hmp_frame_t;
Loading...