This manual is protected under the copyright laws of Germany and the U.S. Acknex, A4, A5, and 3D GameStudio
are trademarks of Conitec Corporation. Windows, DirectX and Direct3D are trademarks of Microsoft, Inc. Voodoo is
a trademark of 3dfx, Inc. Quake is a trademark of Id Software, Inc. Any reproduction of the material and artwork
printed herein without the written permission of Conitec is prohibited. We undertake no guarantee for the accuracy
of this manual. Conitec reserves the right to make alterations or updates without further announcement.
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-soexperienced 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.
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 CScript. The following example implements an Ldexp function (which is already available in CScript) just for demonstration purpose:
- 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.
FromC-Script,activatethisfunctionthroughdeclaringit,andthenexecuting
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
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)
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);
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.
. 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
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;
// 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);
}
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
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.
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:
// 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.
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.
CommandBytecodeArgumentsDescription
cls_fill0x01
FillerbyteforinflatingUDP
messages to a minimum length.
cls_join0x02Long Player_DPID
String Player_Name
Requestforjoiningthesession
(TCP).
cls_create0x03Long Player_DPID
String File_Name
Position Start[3]
Short Action_Index
Short Identifier
cls_remove0x04Long Entity_DPID
cls_ping0x07Long Player_DPID
cls_level0x09Long Player_DPID
String Level_Name
cls_var0x0aShort Var_Index
Short Var_Length
Fixed Var[Var_Length]
cls_string0x0bShort String_Index
String Text
Request creating anentity with
given model name, and link the
client to it (TCP).
Request removing entity onthe
server (TCP).
Sent after each client frame (UDP). If
a client does not send anything for
morethan5seconds,itis
automatically disconnected by the
server.
Inform server that client has loaded
a level (TCP).
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.
CommandBytecodeArgumentsDescription
svc_fill0x01
Filler byte for inflating TCP and
UDP messages to at least 8 bytes.
svc_create0x03Short Entity_Index
Short Identifier
svc_remove0x04Short Entity_Index
Created entity with given index
(TCP).
Removed entity from server (TCP).
svc_entsound0x05Short Entity_Index
Short Sound_Index
Scale(2000) Volume
Long Sound_Handle
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).
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;
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
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
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:
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;
, 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.
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;
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...
+ hidden pages
You need points to download manuals.
1 point = 1 manual.
You can buy points or you can get point for every manual you upload.