00001
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include "../../shared/ufotypes.h"
00027 #include "../../common/mem.h"
00028 #include "../../shared/shared.h"
00029 #include "../../common/filesys.h"
00030 #include "../../common/qfiles.h"
00031 #include "../../shared/typedefs.h"
00032 #include "../../common/mem.h"
00033 #include "../../client/renderer/r_material.h"
00034 #include "../../client/renderer/r_image.h"
00035 #include "../../client/renderer/r_model.h"
00036 #include "../../client/renderer/r_state.h"
00037 #include "../../shared/images.h"
00038
00039 #define VERSION "0.2"
00040
00041 rstate_t r_state;
00042 image_t *r_noTexture;
00043
00044 typedef enum {
00045 ACTION_NONE,
00046
00047 ACTION_MDX,
00048 ACTION_SKINEDIT,
00049 ACTION_CHECK,
00050 ACTION_SKINFIX,
00051 ACTION_GLCMDSREMOVE
00052 } ufoModelAction_t;
00053
00054 typedef struct modelConfig_s {
00055 qboolean overwrite;
00056 qboolean verbose;
00057 char fileName[MAX_QPATH];
00058 ufoModelAction_t action;
00059 float smoothness;
00060 char inputName[MAX_QPATH];
00061 } modelConfig_t;
00062
00063 static modelConfig_t config;
00064
00065 struct memPool_s *com_genericPool;
00066 struct memPool_s *com_fileSysPool;
00067 struct memPool_s *vid_modelPool;
00068 struct memPool_s *vid_imagePool;
00069
00070 static void Exit(int exitCode) __attribute__ ((__noreturn__));
00071
00072 static void Exit (int exitCode)
00073 {
00074 Mem_Shutdown();
00075
00076 exit(exitCode);
00077 }
00078
00079 void Com_Printf (const char *format, ...)
00080 {
00081 char out_buffer[4096];
00082 va_list argptr;
00083
00084 va_start(argptr, format);
00085 Q_vsnprintf(out_buffer, sizeof(out_buffer), format, argptr);
00086 va_end(argptr);
00087
00088 printf("%s", out_buffer);
00089 }
00090
00091 void Com_DPrintf (int level, const char *fmt, ...)
00092 {
00093 if (config.verbose) {
00094 char outBuffer[4096];
00095 va_list argptr;
00096
00097 va_start(argptr, fmt);
00098 Q_vsnprintf(outBuffer, sizeof(outBuffer), fmt, argptr);
00099 va_end(argptr);
00100
00101 Com_Printf("%s", outBuffer);
00102 }
00103 }
00104
00105 image_t *R_LoadImageData (const char *name, byte * pic, int width, int height, imagetype_t type)
00106 {
00107 image_t *image;
00108 size_t len;
00109
00110 len = strlen(name);
00111 if (len >= sizeof(image->name))
00112 Com_Error(ERR_DROP, "R_LoadImageData: \"%s\" is too long", name);
00113 if (len == 0)
00114 Com_Error(ERR_DROP, "R_LoadImageData: name is empty");
00115
00116 image = Mem_PoolAlloc(sizeof(*image), vid_imagePool, 0);
00117 image->has_alpha = qfalse;
00118 image->type = type;
00119 image->width = width;
00120 image->height = height;
00121
00122 Q_strncpyz(image->name, name, sizeof(image->name));
00123
00124 if (len >= 4 && image->name[len - 4] == '.') {
00125 image->name[len - 4] = '\0';
00126 Com_Printf("Image with extension: '%s'\n", name);
00127 }
00128
00129 return image;
00130 }
00131
00132 image_t *R_FindImage (const char *pname, imagetype_t type)
00133 {
00134 char lname[MAX_QPATH];
00135 image_t *image;
00136 SDL_Surface *surf;
00137
00138 if (!pname || !pname[0])
00139 Com_Error(ERR_FATAL, "R_FindImage: NULL name");
00140
00141
00142 Com_StripExtension(pname, lname, sizeof(lname));
00143
00144 if (Img_LoadImage(lname, &surf)) {
00145 image = R_LoadImageData(lname, surf->pixels, surf->w, surf->h, type);
00146 SDL_FreeSurface(surf);
00147 } else {
00148 image = NULL;
00149 }
00150
00151
00152 if (!image) {
00153 Com_Printf(" \\ - could not load skin '%s'\n", pname);
00154 image = r_noTexture;
00155 }
00156
00157 return image;
00158 }
00159
00164 void Com_Error (int code, const char *fmt, ...)
00165 {
00166 va_list argptr;
00167 static char msg[1024];
00168
00169 va_start(argptr, fmt);
00170 Q_vsnprintf(msg, sizeof(msg), fmt, argptr);
00171 va_end(argptr);
00172
00173 fprintf(stderr, "Error: %s\n", msg);
00174 Exit(1);
00175 }
00176
00177
00182 static model_t *LoadModel (const char *name)
00183 {
00184 model_t *mod;
00185 byte *buf;
00186 int modfilelen;
00187
00188
00189 modfilelen = FS_LoadFile(name, &buf);
00190 if (!buf) {
00191 Com_Printf("Could not load '%s'\n", name);
00192 return NULL;
00193 }
00194
00195 mod = Mem_PoolAlloc(sizeof(*mod), vid_modelPool, 0);
00196 Q_strncpyz(mod->name, name, sizeof(mod->name));
00197
00198
00199 switch (LittleLong(*(unsigned *) buf)) {
00200 case IDALIASHEADER:
00201
00202 R_ModLoadAliasMD2Model(mod, buf, modfilelen, qfalse);
00203 break;
00204
00205 case DPMHEADER:
00206 R_ModLoadAliasDPMModel(mod, buf, modfilelen);
00207 break;
00208
00209 case IDMD3HEADER:
00210
00211 R_ModLoadAliasMD3Model(mod, buf, modfilelen);
00212 break;
00213
00214 default:
00215 if (!Q_strcasecmp(mod->name + strlen(mod->name) - 4, ".obj"))
00216 R_LoadObjModel(mod, buf, modfilelen);
00217 else
00218 Com_Error(ERR_FATAL, "R_ModForName: unknown fileid for %s", mod->name);
00219 }
00220
00221 FS_FreeFile(buf);
00222
00223 return mod;
00224 }
00225
00226 static void WriteToFile (const model_t *mod, const mAliasMesh_t *mesh, const char *fileName)
00227 {
00228 int i;
00229 qFILE f;
00230 uint32_t version = MDX_VERSION;
00231 int32_t numIndexes, numVerts, idx;
00232
00233 Com_Printf(" \\ - writing to file '%s'\n", fileName);
00234
00235 FS_OpenFile(fileName, &f, FILE_WRITE);
00236 if (!f.f) {
00237 Com_Printf(" \\ - can not open '%s' for writing\n", fileName);
00238 return;
00239 }
00240
00241 FS_Write(IDMDXHEADER, strlen(IDMDXHEADER), &f);
00242 version = LittleLong(version);
00243 FS_Write(&version, sizeof(version), &f);
00244
00245 numIndexes = LittleLong(mesh->num_tris * 3);
00246 numVerts = LittleLong(mesh->num_verts);
00247 FS_Write(&numVerts, sizeof(int32_t), &f);
00248 FS_Write(&numIndexes, sizeof(int32_t), &f);
00249
00250 for (i = 0; i < mesh->num_tris * 3; i++) {
00251 idx = LittleLong(mesh->indexes[i]);
00252 FS_Write(&idx, sizeof(int32_t), &f);
00253 }
00254
00255 FS_CloseFile(&f);
00256 }
00257
00258 static int PrecalcNormalsAndTangents (const char *filename)
00259 {
00260 char mdxFileName[MAX_QPATH];
00261 model_t *mod;
00262 int i;
00263 int cntCalculated = 0;
00264
00265 Com_Printf("- model '%s'\n", filename);
00266
00267 Com_StripExtension(filename, mdxFileName, sizeof(mdxFileName));
00268 Q_strcat(mdxFileName, ".mdx", sizeof(mdxFileName));
00269
00270 if (!config.overwrite && FS_CheckFile("%s", mdxFileName) != -1) {
00271 Com_Printf(" \\ - mdx already exists\n");
00272 return 0;
00273 }
00274
00275 mod = LoadModel(filename);
00276 if (!mod)
00277 Com_Error(ERR_DROP, "Could not load %s", filename);
00278
00279 Com_Printf(" \\ - # meshes '%i', # frames '%i'\n", mod->alias.num_meshes, mod->alias.num_frames);
00280
00281 for (i = 0; i < mod->alias.num_meshes; i++) {
00282 mAliasMesh_t *mesh = &mod->alias.meshes[i];
00283 R_ModCalcUniqueNormalsAndTangents(mesh, mod->alias.num_frames, config.smoothness);
00286 WriteToFile(mod, mesh, mdxFileName);
00287
00288 cntCalculated++;
00289 }
00290
00291 return cntCalculated;
00292 }
00293
00294 static void PrecalcNormalsAndTangentsBatch (const char *pattern)
00295 {
00296 const char *filename;
00297 int cntCalculated, cntAll;
00298
00299 FS_BuildFileList(pattern);
00300
00301 cntAll = cntCalculated = 0;
00302
00303 while ((filename = FS_NextFileFromFileList(pattern)) != NULL) {
00304 cntAll++;
00305 cntCalculated += PrecalcNormalsAndTangents(filename);
00306 }
00307
00308 FS_NextFileFromFileList(NULL);
00309
00310 Com_Printf("%i/%i\n", cntCalculated, cntAll);
00311 }
00312
00313 static void Usage (void)
00314 {
00315 Com_Printf("Usage:\n");
00316 Com_Printf(" -mdx generate mdx files\n");
00317 Com_Printf(" -skinfix fix skins for md2 models\n");
00318 Com_Printf(" -glcmds remove the unused glcmds from md2 models\n");
00319 Com_Printf(" -check perform general checks for all the models\n");
00320 Com_Printf(" -skinedit <filename> edit skin of a model\n");
00321 Com_Printf(" -overwrite overwrite existing mdx files\n");
00322 Com_Printf(" -s <float> sets the smoothness value for normal-smoothing (in the range -1.0 to 1.0)\n");
00323 Com_Printf(" -f <filename> build tangentspace for the specified model file\n");
00324 Com_Printf(" -v --verbose print debug messages\n");
00325 Com_Printf(" -h --help show this help screen\n");
00326 }
00327
00328 static void UM_DefaultParameter (void)
00329 {
00330 config.smoothness = 0.5;
00331 }
00332
00336 static void UM_Parameter (int argc, char **argv)
00337 {
00338 int i;
00339
00340 for (i = 1; i < argc; i++) {
00341 if (!strcmp(argv[i], "-overwrite")) {
00342 config.overwrite = qtrue;
00343 } else if (!strcmp(argv[i], "-f") && (i + 1 < argc)) {
00344 Q_strncpyz(config.inputName, argv[++i], sizeof(config.inputName));
00345 } else if (!strcmp(argv[i], "-s") && (i + 1 < argc)) {
00346 config.smoothness = strtod(argv[++i], NULL);
00347 if (config.smoothness < -1.0 || config.smoothness > 1.0){
00348 Usage();
00349 Exit(1);
00350 }
00351 } else if (!strcmp(argv[i], "-mdx")) {
00352 config.action = ACTION_MDX;
00353 } else if (!strcmp(argv[i], "-glcmds")) {
00354 config.action = ACTION_GLCMDSREMOVE;
00355 } else if (!strcmp(argv[i], "-skinfix")) {
00356 config.action = ACTION_SKINFIX;
00357 } else if (!strcmp(argv[i], "-check")) {
00358 config.action = ACTION_CHECK;
00359 } else if (!strcmp(argv[i], "-skinedit")) {
00360 config.action = ACTION_SKINEDIT;
00361 if (i + 1 == argc) {
00362 Usage();
00363 Exit(1);
00364 }
00365 Q_strncpyz(config.fileName, argv[i + 1], sizeof(config.fileName));
00366 i++;
00367 } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) {
00368 config.verbose = qtrue;
00369 } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
00370 Usage();
00371 Exit(0);
00372 } else {
00373 Com_Printf("Parameters unknown. Try --help.\n");
00374 Usage();
00375 Exit(1);
00376 }
00377 }
00378 }
00379
00380 static void MD2HeaderCheck (const dMD2Model_t *md2, const char *fileName, int bufSize)
00381 {
00382
00383 const uint32_t version = LittleLong(md2->version);
00384 const uint32_t numSkins = LittleLong(md2->num_skins);
00385 const uint32_t numTris = LittleLong(md2->num_tris);
00386 const uint32_t numVerts = LittleLong(md2->num_verts);
00387
00388 if (version != MD2_ALIAS_VERSION)
00389 Com_Error(ERR_DROP, "%s has wrong version number (%i should be %i)", fileName, version, MD2_ALIAS_VERSION);
00390
00391 if (bufSize != LittleLong(md2->ofs_end))
00392 Com_Error(ERR_DROP, "model %s broken offset values (%i, %i)", fileName, bufSize, LittleLong(md2->ofs_end));
00393
00394 if (numSkins == 0 || numSkins >= MD2_MAX_SKINS)
00395 Com_Error(ERR_DROP, "model '%s' has invalid skin number: %i", fileName, numSkins);
00396
00397 if (numVerts == 0 || numVerts >= MD2_MAX_VERTS)
00398 Com_Error(ERR_DROP, "model %s has too many (or no) vertices (%i/%i)", fileName, numVerts, MD2_MAX_VERTS);
00399
00400 if (numTris == 0 || numTris >= MD2_MAX_TRIANGLES)
00401 Com_Error(ERR_DROP, "model %s has too many (or no) triangles (%i/%i)", fileName, numTris, MD2_MAX_TRIANGLES);
00402 }
00403
00404 static void MD2SkinEdit (const byte *buf, const char *fileName, int bufSize, void *userData)
00405 {
00406 const char *md2Path;
00407 uint32_t numSkins;
00408 int i;
00409 const dMD2Model_t *md2 = (const dMD2Model_t *)buf;
00410
00411 MD2HeaderCheck(md2, fileName, bufSize);
00412
00413 md2Path = (const char *) md2 + LittleLong(md2->ofs_skins);
00414 numSkins = LittleLong(md2->num_skins);
00415
00416 for (i = 0; i < numSkins; i++) {
00417 const char *name = md2Path + i * MD2_MAX_SKINNAME;
00418 Com_Printf(" \\ - skin %i: %s\n", i, name);
00420 }
00421 }
00422
00423 typedef void (*modelWorker_t) (const byte *buf, const char *fileName, int bufSize, void *userData);
00424
00431 static void ModelWorker (modelWorker_t worker, const char *fileName, void *userData)
00432 {
00433 byte *buf = NULL;
00434 int modfilelen;
00435
00436
00437 modfilelen = FS_LoadFile(fileName, &buf);
00438 if (!buf)
00439 Com_Error(ERR_FATAL, "%s not found", fileName);
00440
00441 switch (LittleLong(*(unsigned *) buf)) {
00442 case IDALIASHEADER:
00443 case DPMHEADER:
00444 case IDMD3HEADER:
00445 case IDBSPHEADER:
00446 worker(buf, fileName, modfilelen, userData);
00447 break;
00448
00449 default:
00450 if (!Q_strcasecmp(fileName + strlen(fileName) - 4, ".obj"))
00451 worker(buf, fileName, modfilelen, userData);
00452 else
00453 Com_Error(ERR_DROP, "ModelWorker: unknown fileid for %s", fileName);
00454 }
00455
00456 FS_FreeFile(buf);
00457 }
00458
00459 static void MD2GLCmdsRemove (const byte *buf, const char *fileName, int bufSize, void *userData)
00460 {
00461 const char *md2Path;
00462 uint32_t numGLCmds;
00463 const dMD2Model_t *md2 = (const dMD2Model_t *)buf;
00464
00465 MD2HeaderCheck(md2, fileName, bufSize);
00466
00467 md2Path = (const char *) md2 + LittleLong(md2->ofs_glcmds);
00468 numGLCmds = LittleLong(md2->num_glcmds);
00469
00470 if (numGLCmds > 0) {
00471 dMD2Model_t *fixedMD2 = Mem_Dup(buf, bufSize);
00472 const size_t delta = numGLCmds * sizeof(uint32_t);
00473 const uint32_t offset = LittleLong(fixedMD2->ofs_glcmds);
00474
00475 if (LittleLong(fixedMD2->ofs_skins) > offset || LittleLong(fixedMD2->ofs_frames) > offset
00476 || LittleLong(fixedMD2->ofs_st) > offset || LittleLong(fixedMD2->ofs_tris) > offset) {
00477 Com_Error(ERR_DROP, "Unexpected order of the different data lumps");
00478 }
00479
00480 fixedMD2->ofs_end = LittleLong(fixedMD2->ofs_end - delta);
00481 fixedMD2->ofs_glcmds = 0;
00482 fixedMD2->num_glcmds = 0;
00483
00484 bufSize -= delta;
00485
00486 FS_WriteFile(fixedMD2, bufSize, fileName);
00487
00488 Mem_Free(fixedMD2);
00489
00490 *(size_t *)userData += delta;
00491 Com_Printf(" \\ - removed %i glcmds from '%s' (save "UFO_SIZE_T" bytes)\n",
00492 numGLCmds, fileName, delta);
00493 }
00494 }
00495
00496 static void MD2SkinFix (const byte *buf, const char *fileName, int bufSize, void *userData)
00497 {
00498 const char *md2Path;
00499 uint32_t numSkins;
00500 int i;
00501 const dMD2Model_t *md2 = (const dMD2Model_t *)buf;
00502 byte *model = NULL;
00503
00504 MD2HeaderCheck(md2, fileName, bufSize);
00505
00506 md2Path = (const char *) md2 + LittleLong(md2->ofs_skins);
00507 numSkins = LittleLong(md2->num_skins);
00508
00509 for (i = 0; i < numSkins; i++) {
00510 const char *extension;
00511 int errors = 0;
00512 const char *name = md2Path + i * MD2_MAX_SKINNAME;
00513
00514 if (name[0] != '.')
00515 errors++;
00516 else
00517
00518 name++;
00519
00520 extension = Com_GetExtension(name);
00521 if (extension != NULL)
00522 errors++;
00523
00524 if (errors > 0) {
00525 dMD2Model_t *fixedMD2;
00526 char *skinPath;
00527 char path[MD2_MAX_SKINNAME];
00528 char pathBuf[MD2_MAX_SKINNAME];
00529 const char *fixedPath;
00530 if (model == NULL) {
00531 model = Mem_Dup(buf, bufSize);
00532 Com_Printf("model: %s\n", fileName);
00533 }
00534 fixedMD2 = (dMD2Model_t *)model;
00535 skinPath = (char *) fixedMD2 + LittleLong(fixedMD2->ofs_skins) + i * MD2_MAX_SKINNAME;
00536
00537 memset(path, 0, sizeof(path));
00538
00539 if (extension != NULL) {
00540 Com_StripExtension(name, pathBuf, sizeof(pathBuf));
00541 fixedPath = pathBuf;
00542 } else {
00543 fixedPath = name;
00544 }
00545 if (name[0] != '.')
00546 Com_sprintf(path, sizeof(path), ".%s", Com_SkipPath(fixedPath));
00547 Com_Printf(" \\ - skin %i: changed path to '%s'\n", i + 1, path);
00548 if (R_AliasModelGetSkin(fileName, path) == r_noTexture) {
00549 Com_Printf(" \\ - could not load the skin with the new path\n");
00550 } else {
00551 memcpy(skinPath, path, sizeof(path));
00552 }
00553 }
00554 }
00555 if (model != NULL) {
00556 FS_WriteFile(model, bufSize, fileName);
00557 Mem_Free(model);
00558 }
00559 }
00560
00561 static void MD2Check (const byte *buf, const char *fileName, int bufSize, void *userData)
00562 {
00563 const char *md2Path;
00564 uint32_t numSkins;
00565 int i;
00566 qboolean headline = qfalse;
00567 const dMD2Model_t *md2 = (const dMD2Model_t *)buf;
00568
00569 MD2HeaderCheck(md2, fileName, bufSize);
00570
00571 md2Path = (const char *) md2 + LittleLong(md2->ofs_skins);
00572 numSkins = LittleLong(md2->num_skins);
00573
00574 for (i = 0; i < numSkins; i++) {
00575 const char *extension;
00576 int errors = 0;
00577 const char *name = md2Path + i * MD2_MAX_SKINNAME;
00578
00579 if (name[0] != '.')
00580 errors++;
00581 else
00582
00583 name++;
00584
00585 extension = Com_GetExtension(name);
00586 if (extension != NULL)
00587 errors++;
00588
00589 if (errors > 0) {
00590 if (!headline) {
00591 Com_Printf("model: %s\n", fileName);
00592 headline = qtrue;
00593 }
00594 Com_Printf(" \\ - skin %i: %s - %i errors/warnings\n", i + 1, name, errors);
00595 if (name[0] != '.')
00596 Com_Printf(" \\ - skin contains full path\n");
00597 if (extension != NULL)
00598 Com_Printf(" \\ - skin contains extension '%s'\n", extension);
00599 if (R_AliasModelGetSkin(fileName, md2Path + i * MD2_MAX_SKINNAME) == r_noTexture)
00600 Com_Printf(" \\ - could not load the skin\n");
00601 }
00602 }
00603 }
00604
00605 static void MD2Visitor (modelWorker_t worker, void *userData)
00606 {
00607 const char *fileName;
00608 const char *pattern = "**.md2";
00609
00610 FS_BuildFileList(pattern);
00611
00612 while ((fileName = FS_NextFileFromFileList(pattern)) != NULL)
00613 ModelWorker(worker, fileName, userData);
00614
00615 FS_NextFileFromFileList(NULL);
00616 }
00617
00618 static void ModelCheck (void)
00619 {
00620 MD2Visitor(MD2Check, NULL);
00621 }
00622
00623 static void SkinFix (void)
00624 {
00625 MD2Visitor(MD2SkinFix, NULL);
00626 }
00627
00628 static void GLCmdsRemove (void)
00629 {
00630 size_t bytes = 0;
00631 MD2Visitor(MD2GLCmdsRemove, &bytes);
00632 Com_Printf("Saved "UFO_SIZE_T"bytes after removing all glcmds from the md2 files\n", bytes);
00633 }
00634
00635 int main (int argc, char **argv)
00636 {
00637 Com_Printf("---- ufomodel "VERSION" ----\n");
00638
00639 UM_DefaultParameter();
00640 UM_Parameter(argc, argv);
00641
00642 if (config.action == ACTION_NONE) {
00643 Usage();
00644 Exit(1);
00645 }
00646
00647 Swap_Init();
00648 Mem_Init();
00649
00650 com_genericPool = Mem_CreatePool("ufomodel");
00651 com_fileSysPool = Mem_CreatePool("ufomodel filesys");
00652 vid_modelPool = Mem_CreatePool("ufomodel model");
00653 vid_imagePool = Mem_CreatePool("ufomodel image");
00654
00655 FS_InitFilesystem(qfalse);
00656
00657 r_noTexture = Mem_PoolAlloc(sizeof(*r_noTexture), vid_imagePool, 0);
00658 Q_strncpyz(r_noTexture->name, "noTexture", sizeof(r_noTexture->name));
00659
00660 switch (config.action) {
00661 case ACTION_MDX:
00662 if (config.inputName[0] == '\0') {
00663 PrecalcNormalsAndTangentsBatch("**.md2");
00664 PrecalcNormalsAndTangentsBatch("**.md3");
00665 PrecalcNormalsAndTangentsBatch("**.dpm");
00667
00668 } else {
00669 PrecalcNormalsAndTangents(config.inputName);
00670 }
00671 break;
00672
00673 case ACTION_SKINEDIT:
00674 ModelWorker(MD2SkinEdit, config.fileName, NULL);
00675 break;
00676
00677 case ACTION_CHECK:
00678 ModelCheck();
00679 break;
00680
00681 case ACTION_SKINFIX:
00682 SkinFix();
00683 break;
00684
00685 case ACTION_GLCMDSREMOVE:
00686 GLCmdsRemove();
00687 break;
00688
00689 default:
00690 Exit(1);
00691 }
00692
00693 Mem_Shutdown();
00694
00695 return 0;
00696 }