HMP
From GameStudio Wiki
Contents |
The HMP5 terrain format
A terrain is basically a rectangular mesh of height values with one or several surface textures. It is a variant of the GameStudio MDL5 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; // always 2
vec3 scale; // heightpoint scale factors
vec3 offset; // heightpoint 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 skin 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 A6 engine prior to 5.230, while the new "HMP5" format is used by the A6 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 if it has a different size. Further textures (up to 8) are used for material effects and shaders. You will find the first texture just after the model header, at offset baseskin = 0x54. There are numskins textures to read. The texture and pixel formats are the same as for MDL skins, and are described in detail in the MDL5 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 word unsigned short;
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[2] * z) + offset[2];
The x/y scale and offset vectors determine the terrains x and y boundaries, like this:
float xmin = offset[0];float xmax = scale[0] * 65536 + offset[0];float ymin = offset[1]; float ymax = scale[1] * 65536 + offset[1];
The X and Y position of the vertex 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_trivertxs_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;
A not animated terrain has only one frame, but can have several skin textures, like a MDL. By default, the first skin is used for the terrain texture and the second skin for the detail map. Depending on the terrain material or shader assigned in the GameStudio script, the final terrain texture can be composed from up to 8 skin textures.
Functions how to write HMPs
C++: For this code you have to include the hmp5.h and bind the hmp5.lib. You can find these files in the latest WED / MED SDK of Wlad (can be downloaded from the download page).
HMP5W DllHmpW;
//starts writing of an HMP
DLLFUNC var WriteHMP (STRING* filename)
{
void* handle;
handle = DllHmpW.Open(filename->chars);
if(handle == NULL) { return(_VAR(0)); }
return(_VAR(1));
}
//Writes the Header of an HMP
DLLFUNC var WriteHmpHeader(var header_info[9],VECTOR* min,VECTOR* max)
{
HMP5_HEADER* header;
header = &DllHmpW.Head();
DllHmpW.Head().fnumverts_x = _FLOAT(header_info[0]);
DllHmpW.Head().ftrisize_x = _FLOAT(header_info[1]);
DllHmpW.Head().ftrisize_y = _FLOAT(header_info[2]);
float v_origin[3];
float v_scale[3];
v_origin[0] = _FLOAT(min->x); v_origin[1] = _FLOAT(min->y);
v_origin[2] = _FLOAT(min->z);
v_scale[0] = _FLOAT(max->x) - v_origin[0];
v_scale[1] = _FLOAT(max->y) - v_origin[1];
v_scale[2] = _FLOAT(max->z) - v_origin[2];
v_scale[0] /= 65535.0f;
v_scale[1] /= 65535.0f;
v_scale[2] /= 65535.0f;
DllHmpW.Head().scale[0] = v_scale[0];
DllHmpW.Head().scale[1] = v_scale[1];
DllHmpW.Head().scale[2] = v_scale[2];
DllHmpW.Head().scale_origin[0] = v_origin[0];
DllHmpW.Head().scale_origin[1] = v_origin[1];
DllHmpW.Head().scale_origin[2] = v_origin[2];
return(_VAR(1));
}
//finish hmp writing
DLLFUNC var WriteHmpClose()
{
bool a = DllHmpW.Close();
return(_VAR((int)a));
}
//starts a frame
DLLFUNC var WriteHmpFrame(STRING* framename)
{
bool a = DllHmpW.Frame(framename->chars);
return(_VAR((int)a));
}
//writes one vertex
DLLFUNC var WriteHmpVertex(var height)
{
unsigned short packed_z = 0;
if(DllHmpW.Head().scale[2] == 0)
{
packed_z = 0;
}
else
{
packed_z = (short) ((_FLOAT(height) - DllHmpW.Head().scale_origin[2]) / DllHmpW.Head().scale[2]);
}
bool a = DllHmpW.Vertex(packed_z,84,0);
return(_VAR(packed_z));
}
#define SKINTYP_8BIT_PAL256 0x00
#define SKINTYP_16BIT_565 0x02
#define SKINTYP_16BIT_4444 0x03
#define SKINTYP_24BIT_888 0x04
#define SKINTYP_32BIT_8888 0x05
#define SKINTYP_MIPFLAG 0x08
#define SKINTYP_MATERIAL 0x10
#define SKINTYP_MATERIAL_ASCDEF 0x20
#define SKINTYP_RGBFLAG 0x80
#define SKINTYP_16BIT_565M (SKINTYP_16BIT_565 | SKINTYP_MIPFLAG)
#define SKINTYP_16BIT_4444M (SKINTYP_16BIT_4444 | SKINTYP_MIPFLAG)
#define SKINTYP_24BIT_888M (SKINTYP_24BIT_888 | SKINTYP_MIPFLAG)
#define SKINTYP_32BIT_8888M (SKINTYP_32BIT_8888 | SKINTYP_MIPFLAG)
//generates a mipmap
bool Create_MipMap(BYTE* mip,BYTE* bmp,int width, int height,int bpp, int scalefactor)
{
int a = 0; int b = 0; int c = 0;
while(b < width*height*bpp)
{
c = 0;
while(c < bpp)
{
mip[a+c] = bmp[b+c];
c += 1;
}
a += 1;
b += bpp*scalefactor;
}
return(true);
}
//writes a skin
DLLFUNC var WriteHmpSkin(STRING* str,BMAP* bmp)
{
int bpp = 0;
int type = 0;
bool mips = 0;
int format = _INT(bmap_lock(bmp,0));
switch ( format )
{
case 565: type = SKINTYP_16BIT_565*((bmp->flags & BMPF_MIPMAPS) ==0)
+ SKINTYP_16BIT_565M*((bmp->flags & BMPF_MIPMAPS) !=0); break;
case 4444: type = SKINTYP_16BIT_4444*((bmp->flags & BMPF_MIPMAPS) ==0)
+ SKINTYP_16BIT_4444M*((bmp->flags & BMPF_MIPMAPS)!=0); break;
case 888: type = SKINTYP_24BIT_888*((bmp->flags & BMPF_MIPMAPS) ==0)
+ SKINTYP_24BIT_888M*((bmp->flags & BMPF_MIPMAPS)!=0); break;
case 8888: type = SKINTYP_32BIT_8888*((bmp->flags & BMPF_MIPMAPS) ==0)
+ SKINTYP_32BIT_8888M*((bmp->flags & BMPF_MIPMAPS)!=0); break;
default:
return _VAR(-1);
};
if(bmp->bytespp == 3 && type == SKINTYP_32BIT_8888M)
{
type = SKINTYP_24BIT_888M;
}
if(bmp->bytespp == 3 && type == SKINTYP_32BIT_8888)
{
type = SKINTYP_24BIT_888;
}
mips = (bool) ((int)bmp->flags & BMPF_MIPMAPS);
bpp = bmp->bytespp;
if(!bpp || !type) { return(_VAR(-1)); }
int size = (_INT(bmap_width(bmp))) * (_INT(bmap_height(bmp))) * bpp;
BYTE* nmip = new BYTE[size];
for(int d=0;d<size;d++)
nmip[d] = bmp->pixels[d];
bool erfolg;
int mtrl_typ = type;
DllHmpW.Skin ( mtrl_typ, str->chars);
DllHmpW.SkinSize ( _INT(bmap_width(bmp)), _INT(bmap_height(bmp)), bpp, mips );
if(mips == true)
{
int size = (_INT(bmap_width(bmp))/2) * (_INT(bmap_height(bmp))/2) * bpp;
BYTE* mip2 = new BYTE[size];
Create_MipMap(mip2,bmp->pixels,_INT(bmap_width(bmp)),_INT(bmap_height(bmp)),bpp,4);
size = (_INT(bmap_width(bmp))/4) * (_INT(bmap_height(bmp))/4) * bpp;
BYTE* mip3 = new BYTE[size];
Create_MipMap(mip3,bmp->pixels,_INT(bmap_width(bmp)),_INT(bmap_height(bmp)),bpp,16);
size = (_INT(bmap_width(bmp))/8) * (_INT(bmap_height(bmp))/8) * bpp;
BYTE* mip4 = new BYTE[size];
Create_MipMap(mip4,bmp->pixels,_INT(bmap_width(bmp)),_INT(bmap_height(bmp)),bpp,64);
erfolg = DllHmpW.SkinBits ( nmip, mip2, mip3, mip4);
delete mip2;
delete mip3;
delete mip4;
}
else
{
erfolg = DllHmpW.SkinBits ( nmip, NULL,NULL,NULL);
}
DllHmpW.SkinDone();
bmap_unlock(bmp);
return(_VAR((int)erfolg));
}
//writes an empty skin (1 skin is needed)
DLLFUNC var WriteSkinEmpty(var width2,var height2,STRING* str)
{
int width = _INT(width2);
int height = _INT(height2);
int type = SKINTYP_8BIT_PAL256;
int bpp = 1;
DllHmpW.Skin ( type, str->chars);
DllHmpW.SkinSize ( width, height, bpp, false );
int size = width*height*bpp;
BYTE* skin = new BYTE[size];
memset(skin,0,size);
DllHmpW.SkinBits (skin, NULL, NULL, NULL);
DllHmpW.SkinDone();
return(_VAR(1));
}
//Close an open frame
DLLFUNC var WriteHmpFrameEnd()
{
bool a = DllHmpW.FrameDone();
return(_VAR((int)a));
}
Here is a sample C-Script code to write a HMP:
function Write_Terrain(ent,filename,mode_)
{
var a = 0; var b = 0;
var _min[3];
var _max[3];
var _Headerinfo[3];
p_Entity = ent; //p_entity = entity pointer
Terrain_GetSize(p_Entity,Terrain_Info); //get Size (Num Verts X / Tri Size X/Y) via a help function
_Headerinfo[0] = Terrain_Info[0]; //Num Verts X
_Headerinfo[1] = Terrain_Info[2] / p_Entity.scale_x; //triangle sizeX
_Headerinfo[2] = Terrain_Info[3] / p_Entity.scale_y; //triangle sizeY
WriteHMP(filename); //call dll function
c_setminmax(p_Entity);
_min.z = 70000;
a = 1;
b = 0;
temp = ent_frames(p_Entity);
//check all frames for min / max values of the ent
while(b < ent_frames(p_Entity))
{
while(a <= ent_vertices(p_Entity))
{
vec_for_mesh(temp,p_Entity,a);
if(temp.x < _min.x) { _min.x = temp.x; }
if(temp.y < _min.y) { _min.y = temp.y; }
if(temp.z < _min.z) { _min.z = temp.z; }
if(temp.x > _max.x) { _max.x = temp.x; }
if(temp.y > _max.y) { _max.y = temp.y; }
if(temp.z > _max.z) { _max.z = temp.z; }
a += 1;
}
a = 0;
b += 1;
}
vec_set(temp.x,_Headerinfo.x);
//write header
WriteHmpHeader(temp.x,_min,_max);
a = 1;
//write skins
while(a <= ent_skins(p_Entity))
{
p_Bmp = 0; //bitmap pointer
p_Bmp = bmap_for_entity(p_Entity,a);
if(p_Bmp != 0)
{
str_cpy(material_HelpStr1,"Skin");
str_for_num(HelpStr1,a); str_cat(material_HelpStr1,HelpStr1);
WriteHmpSkin(material_HelpStr1,p_Bmp);
}
a += 1;
}
b = 0;
//write frames
while(b < ent_frames(p_Entity))
{
a = 1;
str_cpy(material_HelpStr1,"Frame");
str_for_num(HelpStr1,a); str_cat(material_HelpStr1,HelpStr1);
WriteHmpFrame(material_HelpStr1);
while(a <= ent_vertices(p_Entity))
{
vec_for_mesh(temp.x,p_Entity,a);
WriteHmpVertex(temp.z);
a += 1;
}
WriteHmpFrameEnd();
a = 0;
b += 1;
}
WriteHmpClose();
return(1);
}
