Skip to content

Instantly share code, notes, and snippets.

@Trass3r
Last active April 18, 2023 15:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Trass3r/914222adb44941e8013d1aa77bc7deba to your computer and use it in GitHub Desktop.
Save Trass3r/914222adb44941e8013d1aa77bc7deba to your computer and use it in GitHub Desktop.
for %i in (*.kmf) do start /wait 010Editor -noui -nowarnings "-template:Dungeon Keeper 2 KMF.bt" "-script:Dungeon Keeper 2 KMF2OBJ.1sc" "%i"
// Dungeon Keeper 2 Meshes (static and vertex animated)
local int i;
struct KMSH {
char magic[4];
int size; // size of file incl. header
int version; // = 17
struct KHEAD {
char magic[4];
int size;
enum {
MESH = 1,
ANIM,
GROP
} format;
int uk; // = 1
} header;
} kmsh;
if (kmsh.header.format > 2)
return;
local int isAnim = kmsh.header.format == 2;
struct Texture {
string name;
};
struct MAT2 {
char magic[4];
int size;
string name;
int numTextures;
struct {
string name;
} textures[numTextures]<optimize=false>;
enum MaterialFlag {
HAS_ALPHA = 0x0001,
// closely related to 0x1 but both exist in isolation too
UNKNOWN2 = 0x0002, // Some sort of shininess, used e.g. in axe blade, piranha tail
ALPHA_ADDITIVE = 0x0004, // Also emits "light" / glows..?
UNKNOWN8 = 0x0008, // ice? only set on #TRANS25#icey
UNKNOWN10 = 0x0010, // only used for 'Angel Bed RibsCreature Bed_Dark Angel2'
UNKNOWN20 = 0x0020, // never used?
IsGammaSet = 0x0040, // metal? sword, pickimpback etc.
IsBrightnessSet = 0x0080, // Some sort of glow? e.g. Lava
Invisible = 0x0100 // Environment mapped, invisible guys have this???
} flags;
float brightness;
float gamma;
string envMap;
};
struct MATL {
char magic[4];
int size;
int numMat;
MAT2 materials[numMat]<optimize=false>;
} matl;
struct MESH {
char sig[4];
int size;
struct HEAD {
char head[4];
int size;
string meshname;
int numGroups; // number of SPRS groups
if (isAnim) {
int numFrames;
int numIndices;
}
int numVerts; // number of vertex positions defined in GEOM, worst case numFrames*numIndices
if (isAnim) {
enum {CLAMP, WRAP} frameFactorFunction;
}
float tx; // translation
float ty;
float tz;
if (isAnim) {
float cubeScale;
}
float scale;
int numLODs; // number of total LOD entries
} header;
struct CTRL {
char magic[4];
int size;
int numCtrl; // only non-zero in ANIM files
struct {
short uk1;
short uk2;
int uk3;
} data[numCtrl]<optimize=false>;
} ctrl;
struct Model {
char magic[4];
int size;
for (i=0; i < header.numGroups; i++)
{
struct SPHD {
char magic[4];
int size;
int numFacesPerLevel[header.numLODs]; // one entry for each LOD
int numVertsEx; // number of entries in the extended vertex list
float mmFactor;
} groups;
}
for (i=0; i < header.numGroups; i++)
{
struct SPRS
{
char magic[4];
int size;
uint materialIdx;
if (isAnim) {
struct POLY {
char magic[4];
int size;
} poly;
}
local int j;
for (j=0; j < header.numLODs; j++)
{
struct {
struct {
ubyte x,y,z; // indices for the extended vertex list
} faces[groups[i].numFacesPerLevel[j]] <read=Str("(%u %u %u)",this.x,this.y,this.z)>;
} levels;
}
if (isAnim) {
char VERTmagic[4];
int VERTsize;
}
struct VertEx {
if (!isAnim) ushort index; // index of the vertex in GEOM
ushort u; // up to 32768
ushort v;
float nX; // the vertex normal
float nY;
float nZ;
if (isAnim) ushort itabIdx;
} vertsEx[groups[i].numVertsEx] <read=Str("%u, uv(%g, %g) n(%g,%g,%g)", isAnim ? this.itabIdx : 0, this.u / 32768.0, 1.0 - this.v / 32768.0, this.nX,this.nY,this.nZ)>;
} groupData;
}
} model;
if (isAnim) {
struct ITAB {
char magic[4];
int size;
struct {
uint geomBase[header.numIndices];
} frameChunks[(uint)((header.numFrames - 1) / 128.0 + 1)]; // per 128 frames
} itab;
typedef struct {
// 10 bits per coordinate (Z, Y, X) = 30 bits (2 last bits can be thrown away)
union {
int data;
struct {
uint z : 10 <format=hex>;
uint y : 10 <format=hex>;
uint x : 10 <format=hex>;
uint garbage : 2;
} bits;
} coords;
byte frameBase;
} GeomAnim<read=GeomAnimRead>;
struct ANIMGEOM {
char magic[4];
int size;
// list of "keyframes" (positions) for each vertex
// "worst case" there is an entry for each frame
// there is at least an entry for the first and last frame
GeomAnim vertices[header.numVerts];
} geom;
struct VGEO {
char magic[4];
int size;
struct {
ubyte geomOffset[header.numFrames];
} geomOffset[header.numIndices];
} vgeo;
}
else {
struct GEOM {
char magic[4];
int size;
struct Vec3f {
float x;
float y;
float z;
} vertices[header.numVerts] <read=Str("(%g, %g, %g)", this.x, this.y, this.z)>;
} geom;
}
} mesh;
string GeomAnimRead(GeomAnim& f)
{
float x = (int)(((f.coords.data >> 20) & 1023) - 512) / 511.0f * mesh.header.scale;
float y = (int)(((f.coords.data >> 10) & 0x3ff) - 512) / 511.0f * mesh.header.scale;
float z = (int)(((f.coords.data >> 0) & 0x3ff) - 512) / 511.0f * mesh.header.scale;
string s;
SPrintf(s, "%u (%g %g %g)", f.frameBase, x, y, z);
return s;
}
// convert an open KMF to OBJ
if (kmsh.header.format > 2)
return;
uint numTotalVertexPositions = mesh.header.numVerts;
int computeAnimVertex(int frameIdx, int itabIdx)
{
uint geomBase = mesh.itab.frameChunks[frameIdx / 128].geomBase[itabIdx];
uint geomOffset = mesh.vgeo.geomOffset[itabIdx].geomOffset[frameIdx];
uint geomIndex = geomBase + geomOffset;
byte frameBase = mesh.geom.vertices[geomIndex].frameBase;
Printf("grp %d tri %d: geomIndex %d base %d frame %d\n", i, j, geomIndex, mesh.geom.vertices[geomIndex].frameBase, frameIdx);
if ((frameIdx & 0x7F) > frameBase) {
byte nextFrameBase = mesh.geom.vertices[geomIndex+1].frameBase;
float geomFactor = (float) ((frameIdx & 0x7F) - frameBase) / (float) (nextFrameBase - frameBase);
Printf("nextBase %d geomFactor %g\n", nextFrameBase, geomFactor);
float x = (int)(mesh.geom.vertices[geomIndex].coords.bits.x - 512) / 511.0f * mesh.header.scale - mesh.header.tx;
float y = (int)(mesh.geom.vertices[geomIndex].coords.bits.y - 512) / 511.0f * mesh.header.scale - mesh.header.ty;
float z = (int)(mesh.geom.vertices[geomIndex].coords.bits.z - 512) / 511.0f * mesh.header.scale - mesh.header.tz;
x = x * (1 - geomFactor) + geomFactor * ((int)(mesh.geom.vertices[geomIndex+1].coords.bits.x - 512) / 511.0f * mesh.header.scale - mesh.header.tx);
y = y * (1 - geomFactor) + geomFactor * ((int)(mesh.geom.vertices[geomIndex+1].coords.bits.y - 512) / 511.0f * mesh.header.scale - mesh.header.ty);
z = z * (1 - geomFactor) + geomFactor * ((int)(mesh.geom.vertices[geomIndex+1].coords.bits.z - 512) / 511.0f * mesh.header.scale - mesh.header.tz);
FPrintf(objFile, "v %g %g %g\n", x, -z, y);
++numTotalVertexPositions;
return numTotalVertexPositions;
}
return geomIndex + 1; // OBJ indices are 1-based
}
int sourceFile = GetFileNum();
const int isAnim = kmsh.header.format == 2;
int i, j;
int NumFaces = 0;
for (i = 0; i < mesh.header.numGroups; i++)
NumFaces += mesh.model.groups[i].numFacesPerLevel[0];
Printf("mesh numGroups: %d numVerts: %d numfaces: %d\n", mesh.header.numGroups, mesh.header.numVerts, NumFaces);
int materialFile = FileNew("Text", false);
for (i = 0; i < matl.numMat; ++i) {
FPrintf(materialFile, "newmtl %s\n", matl.materials[i].name);
FPrintf(materialFile, "map_kd %s.png\n", matl.materials[i].textures[0].name);
FPrintf(materialFile, "map_bump %s_n.png\n", matl.materials[i].textures[0].name);
}
string sourceFilename = GetFileName();
FileSelect(materialFile);
FileSave(FileNameSetExtension(sourceFilename, ".mtl"));
FileClose();
FileSelect(sourceFile);
int objFile = FileNew("Text", false);
FPrintf(objFile, "mtllib %s\n", FileNameGetBase(FileNameSetExtension(sourceFilename, ".mtl")));
FPrintf(objFile, "# %d vertices\n", mesh.header.numVerts);
// write the vertex data
for (i = 0; i < mesh.header.numVerts; i++)
{
if (isAnim)
FPrintf(objFile, "v %g %g %g\n",
(int)(mesh.geom.vertices[i].coords.bits.x - 512) / 511.0f * mesh.header.scale - mesh.header.tx,
-((int)(mesh.geom.vertices[i].coords.bits.z - 512) / 511.0f * mesh.header.scale - mesh.header.tz),
(int)(mesh.geom.vertices[i].coords.bits.y - 512) / 511.0f * mesh.header.scale - mesh.header.ty);
else
FPrintf(objFile, "v %g %g %g\n",
mesh.geom.vertices[i].x - mesh.header.tx, // TODO: * mesh.header.scale ?
-(mesh.geom.vertices[i].z - mesh.header.tz),
mesh.geom.vertices[i].y - mesh.header.ty);
}
// write the normals and UVs
for (i = 0; i < mesh.header.numGroups; i++)
{
FPrintf(objFile, "# %s %d uniq verts\n", matl.materials[mesh.model.groupData[i].materialIdx].name, mesh.model.groups[i].numVertsEx);
for (j=0; j < mesh.model.groups[i].numVertsEx; ++j)
{
#define curVertEx mesh.model.groupData[i].vertsEx[j]
FPrintf(objFile, "vt %g %g\n", curVertEx.u / 32768.0, 1.0 - curVertEx.v / 32768.0);
//FPrintf(objFile, "vn %g %g %g\n", curVertEx.nX, -curVertEx.nZ, curVertEx.nY);
}
}
if (isAnim)
FPrintf(objFile, "# %d frames\n", mesh.header.numFrames);
int v1idx, v2idx, v3idx;
int countVertExs;
int frameIdx = 0;
const int lodLevel = 0;
for (frameIdx = 0; frameIdx < (isAnim ? mesh.header.numFrames : 1); ++frameIdx)
{
FPrintf(objFile, "o %s_%d\n\n", /*mesh.header.meshname is often nonsense*/ FileNameGetBase(sourceFilename, false), frameIdx);
countVertExs = 0;
// loop through all groups
for (i=0; i < mesh.header.numGroups; i++)
{
FPrintf(objFile, "g %s_%d\n",
matl.materials[mesh.model.groupData[i].materialIdx].name, frameIdx);
//mesh.header.meshname, i, frameIdx);
FPrintf(objFile, "usemtl %s\n", matl.materials[mesh.model.groupData[i].materialIdx].name);
// loop through all faces in the selected LOD
FPrintf(objFile, "# %d faces\n", mesh.model.groups[i].numFacesPerLevel[lodLevel]);
for (j=0; j < mesh.model.groups[i].numFacesPerLevel[lodLevel]; ++j)
{
#define v1 mesh.model.groupData[i].vertsEx[mesh.model.groupData[i].levels[lodLevel].faces[j].x]
#define v2 mesh.model.groupData[i].vertsEx[mesh.model.groupData[i].levels[lodLevel].faces[j].y]
#define v3 mesh.model.groupData[i].vertsEx[mesh.model.groupData[i].levels[lodLevel].faces[j].z]
if (isAnim) {
v1idx = computeAnimVertex(frameIdx, v1.itabIdx);
v2idx = computeAnimVertex(frameIdx, v2.itabIdx);
v3idx = computeAnimVertex(frameIdx, v3.itabIdx);
} else {
v1idx = v1.index + 1;
v2idx = v2.index + 1;
v3idx = v3.index + 1;
}
FPrintf(objFile, "f %d/%d %d/%d %d/%d\n", // %d/%d/%d %d/%d/%d %d/%d/%d
v1idx,
//countVertExs + mesh.model.groupData[i].levels[lodLevel].faces[j].x+1,
countVertExs + mesh.model.groupData[i].levels[lodLevel].faces[j].x+1,
v3idx, // invert vertex order to get correct backface culling
//countVertExs + mesh.model.groupData[i].levels[lodLevel].faces[j].z+1,
countVertExs + mesh.model.groupData[i].levels[lodLevel].faces[j].z+1,
v2idx,
//countVertExs + mesh.model.groupData[i].levels[lodLevel].faces[j].y+1,
countVertExs + mesh.model.groupData[i].levels[lodLevel].faces[j].y+1
);
}
countVertExs += mesh.model.groups[i].numVertsEx;
}
}
FileSelect(objFile);
FileSave(FileNameSetExtension(sourceFilename, ".obj"));
FileClose();
if (kmsh.header.format > 2)
return;
int sourceFile = GetFileNum();
string sourceFilePath = GetFileName();
string sourceFileName = FileNameGetBase(sourceFilePath, true);
int logFile = FileOpen("log.txt", false, "Text");
//int logFile = FileNew("Text", false);
FSeek(FileSize());
FileSelect(sourceFile);
const int isAnim = kmsh.header.format == 2;
int i, j;
int NumFaces = 0;
for (i = 0; i < mesh.header.numGroups; i++)
NumFaces += mesh.model.groups[i].numFacesPerLevel[0];
FPrintf(logFile, "mesh %s: \"%s\"\n", sourceFileName, mesh.header.meshname);
FPrintf(logFile, "numGroups: %d numVerts: %d numFaces: %d numLODs: %u numFrames: %d numIndices: %d\n",
mesh.header.numGroups,
mesh.header.numVerts,
NumFaces,
mesh.header.numLODs,
isAnim ? mesh.header.numFrames : 1,
isAnim ? mesh.header.numIndices : 1);
for (i = 0; i < matl.numMat; ++i) {
FPrintf(logFile, "mtl %s\n", matl.materials[i].name);
for (j = 0; j < matl.materials[i].numTextures; ++j)
FPrintf(logFile, " tex: %s\n", matl.materials[i].textures[j].name);
if (matl.materials[i].flags)
FPrintf(logFile, " flags: 0x%X\n", matl.materials[i].flags);
if (matl.materials[i].brightness)
FPrintf(logFile, " brightness: %g\n", matl.materials[i].brightness);
if (matl.materials[i].gamma)
FPrintf(logFile, " gamma: %g\n", matl.materials[i].gamma);
if (matl.materials[i].envMap != "DefaultEnvMap")
FPrintf(logFile, " envMap: %s\n", matl.materials[i].envMap);
}
FPrintf(logFile, "Groups:\n");
for (i=0; i < mesh.header.numGroups; i++)
{
FPrintf(logFile, "vertsEx %d mat %d mm %g\n",
mesh.model.groups[i].numVertsEx,
mesh.model.groupData[i].materialIdx,
mesh.model.groups[i].mmFactor
);
for (j=0; j < mesh.header.numLODs; ++j)
FPrintf(logFile, " %d", mesh.model.groups[i].numFacesPerLevel[j]);
FPrintf(logFile, "\n");
}
FileSelect(logFile);
FileSave();
FileClose();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment