From 1a446879d86a45f6f712a01d747ba04b222aa6f0 Mon Sep 17 00:00:00 2001 From: Shpoike Date: Sat, 6 Apr 2024 01:05:33 +0100 Subject: [PATCH] Preliminary CoD formats support. --- CMakeLists.txt | 23 +- engine/client/m_options.c | 3 +- engine/common/fs.c | 13 +- engine/gl/gl_shader.c | 218 ++++- engine/server/sv_ccmds.c | 8 +- engine/server/sv_init.c | 2 +- plugins/Makefile | 8 + plugins/cod/codbsp.c | 1863 +++++++++++++++++++++++++++++++++++++ plugins/cod/codiwi.c | 119 +++ plugins/cod/codmat.c | 244 +++++ plugins/cod/codmod.c | 955 +++++++++++++++++++ 11 files changed, 3411 insertions(+), 45 deletions(-) create mode 100644 plugins/cod/codbsp.c create mode 100644 plugins/cod/codiwi.c create mode 100644 plugins/cod/codmat.c create mode 100644 plugins/cod/codmod.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0441197fe..f2ffc075a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -937,22 +937,39 @@ IF(FTE_PLUG_QUAKE3) #define the modules and make sure they're linked (one generic, one for server-only builds. ADD_LIBRARY(quake3 STATIC ${FTE_Q3_FILES}) SET_TARGET_PROPERTIES(quake3 PROPERTIES COMPILE_DEFINITIONS "${FTE_LIB_DEFINES};${FTE_REVISON};BOTLIB;BOTLIB_STATIC;FTEPLUGIN;STATIC_Q3") - TARGET_LINK_LIBRARIES(quake3 m) + TARGET_LINK_LIBRARIES(quake3 ${SYS_LIBS}) #ADD_LIBRARY(q3sv STATIC EXCLUDE_FROM_ALL ${FTE_Q3_FILES}) #SET_TARGET_PROPERTIES(q3sv PROPERTIES COMPILE_DEFINITIONS "${FTE_LIB_DEFINES};${FTE_REVISON};BOTLIB;BOTLIB_STATIC;SERVERONLY") #SET_TARGET_PROPERTIES(q3sv PROPERTIES LINK_FLAGS "-Wl,--no-undefined") - #TARGET_LINK_LIBRARIES(q3sv m) + #TARGET_LINK_LIBRARIES(q3sv ${SYS_LIBS}) #SET(FTESV_LIBS ${FTESV_LIBS} q3sv) ELSE() #define the modules and make sure they're linked (one generic, one for server-only builds. ADD_LIBRARY(plug_quake3 MODULE ${FTE_Q3_FILES} plugins/plugin.c) SET_TARGET_PROPERTIES(plug_quake3 PROPERTIES COMPILE_DEFINITIONS "${FTE_LIB_DEFINES};${FTE_REVISON};BOTLIB;BOTLIB_STATIC;FTEPLUGIN") - TARGET_LINK_LIBRARIES(plug_quake3 m) + TARGET_LINK_LIBRARIES(plug_quake3 ${SYS_LIBS}) EMBED_PLUGIN_META(quake3 "Quake3 Compat" "Provides compatability with Quake3's gamecode.") ENDIF() ENDIF() +#still a wip, so disabled by default +SET(FTE_PLUG_COD true CACHE BOOL "Compile Call of Duty plugin.") +IF(FTE_PLUG_COD) + ADD_LIBRARY(plug_cod MODULE + plugins/cod/codmod.c + plugins/cod/codbsp.c + plugins/cod/codmat.c + plugins/cod/codiwi.c + #plugins/cod/codff.c + plugins/plugin.c) + SET_TARGET_PROPERTIES(plug_cod PROPERTIES COMPILE_DEFINITIONS "${FTE_LIB_DEFINES};${FTE_REVISON};FTEPLUGIN") + TARGET_LINK_LIBRARIES(plug_cod ${SYS_LIBS} + #${ZLIB_LIBRARIES} + ) + EMBED_PLUGIN_META(cod "CoD Formats" "Provides compatability with Call Of Duty's file formats.") +ENDIF() + FILE(STRINGS "${FTE_BUILD_CONFIG}" BULLET_INTERNAL REGEX "^#define[\t ]+USE_INTERNAL_BULLET") IF(BULLET_INTERNAL) #Built-in bullet physics plugin... diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 56657dfc8..07c7ed440 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -4423,7 +4423,8 @@ static int QDECL CompleteModelViewerList (const char *name, qofs_t flags, time_t || !strcmp(ext, ".psk") || !strcmp(ext, ".md5mesh") || !strcmp(ext, ".md5anim") || !strcmp(ext, ".bsp") || !strcmp(ext, ".map") || !strcmp(ext, ".hmp") || !strcmp(ext, ".spr") || !strcmp(ext, ".sp2") || !strcmp(ext, ".spr32") - || !strcmp(ext, ".gltf") || !strcmp(ext, ".glb") || !strcmp(ext, ".ase") || !strcmp(ext, ".lwo") || !strcmp(ext, ".obj")) + || !strcmp(ext, ".gltf") || !strcmp(ext, ".glb") || !strcmp(ext, ".ase") || !strcmp(ext, ".lwo") || !strcmp(ext, ".obj") + || !strncmp(name, "xmodel/", 7) || !strncmp(name, "xanim/", 6)) //urgh! { ctx->cb(name, NULL, NULL, ctx); } diff --git a/engine/common/fs.c b/engine/common/fs.c index e339116f1..95b4f8952 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -191,6 +191,10 @@ static const gamemode_info_t gamemode_info[] = { // {"-jk2", "jk2", "FTE-JK2", {"base/assets0.pk3"}, NULL, {"base", "*ftejk2"}, "Jedi Knight II: Jedi Outcast"}, // {"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"}, + + {"-cod4", NULL, "FTE-CoD4", {"cod4.ico"}, NULL, {"main", "*ftecod"}, "Call of Duty 4", NULL, "fteplug_cod"}, + {"-cod2", NULL, "FTE-CoD2", {"main/iw_00.iwd"}, NULL, {"main", "*ftecod"}, "Call of Duty 2", NULL, "fteplug_cod"}, + {"-cod", NULL, "FTE-CoD", {"Main/pak0.pk3"}, NULL, {"Main", "*ftecod"}, "Call of Duty", NULL, "fteplug_cod"}, #endif #if !defined(QUAKETC) && !defined(MINIMAL) // {"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"},"Doom"}, @@ -1554,7 +1558,7 @@ static int QDECL COM_Dir_List(const char *name, qofs_t size, time_t mtime, void } } - if ((!Q_strcasecmp(ext, "bsp") || !Q_strcasecmp(ext, "map") || !Q_strcasecmp(ext, "hmp")) && !strncmp(name, "maps/", 5) && strncmp(name, "maps/b_", 7)) + if ((!Q_strcasecmp(ext, "bsp") || !Q_strcasecmp(ext, "d3dbsp") || !Q_strcasecmp(ext, "map") || !Q_strcasecmp(ext, "hmp")) && !strncmp(name, "maps/", 5) && strncmp(name, "maps/b_", 7)) { Q_snprintfz(link, sizeof(link), "\\tip\\Change Map\\map\\%s", name+5); colour = "^4"; //disconnects @@ -1563,7 +1567,9 @@ static int QDECL COM_Dir_List(const char *name, qofs_t size, time_t mtime, void else if (!Q_strcasecmp(ext, "bsp") || !Q_strcasecmp(ext, "spr") || !Q_strcasecmp(ext, "sp2") || !Q_strcasecmp(ext, "mdl") || !Q_strcasecmp(ext, "md3") || !Q_strcasecmp(ext, "iqm") || !Q_strcasecmp(ext, "vvm") || !Q_strcasecmp(ext, "psk") || !Q_strcasecmp(ext, "dpm") || !Q_strcasecmp(ext, "zym") || !Q_strcasecmp(ext, "md5mesh") || !Q_strcasecmp(ext, "mdx") || !Q_strcasecmp(ext, "md2") || !Q_strcasecmp(ext, "obj") || !Q_strcasecmp(ext, "mds") || !Q_strcasecmp(ext, "mdc") || - !Q_strcasecmp(ext, "md5anim") || !Q_strcasecmp(ext, "gltf") || !Q_strcasecmp(ext, "glb") || !Q_strcasecmp(ext, "ase") || !Q_strcasecmp(ext, "lwo")) + !Q_strcasecmp(ext, "md5anim") || !Q_strcasecmp(ext, "gltf") || !Q_strcasecmp(ext, "glb") || !Q_strcasecmp(ext, "ase") || !Q_strcasecmp(ext, "lwo") || + ((!Q_strncasecmp(name, "xmodel/", 7)||!Q_strncasecmp(name, "xanim/", 6)) && !Q_strcasecmp(ext, "")) //urgh + ) Q_snprintfz(link, sizeof(link), "\\tip\\Open in Model Viewer\\modelviewer\\%s", name); #endif #ifdef TEXTEDITOR @@ -1578,7 +1584,7 @@ static int QDECL COM_Dir_List(const char *name, qofs_t size, time_t mtime, void #endif else if (!Q_strcasecmp(ext, "tga") || !Q_strcasecmp(ext, "png") || !Q_strcasecmp(ext, "jpg") || !Q_strcasecmp(ext, "jpeg")|| !Q_strcasecmp(ext, "lmp") || !Q_strcasecmp(ext, "ico") || !Q_strcasecmp(ext, "pcx") || !Q_strcasecmp(ext, "bmp") || !Q_strcasecmp(ext, "dds") || !Q_strcasecmp(ext, "ktx") || !Q_strcasecmp(ext, "ktx2")|| !Q_strcasecmp(ext, "vtf") || - !Q_strcasecmp(ext, "astc")|| !Q_strcasecmp(ext, "htga")|| !Q_strcasecmp(ext, "exr") || !Q_strcasecmp(ext, "xcf") || !Q_strcasecmp(ext, "psd") || + !Q_strcasecmp(ext, "astc")|| !Q_strcasecmp(ext, "htga")|| !Q_strcasecmp(ext, "exr") || !Q_strcasecmp(ext, "xcf") || !Q_strcasecmp(ext, "psd") || !Q_strcasecmp(ext, "iwi") || !Q_strcasecmp(ext, "pbm") || !Q_strcasecmp(ext, "ppm") || !Q_strcasecmp(ext, "pgm") || !Q_strcasecmp(ext, "pam") || !Q_strcasecmp(ext, "pfm") || !Q_strcasecmp(ext, "hdr") ) { //FIXME: image replacements are getting in the way here. @@ -8272,6 +8278,7 @@ void FS_RegisterDefaultFileSystems(void) FS_RegisterFileSystemType(NULL, "pkz", FSZIP_LoadArchive, true); //q2pro uses a different extension FS_RegisterFileSystemType(NULL, "pkx", FSZIP_LoadArchive, true); //q2xp naturally uses a different extension too... you'll be glad to know that yq2 uses pk3 instead. yay consistency - every engine uses something different! #endif + FS_RegisterFileSystemType(NULL, "iwd", FSZIP_LoadArchive, true); //cod2's variation. FS_RegisterFileSystemType(NULL, "apk", FSZIP_LoadArchive, false); //android package FS_RegisterFileSystemType(NULL, "zip", FSZIP_LoadArchive, false); //regular zip file (don't automatically read from these, because it gets messy) FS_RegisterFileSystemType(NULL, "kpf", FSZIP_LoadArchive, true); //regular zip file (don't automatically read from these, because it gets messy) diff --git a/engine/gl/gl_shader.c b/engine/gl/gl_shader.c index 276c5a835..c7b4acdb7 100644 --- a/engine/gl/gl_shader.c +++ b/engine/gl/gl_shader.c @@ -236,6 +236,7 @@ typedef struct shaderparsestate_s const char *forcedshader; unsigned int parseflags; //SPF_* qboolean droppass; + unsigned int oldflags; //shader flags to revert to if the pass is dropped. //for dpwater compat, used to generate a program int dpwatertype; @@ -3160,6 +3161,8 @@ shaderpass_t *Shaderpass_DefineMap(parsestate_t *ps, shaderpass_t *pass) memset(pass, 0, sizeof(*pass)); } } + else if (pass->numMergedPasses>1) + pass = ps->s->passes+ps->s->numpasses-1; //nextbundle stuff. else pass->numMergedPasses = 1; return pass; @@ -3178,6 +3181,14 @@ static void Shaderpass_Map (parsestate_t *ps, const char **ptr) token = Shader_ParseSensString (ptr); + /*cod compat*/ + if (!stricmp(token, "clamp")) + token = Shader_ParseSensString (ptr); + else if (!stricmp(token, "clampx")) + token = Shader_ParseSensString (ptr); + else if (!stricmp(token, "clampy")) + token = Shader_ParseSensString (ptr); + flags = Shader_SetImageFlags (ps, pass, &token, 0); if (!Shaderpass_MapGen(ps, pass, token)) { @@ -3350,6 +3361,92 @@ static void Shaderpass_RTCW_AnimMap_nos3tc (parsestate_t *ps, const char **ptr) Shaderpass_AnimMap(ps, ptr); } +static void Shader_BeginPass(parsestate_t *ps); +static void Shader_EndPass(parsestate_t *ps); +static void Shaderpass_CoD_NextBundle (parsestate_t *ps, const char **ptr) +{ //in a pass... end it and start the next. cos annoying. + shaderpass_t *basepass = ps->pass; + shaderpass_t *newpass; + if (!basepass->numMergedPasses) + basepass->numMergedPasses = 1; //its explicit... + if (ps->s->numpasses == SHADER_PASS_MAX || ps->s->numpasses == SHADER_TMU_MAX) + ps->droppass = true; + else + { + basepass->numMergedPasses++; + ps->s->numpasses++; + } + newpass = ps->s->passes+ps->s->numpasses-1; + memset(newpass, 0, sizeof(*newpass)); + newpass->numMergedPasses++; + + newpass->tcgen = TC_GEN_UNSPECIFIED; + newpass->shaderbits |= SBITS_SRCBLEND_DST_COLOR | SBITS_DSTBLEND_ZERO; + newpass->shaderbits |= SBITS_DEPTHFUNC_EQUAL; +/* + qboolean depthwrite = !!(ps->pass->shaderbits & SBITS_MISC_DEPTHWRITE); + qboolean dropping = ps->droppass; + Shader_EndPass(ps); + + Shader_BeginPass(ps); + ps->droppass = dropping; + //make it modulate. + ps->pass->shaderbits |= SBITS_SRCBLEND_DST_COLOR | SBITS_DSTBLEND_ZERO; + if (depthwrite) //and if the last one is doing weird alphatest crap, copy its depthfunc status. + ps->pass->shaderbits |= SBITS_DEPTHFUNC_EQUAL; +*/ +} +static void Shaderpass_CoD_Requires (parsestate_t *ps, const char **ptr) +{ + if (!Shader_EvaluateCondition(ps->s, ptr)) + ps->droppass = true; +} +static void Shaderpass_CoD_texEnvCombine (parsestate_t *ps, const char **ptr) +{ + int depth = 0; + char *token; + while (*(token = COM_ParseExt (&ps->ptr, true, true))) + { //extra parsing to even out this unexpected brace without extra warnings. + if (token[0] == '}') + depth--; + else if (token[0] == '{') + depth++; //crap. + if (!depth) + break; + } + ps->droppass = true; +} +static void Shaderpass_CoD_nvRegCombiners (parsestate_t *ps, const char **ptr) +{ + int depth = 0; + char *token; + while (*(token = COM_ParseExt (&ps->ptr, true, true))) + { //extra parsing to even out this unexpected brace without extra warnings. + if (token[0] == '}') + depth--; + else if (token[0] == '{') + depth++; //crap. + if (!depth) + break; + } + ps->droppass = true; +} +static void Shaderpass_CoD_atiFragmentShader (parsestate_t *ps, const char **ptr) +{ + int depth = 0; + char *token; + while (*(token = COM_ParseExt (&ps->ptr, true, true))) + { //extra parsing to even out this unexpected brace without extra warnings. + if (token[0] == '}') + depth--; + else if (token[0] == '{') + depth++; //crap. + if (!depth) + break; + } + ps->droppass = true; +} + static void Shaderpass_SLProgramName (shader_t *shader, shaderpass_t *pass, const char **ptr, int qrtype) { /*accepts: @@ -3433,7 +3530,8 @@ static void Shaderpass_RGBGen (parsestate_t *ps, const char **ptr) pass->rgbgen = RGB_GEN_ENTITY_LIGHTING_DIFFUSE; else if (!Q_stricmp (token, "exactvertex")) pass->rgbgen = RGB_GEN_VERTEX_EXACT; - else if (!Q_stricmp (token, "const") || !Q_stricmp (token, "constant")) + else if (!Q_stricmp (token, "const") || !Q_stricmp (token, "constant") + || !Q_stricmp (token, "constLighting")) { pass->rgbgen = RGB_GEN_CONST; pass->rgbgen_func.type = SHADER_FUNC_CONSTANT; @@ -3587,6 +3685,9 @@ static void Shaderpass_BlendFunc (parsestate_t *ps, const char **ptr) shaderpass_t *pass = ps->pass; char *token; + if (pass->numMergedPasses>1) + pass = ps->s->passes+ps->s->numpasses-1; //nextbundle stuff. + //reset to defaults pass->shaderbits &= ~(SBITS_BLEND_BITS); pass->stagetype = ST_AMBIENT; @@ -3699,6 +3800,9 @@ static void Shaderpass_TcMod (parsestate_t *ps, const char **ptr) tcmod_t *tcmod; char *token; + if (pass->numMergedPasses>1) + pass = ps->s->passes+ps->s->numpasses-1; //nextbundle stuff. + if (pass->numtcmods >= SHADER_MAX_TC_MODS) { return; @@ -3853,6 +3957,9 @@ static void Shaderpass_TcGen (parsestate_t *ps, const char **ptr) shaderpass_t *pass = ps->pass; char *token; + if (pass->numMergedPasses>1) + pass = ps->s->passes+ps->s->numpasses-1; //nextbundle stuff. + token = Shader_ParseString ( ptr ); if ( !Q_stricmp (token, "base") ) { pass->tcgen = TC_GEN_BASE; @@ -4073,6 +4180,12 @@ static shaderkey_t shaderpasskeys[] = // {"greyscale", Shaderpass_QF_Greyscale, "qf"}, // {"skip", Shaderpass_QF_Skip, "qf"}, + {"nextbundle", Shaderpass_CoD_NextBundle, "cod"}, + {"requires", Shaderpass_CoD_Requires, "cod"}, + {"texEnvCombine", Shaderpass_CoD_texEnvCombine, "cod"}, + {"nvRegCombiners", Shaderpass_CoD_nvRegCombiners, "cod"}, + {"atiFragmentShader", Shaderpass_CoD_atiFragmentShader, "cod"}, + {NULL, NULL} }; @@ -4775,14 +4888,12 @@ static qboolean Shader_Conditional_Read(parsestate_t *ps, struct scondinfo_s *co return true; } -static void Shader_Readpass (parsestate_t *ps) +static void Shader_BeginPass(parsestate_t *ps) { shader_t *shader = ps->s; - const char *token; shaderpass_t *pass; static shader_t dummy; - struct scondinfo_s cond = {0}; - unsigned int oldflags = shader->flags; + ps->oldflags = shader->flags; if ( shader->numpasses >= SHADER_PASS_MAX ) { @@ -4812,25 +4923,11 @@ static void Shader_Readpass (parsestate_t *ps) pass->flags |= SHADER_PASS_NOMIPMAP; ps->pass = pass; - - while ( ps->ptr ) - { - token = COM_ParseExt (&ps->ptr, true, true); - - if ( !token[0] ) - { - continue; - } - else if (!Shader_Conditional_Read(ps, &cond, token, &ps->ptr)) - { - if ( token[0] == '}' ) - break; - else if (token[0] == '{') - Con_Printf(CON_WARNING"%s: unexpected indentation in %s\n", ps->sourcename, shader->name); - else if ( Shader_Parsetok (ps, shaderpasskeys, token) ) - break; - } - } +} +static void Shader_EndPass(parsestate_t *ps) +{ + shader_t *shader = ps->s; + shaderpass_t *pass = ps->pass; if (pass->alphagen == ALPHA_GEN_UNDEFINED) pass->alphagen = ALPHA_GEN_IDENTITY; @@ -4839,11 +4936,6 @@ static void Shader_Readpass (parsestate_t *ps) if (!pass->numMergedPasses) pass->numMergedPasses = 1; - if (cond.depth) - { - Con_Printf("if statements without endif in shader %s\n", shader->name); - } - if (pass->tcgen == TC_GEN_UNSPECIFIED) pass->tcgen = TC_GEN_BASE; @@ -4926,10 +5018,57 @@ static void Shader_Readpass (parsestate_t *ps) Shader_FreePass (pass+--pass->numMergedPasses); shader->numpasses--; } - shader->flags = oldflags; + shader->flags = ps->oldflags; } ps->pass = NULL; } +static void Shader_Readpass (parsestate_t *ps) +{ + shader_t *shader = ps->s; + const char *token; + struct scondinfo_s cond = {0}; + + Shader_BeginPass(ps); + + while ( ps->ptr ) + { + token = COM_ParseExt (&ps->ptr, true, true); + + if ( !token[0] ) + { + continue; + } + else if (!Shader_Conditional_Read(ps, &cond, token, &ps->ptr)) + { + if ( token[0] == '}' ) + break; + else if (token[0] == '{') + { + int depth = 1; + Con_Printf(CON_WARNING"%s: unexpected indentation in %s\n", ps->sourcename, shader->name); + while (*(token = COM_ParseExt (&ps->ptr, true, true))) + { //extra parsing to even out this unexpected brace without extra warnings. + if (token[0] == '}') + { + if (depth--==0) + break; + } + else if (token[0] == '{') + depth++; //crap. + } + } + else if ( Shader_Parsetok (ps, shaderpasskeys, token) ) + break; + } + } + + if (cond.depth) + { + Con_Printf("if statements without endif in shader %s\n", shader->name); + } + + Shader_EndPass(ps); +} //we've read the first token, now make sense of it and any args static qboolean Shader_Parsetok(parsestate_t *ps, shaderkey_t *keys, const char *token) @@ -5013,7 +5152,7 @@ static void Shader_SetPassFlush (shaderpass_t *pass, shaderpass_t *pass2) return; /*rgbgen must be identity too except if the later pass is identity_ligting, in which case all is well and we can switch the first pass to identity_lighting instead*/ - if (pass2->rgbgen == RGB_GEN_IDENTITY_LIGHTING && (pass2->blendmode == PBM_OVERBRIGHT || pass2->blendmode == PBM_MODULATE) && pass->rgbgen == RGB_GEN_IDENTITY) + if (pass2->rgbgen == RGB_GEN_IDENTITY_LIGHTING && (pass2->blendmode == PBM_OVERBRIGHT || pass2->blendmode == PBM_MODULATE) && (pass->rgbgen == RGB_GEN_IDENTITY||pass->rgbgen == RGB_GEN_VERTEX_EXACT)) { if (pass->blendmode == PBM_REPLACE) pass->blendmode = PBM_REPLACELIGHT; @@ -5961,7 +6100,7 @@ void QDECL R_BuildDefaultTexnums(texnums_t *src, shader_t *shader, unsigned int unsigned int a, aframes; strcpy(imagename, shader->name); h = strchr(imagename, '#'); - if (h) + if (h && !strchr(imagename, '@')) *h = 0; if (*imagename == '/' || strchr(imagename, ':')) { //this is not security. this is anti-spam for the verbose security in the filesystem code. @@ -6409,7 +6548,7 @@ shader_t *Mod_RegisterBasicShader(struct model_s *mod, const char *texname, unsi char mapbase[64]; if (shadertext) s = R_RegisterShader(texname, usageflags, shadertext); - else if (mod->type == mod_brush) + else if (mod->type != mod_brush) s = R_RegisterCustom(mod, texname, usageflags, Shader_DefaultSkin, NULL); else s = R_RegisterCustom(mod, texname, usageflags, Shader_DefaultBSPLM, NULL); @@ -7410,7 +7549,8 @@ static qboolean Shader_ParseShader(parsestate_t *ps, const char *parsename) const char *buf = NULL; shadercachefile_t *sourcefile = NULL; const char *file; - const char *token; + const char *token="."; + size_t i; if (!strchr(parsename, ':')) { @@ -7419,7 +7559,6 @@ static qboolean Shader_ParseShader(parsestate_t *ps, const char *parsename) if (!strcmp(token, ".mat") || !*token) { char shaderfile[MAX_QPATH]; - size_t i; if (!*token) { for (i = 0; i < materialloader_count; i++) @@ -7471,6 +7610,15 @@ static qboolean Shader_ParseShader(parsestate_t *ps, const char *parsename) } } + if (*token) + { + for (i = 0; i < materialloader_count; i++) + { + if (materialloader[i].funcs->ReadMaterial(ps, parsename, Shader_LoadMaterialString)) + return true; + } + } + return false; } void R_UnloadShader(shader_t *shader) diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 1fa2927a0..2604e85dc 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -435,13 +435,15 @@ static int QDECL ShowMapList (const char *name, qofs_t flags, time_t mtime, void if (!strcmp(ext, ".gz") || !strcmp(ext, ".xz")) ext = COM_GetFileExtension (name+5, ext); //.gz files should be listed too. - if (!strcmp(ext, ".bsp")) + if (!strcmp(ext, ".bsp") || !Q_strcasecmp(ext, ".d3dbsp")) { ext = ""; //hide it cmd = stripped; //omit it, might as well. should give less confusing mapname serverinfo etc. } else if (!Q_strcasecmp(ext, ".bsp") || !Q_strcasecmp(ext, ".bsp.gz") || !Q_strcasecmp(ext, ".bsp.xz")) ; + else if (!Q_strcasecmp(ext, ".d3dbsp") || !Q_strcasecmp(ext, ".d3dbsp.gz") || !Q_strcasecmp(ext, ".d3dbsp.xz")) + ; //cod2 compat. vile. #ifdef TERRAIN else if (!Q_strcasecmp(ext, ".map") || !Q_strcasecmp(ext, ".map.gz") || !Q_strcasecmp(ext, ".hmp")) ; @@ -547,6 +549,7 @@ static void SV_Map_c(int argn, const char *partial, struct xcommandargcompletion { //FIXME: maps/mapname#modifier.ent COM_EnumerateFiles(va("maps/%s*.bsp", partial), CompleteMapList, ctx); + COM_EnumerateFiles(va("maps/%s*.d3dbsp", partial), CompleteMapList, ctx); COM_EnumerateFiles(va("maps/%s*.bsp.gz", partial), CompleteMapListExt, ctx); COM_EnumerateFiles(va("maps/%s*.bsp.xz", partial), CompleteMapListExt, ctx); COM_EnumerateFiles(va("maps/%s*.map", partial), CompleteMapListExt, ctx); @@ -557,6 +560,7 @@ static void SV_Map_c(int argn, const char *partial, struct xcommandargcompletion COM_EnumerateFiles(va("maps/%s*.ent", partial), CompleteMapListEnt, ctx); COM_EnumerateFiles(va("maps/%s*/*.bsp", partial), CompleteMapList, ctx); + COM_EnumerateFiles(va("maps/%s*/*.d3dbsp", partial), CompleteMapList, ctx); COM_EnumerateFiles(va("maps/%s*/*.bsp.gz", partial), CompleteMapListExt, ctx); COM_EnumerateFiles(va("maps/%s*/*.bsp.xz", partial), CompleteMapListExt, ctx); COM_EnumerateFiles(va("maps/%s*/*.map", partial), CompleteMapListExt, ctx); @@ -949,7 +953,7 @@ void SV_Map_f (void) else #endif { - char *exts[] = {"%s", "maps/%s", "maps/%s.bsp", "maps/%s.bsp.gz", "maps/%s.bsp.xz", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ /*"maps/%s.ent",*/ NULL}; + char *exts[] = {"%s", "maps/%s", "maps/%s.bsp", "maps/%s.bsp.gz", "maps/%s.bsp.xz", "maps/%s.d3dbsp", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ /*"maps/%s.ent",*/ NULL}; int i, j; for (i = 0; exts[i]; i++) diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index c9fe24db4..67e2add18 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -1013,7 +1013,7 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, { //.map is commented out because quite frankly, they're a bit annoying when the engine loads the gpled start.map when really you wanted to just play the damn game intead of take it apart. //if you want to load a .map, just use 'map foo.map' instead. - char *exts[] = {"%s", "maps/%s", "maps/%s.bsp", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ "maps/%s.bsp.gz", "maps/%s.bsp.xz", NULL}, *e; + char *exts[] = {"%s", "maps/%s", "maps/%s.bsp", "maps/%s.d3dbsp", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ "maps/%s.bsp.gz", "maps/%s.bsp.xz", NULL}, *e; int depth, bestdepth = FDEPTH_MISSING; flocation_t loc; time_t filetime; diff --git a/plugins/Makefile b/plugins/Makefile index f2746c872..fb7778e2e 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -515,6 +515,14 @@ $(PLUG_PREFIX)hl2$(PLUG_NATIVE_EXT): hl2/fs_vpk.c hl2/img_vtf.c hl2/mod_hl2.c hl NATIVE_PLUGINS+=hl2 ###################################### +###################################### +#for compat with cod's file formats +$(PLUG_PREFIX)cod$(PLUG_NATIVE_EXT): cod/codmod.c cod/codbsp.c cod/codmat.c cod/codiwi.c plugin.c + $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -DMULTITHREAD -o $@ -shared $(PLUG_CFLAGS) $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) + $(call EMBEDMETA,cod,$@,CoD Formats,Provides support for various formats used by Call of Duty.) +NATIVE_PLUGINS+=cod +###################################### + all: $(foreach FOO,$(NATIVE_PLUGINS), $(PLUG_PREFIX)$(FOO)$(PLUG_NATIVE_EXT)) diff --git a/plugins/cod/codbsp.c b/plugins/cod/codbsp.c new file mode 100644 index 000000000..1d04ef71d --- /dev/null +++ b/plugins/cod/codbsp.c @@ -0,0 +1,1863 @@ +// https://wiki.zeroy.com/index.php?title=Call_of_Duty_1:_d3dbsp +// https://wiki.zeroy.com/index.php?title=Call_of_Duty_2:_d3dbsp +//yes, shockingly badly documented. that's half the challenge though, right? +//vaguely derived from quake3. + +#include "../plugin.h" +#include "../engine/common/com_mesh.h" +#include "../engine/common/com_bih.h" +static plugfsfuncs_t *filefuncs; +static plugmodfuncs_t *modfuncs; +static plugthreadfuncs_t *threadfuncs; + +typedef struct +{ + //materials are used for rendering and collisions + q2mapsurface_t *surfaces; //for collision properties, texturing info, not actual surfaces. + + //generally useful stuff + unsigned int codbspver; + mplane_t *planes; + size_t num_planes; + struct codnode_s + { + mplane_t *plane; + int childnum[2]; + ivec3_t mins, maxs; + } *nodes; + size_t numnodes; + struct codleaf_s + { + int cluster; + int area; + //we don't care about what we don't understand. + //unsigned int firstleafbrush; + //unsigned int numleafbrushes; + //unsigned int firstleafsurface; + //unsigned int numleafsurfaces; + //int cell; + unsigned int firstlightindex; + unsigned int numlightindexes; + } *leaf; + size_t numleafs; + qbyte *pvsdata; + + //rendering stuff. this is pretty simple as its all soup. + mesh_t soupverts; //don't trust the indexes! + struct codsoup_s + { + unsigned int vertex_offset; + unsigned int index_offset; + unsigned int index_fixup; + } *soups; //aka surfs + + //brush collision... this stuff all goes away once we've built our BIH. + const struct codbsp_brushside_s + { //unswapped... + union{ + unsigned int plane; + float dist; + }; + unsigned int material_idx; + } *brushsides; + size_t num_brushsides; + q2cbrush_t *brushes; + size_t num_brushes; + //patch/soup collision nightmare. + const struct codpatch_s + { //unswapped... + short mat; //material? + short mode; //mode + union + { + struct + { //patch + short w; //width + short h; //height + int unknown; //unknown. flatness? + int firstvert; //first CODLUMP_COLLISIONVERTS + } mode0; + struct + { //soup + short numverts; + short numidx; + int firstvert; //first CODLUMP_COLLISIONVERTS + int firstidx; //first CODLUMP_COLLISIONINDEXES + } mode1; + }; + } *patches; + size_t numpatches; + const unsigned int *leafpatches; //loadtime only, unswapped... + size_t numleafpatches; + vecV_t *patchvertexes; + size_t numpatchvertexes; + index_t *patchindexes; + size_t numpatchindexes; + + unsigned short *lightindexes; + size_t numlightindexes; + struct codlight_s + { + int type; + vec3_t xyz; + vec3_t rgb; + vec3_t dir; + float scale; + float fov; + } *lights; + size_t numlights; +} codbspinfo_t; + +#define COD1BSP_VERSION 0x0000003b +#define COD2BSP_VERSION 0x00000004 +enum +{ + COD1LUMP_MATERIALS=0, //names, surfaceflags, and contentbits for said materials (so dedicated servers don't need to parse anything extra). + COD1LUMP_LIGHTMAPS=1, //just 2d images. 512*512 RGB ones. + COD1LUMP_PLANES=2, //used for nodes and brushsides + COD1LUMP_BRUSHSIDES=3, //which planes+materials to use for each side of the brushes... + COD1LUMP_BRUSHES=4, //defines which sets of said sides to group, and their material(contents). +// COD1LUMP_UNKNOWN=5, + COD1LUMP_SOUPS=6, //q3 would call em surfaces. except they're ALL trisoup, none are planar/patches, they're prebatched (within their 'cells'). + COD1LUMP_SOUPVERTS=7, //the vertex attributes for the soup + COD1LUMP_SOUPINDEXES=8, //the indexes, woo. how fancy. it ain't soup without this! + COD1LUMP_CULLGROUPS=9, + COD1LUMP_CULLGROUPINDEXES=10, + COD1LUMP_PORTALVERTS=11, + COD1LUMP_OCCLUDERS=12, + COD1LUMP_OCCLUDERPLANES=13, + COD1LUMP_OCCLUDEREDGES=14, + COD1LUMP_OCCLUDERINDEXES=15, + COD1LUMP_AABBTREES=16, //some sort of tree... + COD1LUMP_CELLS=17, //part of the portal rendering system (reduced number vs leafs so there's less to compute) + COD1LUMP_PORTALS=18, //for walking between cells? + COD1LUMP_LIGHTINDEXES=19, //indexes into LIGHTVALUES + COD1LUMP_NODES=20, //tree leading to leafs. + COD1LUMP_LEAFS=21, //describes a small convex area + COD1LUMP_LEAFBRUSHES=22, //list of brushes so leafs can share them, for collision+pointcontents. + COD1LUMP_LEAFPATCHES=23, //list of patches and embedded meshes per leaf, for collision + COD1LUMP_PATCHCOLLISION=24, //defines which verts/topology etc to use for each mesh/patch + COD1LUMP_COLLISIONVERTS=25, //simple verts used for patch/embedded-mesh collision + COD1LUMP_COLLISIONINDEXES=26,//indexes for the collision verts, only used for embedded meshes + COD1LUMP_MODELS=27, //sub (aka inline) models - the first entry is the worldmodel, the second is your "*1" model and upwards. + COD1LUMP_VISIBILITY=28, //numclusters, numrowbytes, then just packed rows per cluster (no compression like q3 unlike q1). + COD1LUMP_ENTITIES=29, + COD1LUMP_LIGHTS=30, +// COD1LUMP_UNKNOWN=31, +// COD1LUMP_UNKNOWN=32, + COD1LUMP_COUNT=33 +}; +enum +{ + COD2LUMP_MATERIALS=0, + COD2LUMP_LIGHTMAPS=1, + COD2LUMP_LIGHTGRIDHASH=2, + COD2LUMP_LIGHTGRIDVALUES=3, + COD2LUMP_PLANES=4, + COD2LUMP_BRUSHSIDES=5, + COD2LUMP_BRUSHES=6, + COD2LUMP_SOUPS=7, + COD2LUMP_SOUPVERTS=8, + COD2LUMP_SOUPINDEXES=9, + COD2LUMP_CULLGROUPS=10, + COD2LUMP_CULLGROUPINDEXES=11, +// COD2LUMP_UNKNOWN=12, +// COD2LUMP_UNKNOWN=13, +// COD2LUMP_UNKNOWN=14, +// COD2LUMP_UNKNOWN=15, +// COD2LUMP_UNKNOWN=16, + COD2LUMP_PORTALVERTS=17, + COD2LUMP_OCCLUDERS=18, + COD2LUMP_OCCLUDERPLANES=19, + COD2LUMP_OCCLUDEREDGES=20, + COD2LUMP_OCCLUDERINDEXES=21, + COD2LUMP_AABBTREES=22, + COD2LUMP_CELLS=23, + COD2LUMP_PORTALS=24, + COD2LUMP_NODES=25, + COD2LUMP_LEAFS=26, + COD2LUMP_LEAFBRUSHES=27, +// COD2LUMP_LEAFPATCHES=28, //o.O + COD2LUMP_COLLISIONVERTS=29, + COD2LUMP_COLLISIONEDGES=30, + COD2LUMP_COLLISIONINDEXES=31, + COD2LUMP_COLLISIONBORDERS=32, + COD2LUMP_COLLISIONPARTS=33, + COD2LUMP_COLLISIONAABBS=34, + COD2LUMP_MODELS=35, + COD2LUMP_VISIBILITY=36, + COD2LUMP_ENTITIES=37, + COD2LUMP_PATHS=38, + COD2LUMP_COUNT=39 +}; + +static struct codleaf_s *CODBSP_LeafForPoint (model_t *mod, const vec3_t p, int num) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + float d; + struct codnode_s *node; + mplane_t *plane; + + while (num >= 0) + { + node = prv->nodes + num; + plane = node->plane; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->childnum[1]; + else + num = node->childnum[0]; + } + + return &prv->leaf[-1 - num]; +} +static int CODBSP_ClusterForPoint (struct model_s *model, const vec3_t point, int *areaout) +{ + struct codleaf_s *leaf = CODBSP_LeafForPoint(model, point, 0); + if (areaout) + *areaout = leaf->area; + return leaf->cluster; +} +static qbyte *CODBSP_ClusterPVS (struct model_s *model, int cluster, pvsbuffer_t *pvsbuffer, pvsmerge_t merge) +{ + codbspinfo_t *prv = (codbspinfo_t*)model->meshinfo; + size_t i; + if (cluster >= 0 && cluster < model->numclusters) + { + qbyte *pvs = prv->pvsdata + cluster*model->pvsbytes; //packed, without compresion. + if (merge == PVM_FAST) + return pvs; + else + { + if (pvsbuffer->buffersize < model->pvsbytes) + pvsbuffer->buffer = plugfuncs->Realloc(pvsbuffer->buffer, pvsbuffer->buffersize = model->pvsbytes); + if (merge==PVM_REPLACE) + memcpy(pvsbuffer->buffer, pvs, model->pvsbytes); + else for (i = 0; i < model->pvsbytes; i++) + pvsbuffer->buffer[i] |= pvs[i]; //slooooow + return pvsbuffer->buffer; + } + } + + if (pvsbuffer) + { + if (pvsbuffer->buffersize < model->pvsbytes) + pvsbuffer->buffer = plugfuncs->Realloc(pvsbuffer->buffer, pvsbuffer->buffersize = model->pvsbytes); + if (merge!=PVM_MERGE) + memset(pvsbuffer->buffer, 0, model->pvsbytes); + return pvsbuffer->buffer; + } + return NULL; +} +//static qbyte *CODBSP_ClusterPHS (struct model_s *model, int cluster, pvsbuffer_t *pvsbuffer){return "\xff";} + + +//static void CODBSP_PurgeModel(struct model_s *mod){} + +//static qbyte *CODBSP_ClustersInSphere(struct model_s *model, const vec3_t point, float radius, pvsbuffer_t *pvsbuffer, const qbyte *fte_restrict unionwith){} + +//static size_t CODBSP_WriteAreaBits(struct model_s *model, qbyte *buffer, size_t maxbytes, int area, qboolean merge){} +//static qboolean CODBSP_AreasConnected(struct model_s *model, unsigned int area1, unsigned int area2){} +//static void CODBSP_SetAreaPortalState(struct model_s *model, unsigned int portal, unsigned int area1, unsigned int area2, qboolean open){} +//static size_t CODBSP_SaveAreaPortalBlob(struct model_s *model, void **ptr){} +//static size_t CODBSP_LoadAreaPortalBlob(struct model_s *model, void *ptr, size_t size){} + + +#ifdef HAVE_SERVER +static int leaf_count, leaf_maxcount; +static struct codleaf_s **leaf_list; +static const float *leaf_mins, *leaf_maxs; +static int leaf_topnode; +#define BoxOnPlaneSide CodBoxOnPlaneSide +static int BoxOnPlaneSide (const vec3_t emins, const vec3_t emaxs, const mplane_t *p) +{ + float dist1, dist2; + int sides; +// general case + switch (p->signbits) + { + default: + case 0: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + } + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + return sides; +} +static void CODBSP_BoxLeafs_r (codbspinfo_t *prv, int nodenum) +{ + mplane_t *plane; + struct codnode_s *node; + int s; + while (1) + { + if (nodenum < 0) + { + if (leaf_count >= leaf_maxcount) + return; + leaf_list[leaf_count++] = &prv->leaf[-1 - nodenum]; + return; + } + node = &prv->nodes[nodenum]; + plane = node->plane; + s = BOX_ON_PLANE_SIDE(leaf_mins, leaf_maxs, plane); + if (s == 1) + nodenum = node->childnum[0]; + else if (s == 2) + nodenum = node->childnum[1]; + else + { // go down both + if (leaf_topnode == -1) + leaf_topnode = nodenum; + CODBSP_BoxLeafs_r (prv, node->childnum[0]); + nodenum = node->childnum[1]; + } + } +} +static int CODBSP_BoxLeafs (model_t *mod, const vec3_t mins, const vec3_t maxs, struct codleaf_s **list, int listsize, int *topnode) +{ + leaf_list = list; + leaf_count = 0; + leaf_maxcount = listsize; + leaf_mins = mins; + leaf_maxs = maxs; + + leaf_topnode = -1; + + CODBSP_BoxLeafs_r (mod->meshinfo, 0); + + if (topnode) + *topnode = leaf_topnode; + + return leaf_count; +} +static unsigned int CODBSP_FatPVS (struct model_s *mod, const vec3_t org, pvsbuffer_t *result, qboolean merge) +{ + struct codleaf_s *leafs[64]; + int i, j, count; + vec3_t mins, maxs; + + for (i=0 ; i<3 ; i++) + { + mins[i] = org[i] - 8; + maxs[i] = org[i] + 8; + } + + count = CODBSP_BoxLeafs (mod, mins, maxs, leafs, countof(leafs), NULL); + if (count < 1) + Sys_Errorf ("CODBSP_FatPVS: count < 1"); + + //grow the buffer if needed + if (result->buffersize < mod->pvsbytes) + result->buffer = plugfuncs->Realloc(result->buffer, result->buffersize=mod->pvsbytes); + + if (count == 1 && leafs[0]->cluster == -1) + { //if the only leaf is the outside then broadcast it. + memset(result->buffer, 0xff, mod->pvsbytes); + i = count; + } + else + { + i = 0; + if (!merge) + mod->funcs.ClusterPVS(mod, leafs[i++]->cluster, result, PVM_REPLACE); + // or in all the other leaf bits + for ( ; ifuncs.ClusterPVS(mod, leafs[i]->cluster, result, PVM_MERGE); + } + } + return mod->pvsbytes; +} +static qboolean CODBSP_HeadnodeVisible (codbspinfo_t *prv, int nodenum, const qbyte *visbits) +{ + int leafnum; + int cluster; + struct codnode_s *node; + + if (nodenum < 0) + { + leafnum = -1-nodenum; + cluster = prv->leaf[leafnum].cluster; + if (cluster == -1) + return false; + if (visbits[cluster>>3] & (1<<(cluster&7))) + return true; + return false; + } + + node = &prv->nodes[nodenum]; + if (CODBSP_HeadnodeVisible(prv, node->childnum[0], visbits)) + return true; + return CODBSP_HeadnodeVisible(prv, node->childnum[1], visbits); +} +static qboolean CODBSP_EdictInFatPVS (struct model_s *mod, const struct pvscache_s *ent, const qbyte *pvs, const int *areas) +{ + int i,l; + /*int nullarea = -1; + if (areas) + { //areas[0] is the count of areas the camera is in, if valid. requires us to track portal states... + for (i = 1; ; i++) + { + if (i > areas[0]) + return false; //none of the camera's areas could see the entity + if (areas[i] == ent->areanum) + { + if (areas[i] != nullarea) + break; + //else entity is fully outside the world, invisible to all... + } + else if (CODBSP_AreasConnected (mod, areas[i], ent->areanum)) + break; + // doors can legally straddle two areas, so + // we may need to check another one + else if (ent->areanum2 != nullarea && CODBSP_AreasConnected (mod, areas[i], ent->areanum2)) + break; + } + }*/ + + if (ent->num_leafs == -1) + { // too many leafs for individual check, go by headnode + if (!CODBSP_HeadnodeVisible (mod->meshinfo, ent->headnode, pvs)) + return false; + } + else + { // check individual leafs + for (i=0 ; i < ent->num_leafs ; i++) + { + l = ent->leafnums[i]; + if (pvs[l >> 3] & (1 << (l&7) )) + break; + } + if (i == ent->num_leafs) + return false; // not visible + } + return true; +} +static void CODBSP_FindTouchedLeafs (struct model_s *model, struct pvscache_s *ent, const vec3_t mins, const vec3_t maxs) +{ +#define MAX_TOTAL_ENT_LEAFS MAX_ENT_LEAFS+1 + struct codleaf_s *leafs[MAX_TOTAL_ENT_LEAFS]; + int clusters[MAX_TOTAL_ENT_LEAFS]; + int num_leafs; + int topnode; + int i, j; + int area; + int nullarea = -1; + + //ent->num_leafs == q2's ent->num_clusters + ent->num_leafs = 0; + ent->areanum = nullarea; + ent->areanum2 = nullarea; + + if (!mins || !maxs) + return; + + //get all leafs, including solids + num_leafs = CODBSP_BoxLeafs (model, mins, maxs, leafs, MAX_TOTAL_ENT_LEAFS, &topnode); + + // set areas + for (i=0 ; icluster; //could dedupe these. + area = leafs[i]->area; + if (area != nullarea) + { // doors may legally straggle two areas, + // but nothing should ever need more than that + if (ent->areanum != nullarea && ent->areanum != area) + ent->areanum2 = area; + else + ent->areanum = area; + } + } + + if (num_leafs >= MAX_TOTAL_ENT_LEAFS) + { // assume we missed some leafs, and mark by headnode + ent->num_leafs = -1; + ent->headnode = topnode; + } + else + { + ent->num_leafs = 0; + for (i=0 ; inum_leafs == MAX_ENT_LEAFS) + { // assume we missed some leafs, and mark by headnode + ent->num_leafs = -1; + ent->headnode = topnode; + break; + } + + ent->leafnums[ent->num_leafs++] = clusters[i]; + } + } + } +} +#endif +#ifdef HAVE_CLIENT +static void CODBSP_LightPointValues (struct model_s *mod, const vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct codleaf_s *leaf = CODBSP_LeafForPoint(mod, point, 0); + unsigned short *lightindexes = prv->lightindexes + leaf->firstlightindex; + size_t i; + struct codlight_s *light; +// vec3_t move; +// float d; + float scale; + + if (!leaf->numlightindexes) + { + VectorSet(res_diffuse, 128,128,128); + VectorSet(res_ambient, 128,128,128); + VectorSet(res_dir, 1,0,0); + return; + } + + VectorSet(res_diffuse, 0,0,0); + VectorSet(res_ambient, 0,0,0); + VectorSet(res_dir, 0,0,0); + + for (i = 0; i < leaf->numlightindexes; i++, lightindexes++) + { + if (*lightindexes >= prv->numlights) + { // :( don't know what this is meant to signify, happens with the first index more often than not. + if (!prv->numlights) + continue; + light = prv->lights; + } + else + light = prv->lights + *lightindexes; + + switch (light->type) + { + case 1: //sun... + scale = 256; + break; +/* case 4: + VectorSubtract(point, light->xyz, move); + d = DotProduct(move,move); + if (d > light->scale) + continue; + scale = (light->scale-d)/d; + scale *= 256; + break;*/ +/* case 5: + break;*/ +/* case 7: + break;*/ + default: + continue; + } + VectorMA(res_diffuse, scale, light->rgb, res_diffuse); + VectorMA(res_ambient, scale, light->rgb, res_ambient); + VectorMA(res_dir, scale, light->dir, res_dir); + + break; //:( + } + + scale = DotProduct(res_dir,res_dir); + if (scale <= 0) + VectorSet(res_dir, 1,0,0); + else + VectorScale(res_dir, 1/scale, res_dir); +} +//static void CODBSP_GenerateShadowMesh (struct model_s *model, dlight_t *dl, const qbyte *lvis, qbyte *truevis, void(*callback)(struct msurface_s*)){} +//static void CODBSP_StainNode (struct model_s *model, float *parms){} +//static void CODBSP_MarkLights (struct dlight_s *light, dlightbitmask_t bit, struct mnode_s *node){} + +static void CODBSP_BuildSurfMesh(model_t *mod, msurface_t *surf, builddata_t *bd) +{ //just builds the actual mesh data, now that it has per-batch storage allocated. + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + mesh_t *mesh = surf->mesh; + struct codsoup_s *soup = &prv->soups[surf-mod->surfaces]; + int i; + + mesh->istrifan = false; + + memcpy(mesh->xyz_array, prv->soupverts.xyz_array+soup->vertex_offset, sizeof(*mesh->xyz_array)*mesh->numvertexes); + memcpy(mesh->normals_array, prv->soupverts.normals_array+soup->vertex_offset, sizeof(*mesh->normals_array)*mesh->numvertexes); + for (i = 0; i < mesh->numvertexes; i++) + Vector4Scale(prv->soupverts.colors4b_array[soup->vertex_offset+i], 1.f/255, mesh->colors4f_array[0][i]); + //memcpy(mesh->colors4b_array, prv->soupverts.colors4b_array+soup->vertex_offset, sizeof(*mesh->colors4b_array)*mesh->numvertexes); + memcpy(mesh->st_array, prv->soupverts.st_array+soup->vertex_offset, sizeof(*mesh->st_array)*mesh->numvertexes); + memcpy(mesh->lmst_array[0], prv->soupverts.lmst_array[0]+soup->vertex_offset, sizeof(*mesh->lmst_array[0])*mesh->numvertexes); + + if (soup->index_fixup) + { + for (i = 0; i < mesh->numindexes; i++) + mesh->indexes[i] = prv->soupverts.indexes[soup->index_offset+i] - soup->index_fixup; + } + else + memcpy(mesh->indexes, prv->soupverts.indexes+soup->index_offset, sizeof(*mesh->indexes)*mesh->numindexes); + + if (prv->soupverts.snormals_array) + { //cod2 made them explicit. yay. + memcpy(mesh->snormals_array, prv->soupverts.snormals_array+soup->vertex_offset, sizeof(*mesh->snormals_array)*mesh->numvertexes); + memcpy(mesh->tnormals_array, prv->soupverts.tnormals_array+soup->vertex_offset, sizeof(*mesh->tnormals_array)*mesh->numvertexes); + } + else + { //compute the tangents for rtlights. + modfuncs->AccumulateTextureVectors(mesh->xyz_array, mesh->st_array, mesh->normals_array, mesh->snormals_array, mesh->tnormals_array, mesh->indexes, mesh->numindexes, false); + modfuncs->NormaliseTextureVectors(mesh->normals_array, mesh->snormals_array, mesh->tnormals_array, mesh->numvertexes, false); + } +} +static void CODBSP_GenerateMaterials(void *ctx, void *data, size_t a, size_t b) +{ + model_t *mod = ctx; + const char *script; + + if (!a) + { //submodels share textures, so only do this if 'a' is 0 (inline index, 0 = world). + for(a = 0; a < mod->numtextures; a++) + { + script = NULL; + if (!strncmp(mod->textures[a]->name, "sky/", 4)) + script = + "{\n" + "sort sky\n" + "surfaceparm nodlight\n" + "skyparms - - -\n" + "}\n"; + mod->textures[a]->shader = modfuncs->RegisterBasicShader(mod, mod->textures[a]->name, SUF_LIGHTMAP, script, PTI_INVALID, 0, 0, NULL, NULL); + } + } + modfuncs->Batches_Build(mod, data); + if (data) + plugfuncs->Free(data); +} + +static void CODBSP_PrepareFrame(struct model_s *mod, refdef_t *refdef, int area, int clusters[2], pvsbuffer_t *vis, qbyte **entvis_out, qbyte **surfvis_out) +{ + *entvis_out = *surfvis_out = CODBSP_ClusterPVS(mod, clusters[0], vis, false); + if (clusters[1] != -1) + CODBSP_ClusterPVS(mod, clusters[1], vis, true); + + /*if (!refdef->areabitsknown) + { //generate the info each frame, as the gamecode didn't tell us what to use. + int leafnum = CODBSP_PointLeafnum (mod, refdef->vieworg); + int clientarea = CODBSP_LeafArea (mod, leafnum); + CODBSP_WriteAreaBits(mod, refdef->areabits, clientarea, false); + refdef->areabitsknown = true; + }*/ + + if (0) + { + size_t i; + msurface_t *surf; + for (i = mod->firstmodelsurface+mod->nummodelsurfaces; i --> mod->firstmodelsurface; ) + { + surf = &mod->surfaces[i]; + surf->sbatch->mesh[surf->sbatch->meshes++] = surf->mesh; + } + } + else + { + size_t i; + msurface_t *surf; + for (i = mod->firstmodelsurface; i < mod->nummodelsurfaces; i++) + { + surf = &mod->surfaces[i]; + surf->sbatch->mesh[surf->sbatch->meshes++] = surf->mesh; + } + } + + //for static props... + //ent = modfuncs->NewSceneEntity(); +} +static void CODBSP_InfoForPoint(struct model_s *mod, vec3_t pos, int *area, int *cluster, unsigned int *contentbits) +{ + struct codleaf_s *leaf = CODBSP_LeafForPoint(mod, pos, 0); + *area = leaf->area; + *cluster = leaf->cluster; + *contentbits = mod->funcs.PointContents(mod, NULL, pos); //needs a proper pointcontents. +} +#endif + +static qboolean CODBSP_LoadShaders (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct + { + char shadername[64]; + unsigned int surfflags; + unsigned int contents; + } *in = (void *)(mod_base + l->fileofs); + q2mapsurface_t *out; + int i, count; + texture_t *tex; + + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadShaders: funny lump size\n"); + return false; + } + count = l->filelen / sizeof(*in); + + if (count < 1) + { + Con_Printf (CON_ERROR "CODBSP_LoadShaders: Map with no shaders\n"); + return false; + } + + mod->numtexinfo = count; + out = prv->surfaces = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*out)); + + mod->textures = plugfuncs->GMalloc(&mod->memgroup, (sizeof(texture_t*)+sizeof(mtexinfo_t)+sizeof(texture_t))*count); //+1 is 'noshader' for flares. + mod->texinfo = (mtexinfo_t*)(mod->textures+count); + tex = (texture_t*)(mod->texinfo+count); + mod->numtextures = count; + + for ( i=0 ; ic.flags = LittleLong ( in->surfflags ); + out->c.value = LittleLong ( in->contents ); + Q_strlcpy(out->rname, in->shadername, sizeof(out->rname)); + + mod->texinfo[i].texture = tex+i; + mod->texinfo[i].flags = prv->surfaces[i].c.flags; + Q_strlcpy(mod->texinfo[i].texture->name, in->shadername, sizeof(mod->texinfo[i].texture->name)); + mod->textures[i] = mod->texinfo[i].texture; + } + + return true; +} + +static qboolean COD1BSP_LoadLightmap(model_t *mod, qbyte *mod_base, lump_t *l) +{ +// codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + int overbright; + int bytes; + size_t i, lev; + qbyte *in = mod_base+l->fileofs; + mod->lightmaps.width = 512; + mod->lightmaps.height = 512; + mod->lightmaps.prebaked = PTI_RGB8; + mod->lightmaps.fmt = LM_RGB8; + bytes = 3; + mod->lightmaps.surfstyles = 1; //always style 0... + mod->lightmaps.maxstyle = 0; + mod->lightmaps.deluxemapping = false; + mod->lightmaps.deluxemapping_modelspace = false; + mod->lightmaps.first = 0; + mod->lightmaps.count = l->filelen / (mod->lightmaps.width*mod->lightmaps.height*bytes); + if (l->filelen != mod->lightmaps.count * (mod->lightmaps.width*mod->lightmaps.height*bytes)) + { + Con_Printf (CON_ERROR "CODBSP_LoadLighting: funny lump size\n"); + return false; //err... rounded badly. + } + + mod->lightdata = plugfuncs->GMalloc(&mod->memgroup, l->filelen); + mod->lightdatasize = l->filelen; + + overbright = cvarfuncs->GetFloat("gl_overbright"); + mod->engineflags = MDLF_NEEDOVERBRIGHT; + if (overbright == 2) + memcpy(mod->lightdata, in, l->filelen); + else + { + qbyte *out = mod->lightdata; + overbright = (1<<(2-overbright)); + for (i = 0; i < l->filelen; i++, in++) + { + lev = *in * overbright; + *out++ = min(255, lev); + } + } + return true; +} +static qboolean COD2BSP_LoadLightmap(model_t *mod, qbyte *mod_base, lump_t *l) +{ + //seems to be sets of 4 images (3 normalmaps and some extra discoloured one). more deluxemap than lightmap. this is not useful to us. + //cod2 bundles some hlsl code, which may reveal clues. +// codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + + mod->lightmaps.width = 512; + mod->lightmaps.height = 512; + mod->lightmaps.prebaked = PTI_RGBA8; //needs glsl to use properly. + + mod->lightmaps.surfstyles = 1; //always style 0... + mod->lightmaps.maxstyle = 0; + mod->lightmaps.deluxemapping = false; //fixme: uses 4 lightmap textures at a time. + mod->lightmaps.deluxemapping_modelspace = false; + mod->lightmaps.first = 0; + mod->lightmaps.count = 0; + + Con_Printf(CON_WARNING"COD2 lightmaps are not supported\n"); + return true; +} +static qboolean COD1BSP_LoadSoupVertices (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct + { + vec3_t position; + vec2_t tc; + vec2_t lmtc; + vec3_t normal; + byte_vec4_t rgba; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t i, count = l->filelen / sizeof(*in); + mesh_t *mesh = &prv->soupverts; + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadSoupVertices: funny lump size\n"); + return false; + } + + //allocate lots of space. stoopid separate arrays. + prv->soupverts.istrifan = false; + prv->soupverts.xyz_array = plugfuncs->GMalloc(&mod->memgroup, count*( + sizeof(*mesh->xyz_array)+ + sizeof(*mesh->normals_array)+ + sizeof(*mesh->colors4b_array)+ + sizeof(*mesh->st_array)+ + sizeof(*mesh->lmst_array[0]))); + mesh->normals_array = (void*)(mesh->xyz_array + count); + mesh->colors4b_array = (void*)(mesh->normals_array + count); + mesh->st_array = (void*)(mesh->colors4b_array + count); + mesh->lmst_array[0] = (void*)(mesh->st_array + count); + + //copy it all over. + for (i = 0; i < count; i++, in++) + { + mesh->xyz_array[i][0] = LittleFloat(in->position[0]); + mesh->xyz_array[i][1] = LittleFloat(in->position[1]); + mesh->xyz_array[i][2] = LittleFloat(in->position[2]); + mesh->st_array[i][0] = LittleFloat(in->tc[0]); + mesh->st_array[i][1] = LittleFloat(in->tc[1]); + mesh->lmst_array[0][i][0] = LittleFloat(in->lmtc[0]); + mesh->lmst_array[0][i][1] = LittleFloat(in->lmtc[1]); + mesh->normals_array[i][0] = LittleFloat(in->normal[0]); + mesh->normals_array[i][1] = LittleFloat(in->normal[1]); + mesh->normals_array[i][2] = LittleFloat(in->normal[2]); + mesh->colors4b_array[i][0] = in->rgba[0]; + mesh->colors4b_array[i][1] = in->rgba[1]; + mesh->colors4b_array[i][2] = in->rgba[2]; + mesh->colors4b_array[i][3] = in->rgba[3]; + } + return true; +} +static qboolean COD2BSP_LoadSoupVertices (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct + { //slightly rearranged for some reason (plus addition of tangents at the end) + vec3_t position; + vec3_t normal; + byte_vec4_t rgba; + vec2_t tc; + vec2_t lmtc; + vec3_t sdir; + vec3_t tdir; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t i, count = l->filelen / sizeof(*in); + mesh_t *mesh = &prv->soupverts; + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadSoupVertices: funny lump size\n"); + return false; + } + + //allocate lots of space. stoopid separate arrays. + prv->soupverts.istrifan = false; + prv->soupverts.xyz_array = plugfuncs->GMalloc(&mod->memgroup, count*( + sizeof(*mesh->xyz_array)+ + sizeof(*mesh->normals_array)+ + sizeof(*mesh->snormals_array)+ + sizeof(*mesh->tnormals_array)+ + sizeof(*mesh->colors4b_array)+ + sizeof(*mesh->st_array)+ + sizeof(*mesh->lmst_array[0]))); + mesh->normals_array = (void*)(mesh->xyz_array + count); + mesh->snormals_array = (void*)(mesh->normals_array + count); + mesh->tnormals_array = (void*)(mesh->snormals_array + count); + mesh->colors4b_array = (void*)(mesh->tnormals_array + count); + mesh->st_array = (void*)(mesh->colors4b_array + count); + mesh->lmst_array[0] = (void*)(mesh->st_array + count); + + //copy it all over. + for (i = 0; i < count; i++, in++) + { + mesh->xyz_array[i][0] = LittleFloat(in->position[0]); + mesh->xyz_array[i][1] = LittleFloat(in->position[1]); + mesh->xyz_array[i][2] = LittleFloat(in->position[2]); + mesh->st_array[i][0] = LittleFloat(in->tc[0]); + mesh->st_array[i][1] = LittleFloat(in->tc[1]); + mesh->lmst_array[0][i][0] = LittleFloat(in->lmtc[0]); + mesh->lmst_array[0][i][1] = LittleFloat(in->lmtc[1]); + mesh->normals_array[i][0] = LittleFloat(in->normal[0]); + mesh->normals_array[i][1] = LittleFloat(in->normal[1]); + mesh->normals_array[i][2] = LittleFloat(in->normal[2]); + mesh->snormals_array[i][0] = LittleFloat(in->sdir[0]); + mesh->snormals_array[i][1] = LittleFloat(in->sdir[1]); + mesh->snormals_array[i][2] = LittleFloat(in->sdir[2]); + mesh->tnormals_array[i][0] = LittleFloat(in->tdir[0]); + mesh->tnormals_array[i][1] = LittleFloat(in->tdir[1]); + mesh->tnormals_array[i][2] = LittleFloat(in->tdir[2]); + mesh->colors4b_array[i][0] = in->rgba[0]; + mesh->colors4b_array[i][1] = in->rgba[1]; + mesh->colors4b_array[i][2] = in->rgba[2]; + mesh->colors4b_array[i][3] = in->rgba[2]; + } + return true; +} +static qboolean CODBSP_LoadSoupIndexes (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + unsigned short *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadSoupIndexes: funny lump size\n"); + return false; + } + prv->soupverts.indexes = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*prv->soupverts.indexes)); + for (i = 0; i < count; i++, in++) + prv->soupverts.indexes[i] = LittleShort(*in); + + return true; +} +static qboolean CODBSP_LoadSoups (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct + { + unsigned short material_idx; + unsigned short lightmap_idx; + unsigned int vertex_offset; + unsigned short vertex_count; + unsigned short index_count; + unsigned int index_offset; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + struct codsoup_s *out; + mesh_t *mesh; + size_t j, i, count = l->filelen / sizeof(*in); + unsigned int mn,mx, idx; + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadSoups: funny lump size\n"); + return false; + } + prv->soups = out = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*out)); + mod->numsurfaces = count; + mod->nummodelsurfaces = count; + mod->surfaces = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*mod->surfaces) + + count*sizeof(*mesh)); + mesh = (void*)(mod->surfaces+count); + for (i = 0; i < count; i++, in++, out++) + { + unsigned short tex = LittleShort(in->material_idx); + unsigned short lmap = LittleShort(in->lightmap_idx); + out->vertex_offset = LittleLong (in->vertex_offset); + out->index_offset = LittleLong (in->index_offset); + + if (tex >= mod->numtexinfo) + return false; + + for (j = 0; j < MAXCPULIGHTMAPS; j++) + mod->surfaces[i].styles[j] = INVALID_LIGHTSTYLE; + for (j = 0; j < MAXRLIGHTMAPS; j++) + { + mod->surfaces[i].vlstyles[j] = INVALID_VLIGHTSTYLE; + mod->surfaces[i].lightmaptexturenums[j] = -1; + } + mod->surfaces[i].styles[0] = 0; + mod->surfaces[i].lightmaptexturenums[0] = lmap==(unsigned short)~0u?INVALID_LIGHTSTYLE:lmap; + + mod->surfaces[i].texinfo = &mod->texinfo[tex]; + mod->surfaces[i].mesh = &mesh[i]; + mesh[i].numindexes = LittleShort(in->index_count); + mesh[i].numvertexes = LittleShort(in->vertex_count); + + //cod2 sucks and is way out and results in horrible memory use. calculate what it should have been. + mn = ~0i; + mx = 0; + for (j = 0; j < mesh[i].numindexes; j++) + { + idx = LittleLong(prv->soupverts.indexes[out->index_offset+j]); + if (mx<= idx) + mx = idx+1; + if (mn > idx) + mn = idx; + } + if (mx 65535) + return false; + out->index_fixup = mn; + out->vertex_offset += mn; + } + + return true; +} + +static qboolean CODBSP_LoadLights (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct + { //18... + int type; //1=sunlight? + //4=omni? + //5=oriented non-spot? + //7=oriented spot light? + vec3_t rgb; //these values are completely fucked in most modes. :( + vec3_t xyz; //actually matches a lightsource + vec3_t dir; + float pointnineish; //some sort of exponent? falloff? + float scale; //?big float. radius? sometimes denormalised? + float fov; //very fovy + int naught0; //no info. could be floats. + int naught1; //no info. could be floats. + int naught2; //no info. could be floats. + int naught3; //no info. could be floats. + int naught4; //no info. could be floats. + } *in = (void*)((qbyte*)mod_base + l->fileofs); + struct codlight_s *out; + size_t i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadLightValues: funny lump size\n"); + return false; + } + prv->lights = out = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*out)); + prv->numlights = count; + for (i = 0; i < count; i++, in++, out++) + { + out->type = LittleLong(in->type); + out->xyz[0] = LittleFloat(in->xyz[0]); + out->xyz[1] = LittleFloat(in->xyz[1]); + out->xyz[2] = LittleFloat(in->xyz[2]); + out->rgb[0] = LittleFloat(in->rgb[0]); + out->rgb[1] = LittleFloat(in->rgb[1]); + out->rgb[2] = LittleFloat(in->rgb[2]); + out->dir[0] = LittleFloat(in->dir[0]); + out->dir[1] = LittleFloat(in->dir[1]); + out->dir[2] = LittleFloat(in->dir[2]); + out->scale = LittleFloat(in->scale); + out->fov = LittleFloat(in->fov); + } + + return true; +} +static qboolean CODBSP_LoadLightIndexes (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + unsigned short *in = (void*)((qbyte*)mod_base + l->fileofs), *out, v; + size_t i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadLightIndexes: funny lump size\n"); + return false; + } + prv->lightindexes = out = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*out)); + prv->numlightindexes = count; + for (i = 0; i < count; i++) + { + v = LittleShort(*in++); + if (v == 0xffff) + ; //o.O + else if (v >= prv->numlights) + { + Con_Printf (CON_ERROR "CODBSP_LoadLightIndexes: invalid index %i\n", v); + return false; + } + *out++ = v; + } + return true; +} +static qboolean CODBSP_LoadEntities (model_t *mod, qbyte *mod_base, lump_t *l) +{ //just quake-style { "field" "value" "field2" "value2" } blocks. + return modfuncs->LoadEntities(mod, mod_base+l->fileofs, l->filelen); +} + +static qboolean CODBSP_LoadPlanes (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + vec4_t *in = (void*)((qbyte*)mod_base + l->fileofs); + mplane_t *out; + size_t j, i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadPlanes: funny lump size\n"); + return false; + } + prv->planes = out = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*out)); + prv->num_planes = count; + for (i = 0; i < count; i++, out++) + { + out->normal[0] = LittleFloat(in[i][0]); + out->normal[1] = LittleFloat(in[i][1]); + out->normal[2] = LittleFloat(in[i][2]); + out->dist = LittleFloat(in[i][3]); + + out->type = PLANE_ANYZ; + out->signbits = 0; + for (j=0 ; j<3 ; j++) + { + if (out->normal[j] < 0) + out->signbits |= 1<normal[j] == 1) + out->type = j; + } + } + return true; +} +static qboolean CODBSP_LoadBrushSides (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct codbsp_brushside_s *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadBrushSides: funny lump size\n"); + return false; + } + prv->brushsides = in; //used elsewhere in the loader + prv->num_brushsides = count; + return true; +} +static qboolean CODBSP_LoadBrushes (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + const struct + { + unsigned short sides; + unsigned short material; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + const struct codbsp_brushside_s *inside = prv->brushsides; + q2cbrush_t *out; + q2cbrushside_t *outside; + mplane_t *aplane; + size_t j, i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadBrushes: funny lump size\n"); + return false; + } + prv->brushes = out = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*out)); + aplane = plugfuncs->GMalloc(&mod->memgroup, count*6*sizeof(*aplane)); + //read the data + for (i = 0, j = 0; i < count; i++, in++) + { + unsigned int mat = LittleShort(in->material); + out[i].numsides = (unsigned short)LittleShort(in->sides); + out[i].contents = prv->surfaces[mat].c.value; //is this right? seems to kinda work? feels wrong though. + j += out[i].numsides; + } + //fix up the planes... + outside = plugfuncs->GMalloc(&mod->memgroup, j*sizeof(*outside)); + prv->num_brushes = count; + for (i = 0, j = 0; i < count; i++, out++) + { + out->brushside = outside; + for (j = 0; j < out->numsides; j++, inside++, outside++) + { + unsigned int mat = LittleLong(inside->material_idx); + if (j < 6) + { + aplane->dist = LittleFloat(inside->dist); + if (j&1) + { //stored nx px ny py nz pz + aplane->normal[j>>1] = 1; + out->absmaxs[j>>1] = aplane->dist; + } + else + { + aplane->normal[j>>1] = -1; + out->absmins[j>>1] = aplane->dist; + aplane->dist *= -1; + } + outside->plane = aplane++; + } + else + outside->plane = prv->planes + LittleLong(inside->plane); + outside->surface = &prv->surfaces[mat]; + } + } + return true; +} +/*static qboolean CODBSP_LoadLeafBrushes (model_t *mod, qbyte *mod_base, lump_t *l) +{ //we don't really care about this, as we're using our BIH stuff for collisions instead. +// codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct + { + int a; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t i, count = l->filelen / sizeof(*in); + int highest=0; + int lowest=0; + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadLeafBrushes: funny lump size\n"); + return false; + } + for (i = 0; i < count; i++, in++) + { + if (lowest > in->a) + lowest = in->a; + if (highest < in->a) + highest = in->a; +// Con_Printf("%i: %i\n", (int)i, in->a); + } + Con_Printf("leaf brushes: %i - %i\n", lowest, highest); + return true; +}*/ + +static qboolean CODBSP_LoadPatchVertexes (model_t *mod, qbyte *mod_base, lump_t *l) +{ //for collision + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + const vec3_t *in = (void*)((qbyte*)mod_base + l->fileofs); + vecV_t *out; + size_t i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadPatchVertexes: funny lump size\n"); + return false; + } + prv->patchvertexes = out = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*out)); + prv->numpatchvertexes = count; + for (i = 0; i < count; i++) + { + out[i][0] = LittleFloat(in[i][0]); + out[i][1] = LittleFloat(in[i][1]); + out[i][2] = LittleFloat(in[i][2]); + } + return true; +} +static qboolean CODBSP_LoadPatchIndexes (model_t *mod, qbyte *mod_base, lump_t *l) +{ //for collision + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + unsigned short *in = (void*)((qbyte*)mod_base + l->fileofs); + index_t *out; + size_t i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadPatchIndexes: funny lump size\n"); + return false; + } + prv->patchindexes = out = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*out)); + prv->numpatchindexes = count; + for (i = 0; i < count; i++) + { + *out++ = (unsigned short)LittleShort(*in++); + } + return true; +} +static qboolean CODBSP_LoadPatchCollision (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct codpatch_s *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadPatchesCollision: funny lump size\n"); + return false; + } + prv->patches = in; + prv->numpatches = count; + for (i = 0; i < count; i++, in++) + { + if (in->mode==0) + ;//Con_Printf("p%i: %s ?+%i*%-4i v%i+%i %i\n", (int)i, mod->textures[in->mat]->name, in->mode0.w, in->mode0.h, in->mode0.firstvert,in->mode0.w*in->mode0.h, in->mode0.unknown); + else if (in->mode==1) + ;//Con_Printf("s%i: %s %4i+%-4i v%i+%i\n", (int)i, mod->textures[in->mat]->name, in->mode1.firstidx,in->mode1.numidx, in->mode1.firstvert,in->mode1.numverts); + else + { + Con_Printf("?%i: %s %i ?!?!?!?!?\n", (int)i, mod->textures[in->mat]->name, in->mode); + return false; //nope. + } + } + + return true; +} +static qboolean CODBSP_LoadLeafPatches (model_t *mod, qbyte *mod_base, lump_t *l) +{ //for collision + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + unsigned int *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadLeafPatches: funny lump size\n"); + return false; + } + prv->leafpatches = in; + prv->numleafpatches = count; + return true; +} + +/* +static qboolean CODBSP_LoadAABBs (model_t *mod, qbyte *mod_base, lump_t *l) +{ +// codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct + { + int a; //surfaceindex (submodel0 only, so stops short of the full range implied by the lump's count) + int b; //numsurfaces + int c; //some sort of offset? + } *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadAABBs: funny lump size\n"); + return false; + } + for (i = 0; i < count; i++, in++) + { +// Con_Printf("%i: %i+%i %i\n", (int)i, in->a, in->b, in->c); + } + return true; +} +static qboolean CODBSP_LoadCells (model_t *mod, qbyte *mod_base, lump_t *l) +{ +// codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct + { + vec3_t mins; + vec3_t maxs; + + int aabtree; //lump 16ish? CODLUMP_AABBTREES + int firstportal; //lump 18? CODLUMP_PORTALS + int numportals; + int firstcullgroupindex; //lump 10? CODLUMP_CULLGROUPINDEXES + int numcullgroupindexes; + int firstoccluderindex; + int numoccluders; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t i, count = l->filelen / sizeof(*in); + if (l->filelen % sizeof(*in)) + { + Con_Printf (CON_ERROR "CODBSP_LoadCells: funny lump size\n"); + return false; + } + for (i = 0; i < count; i++, in++) + { + Con_Printf("%i: [%f %f %f] [%f %f %f] %i %i+%i %i+%i %i+%i\n", (int)i, + in->mins[0], in->mins[1], in->mins[2], + in->maxs[0], in->maxs[1], in->maxs[2], + in->aabtree, in->firstportal, in->numportals, in->firstcullgroupindex, + in->numcullgroupindexes, in->firstoccluderindex, in->numoccluders); + } + return true; +}*/ + +static qboolean CODBSP_LoadLeafs (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct codinlinemodel_s{ + int cluster; //-1 for invalid + int area; //-1 for invalid + unsigned int firstleafsurfs; + unsigned int numsurfaces; + unsigned int firstleafbrushes; + unsigned int numbrushes; + + int cell; //-1 for invalid + unsigned int firstlightindex; + unsigned int numlightindexes; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t count = l->filelen / sizeof(*in); + size_t i; + if (l->filelen % sizeof(*in) || count < 1) + { + Con_Printf (CON_ERROR "CODBSP_LoadLeafs: funny lump size\n"); + return false; + } + prv->leaf = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*prv->leaf)); + prv->numleafs = count; + for (i = 0; i < count; i++, in++) + { + prv->leaf[i].cluster = LittleLong(in->cluster); + prv->leaf[i].area = LittleLong(in->area); + + prv->leaf[i].firstlightindex = LittleLong(in->firstlightindex); + prv->leaf[i].numlightindexes = LittleLong(in->numlightindexes); + } + return true; +} +static qboolean CODBSP_LoadNodes (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + struct codinlinemodel_s{ + unsigned int plane; + int child[2]; //negative for leaf + ivec3_t mins; + ivec3_t maxs; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t count = l->filelen / sizeof(*in); + size_t i; + if (l->filelen % sizeof(*in) || count < 1) + { + Con_Printf (CON_ERROR "CODBSP_LoadNodes: funny lump size\n"); + return false; + } + prv->nodes = plugfuncs->GMalloc(&mod->memgroup, count*sizeof(*prv->nodes)); + prv->numnodes = count; + for (i = 0; i < count; i++, in++) + { + prv->nodes[i].plane = &prv->planes[LittleLong(in->plane)]; + prv->nodes[i].childnum[0] = LittleLong(in->child[0]); + prv->nodes[i].childnum[1] = LittleLong(in->child[1]); + + prv->nodes[i].mins[0] = LittleLong(in->mins[0]); + prv->nodes[i].mins[1] = LittleLong(in->mins[1]); + prv->nodes[i].mins[2] = LittleLong(in->mins[2]); + prv->nodes[i].maxs[0] = LittleLong(in->maxs[0]); + prv->nodes[i].maxs[1] = LittleLong(in->maxs[1]); + prv->nodes[i].maxs[2] = LittleLong(in->maxs[2]); + } + return true; +} +static qboolean CODBSP_LoadVisibility (model_t *mod, qbyte *mod_base, lump_t *l) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + qbyte *in = (void*)((qbyte*)mod_base + l->fileofs); + if (!l->filelen) + { //unvised. + mod->numclusters = 0; + mod->pvsbytes = 0; + prv->pvsdata = NULL; + return true; + } + if (l->filelen < 8) + { + Con_Printf (CON_ERROR "CODBSP_LoadVisibility: funny lump size\n"); + return false; + } + mod->numclusters = LittleLong(((int*)in)[0]); + mod->pvsbytes = LittleLong(((int*)in)[1]); + if (l->filelen != 8 + mod->numclusters*mod->pvsbytes) + { + Con_Printf (CON_ERROR "CODBSP_LoadVisibility: funny lump size\n"); + return false; + } + + prv->pvsdata = plugfuncs->GMalloc(&mod->memgroup, l->filelen-8); + memcpy(prv->pvsdata, in+8, mod->numclusters*mod->pvsbytes); + return true; +} + +void AddPointToBounds (const vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} +static void CODBSP_BuildBIH (model_t *mod, size_t firstbrush, size_t numbrushes, size_t firstleafpatch, size_t numleafpatches) +{ + codbspinfo_t *prv = (codbspinfo_t*)mod->meshinfo; + size_t numtriangles = 0; + size_t numquads = 0; + + index_t *silly; + struct bihleaf_s *bihleaf, *l; + size_t i, j, lp; + qbyte *patches = memset(alloca((prv->numpatches+7)>>3), 0, (prv->numpatches+7)>>3); + for (i = firstleafpatch; i < numleafpatches; i++) + { //de-dupe them... *sigh* + lp = LittleLong(prv->leafpatches[i]); + if (lp < prv->numpatches) + patches[lp>>3] |= 1u<<(lp&7); + } + for (i = 0; i < prv->numpatches; i++) + { + if (patches[i>>3] & (1u<<(i&7))) + { + if (prv->patches[i].mode) + numtriangles+=(unsigned short)LittleShort(prv->patches[i].mode1.numidx)/3; + else + numquads+=(unsigned int)((unsigned short)LittleShort(prv->patches[i].mode0.w)-1) * ((unsigned short)LittleShort(prv->patches[i].mode0.h)-1); + } + } + + bihleaf = l = plugfuncs->Malloc(sizeof(*bihleaf)*(numbrushes+numtriangles+numquads*2)); + + silly = plugfuncs->GMalloc(&mod->memgroup, sizeof(*silly)*6*numquads); + + //now we have enough storage, spit them out providing bounds info. + for (i = 0; i < prv->numpatches; i++) + { + if (patches[i>>3] & (1u<<(i&7))) + { + if (prv->patches[i].mode) + { + unsigned int numidx = (unsigned short)LittleShort(prv->patches[i].mode1.numidx); + for (j = 0; j < numidx; j+=3) + { + vec_t *v1,*v2,*v3; + l->type = BIH_TRIANGLE; + l->data.contents = prv->surfaces[LittleLong(prv->patches[i].mat)].c.value; + l->data.tri.xyz = prv->patchvertexes + (unsigned int)LittleLong(prv->patches[i].mode1.firstvert); + l->data.tri.indexes = prv->patchindexes + (unsigned int)LittleLong(prv->patches[i].mode1.firstidx) + j; + + v1 = l->data.tri.xyz[l->data.tri.indexes[0]]; + v2 = l->data.tri.xyz[l->data.tri.indexes[1]]; + v3 = l->data.tri.xyz[l->data.tri.indexes[2]]; + + VectorCopy(v1, l->mins); + VectorCopy(v1, l->maxs); + AddPointToBounds(v2, l->mins, l->maxs); + AddPointToBounds(v3, l->mins, l->maxs); + l++; + } + } + else + { + unsigned int w = (unsigned short)LittleShort(prv->patches[i].mode0.w); + unsigned int h = (unsigned short)LittleShort(prv->patches[i].mode0.h); + unsigned int x, y; + for (y = 0; y < h-1; y++) + for (x = 0; x < w-1; x++) + { + const vec_t *v1,*v2,*v3; + + silly[0] = x+y*w; + silly[1] = silly[0]+1; + silly[2] = silly[0]+w; + silly[3] = silly[1]; + silly[4] = silly[1]+w; + silly[5] = silly[2]; + + l->type = BIH_TRIANGLE; + l->data.contents = FTECONTENTS_SOLID; //prv->surfaces[LittleLong(prv->patches[i].mat)].c.value; + l->data.tri.xyz = prv->patchvertexes + (unsigned int)LittleLong(prv->patches[i].mode0.firstvert); + l->data.tri.indexes = silly; + + v1 = l->data.tri.xyz[l->data.tri.indexes[0]]; + v2 = l->data.tri.xyz[l->data.tri.indexes[1]]; + v3 = l->data.tri.xyz[l->data.tri.indexes[2]]; + + VectorCopy(v1, l->mins); + VectorCopy(v1, l->maxs); + AddPointToBounds(v2, l->mins, l->maxs); + AddPointToBounds(v3, l->mins, l->maxs); + l++; + silly+=3; + + + l->type = BIH_TRIANGLE; + l->data.contents = FTECONTENTS_SOLID; //prv->surfaces[LittleLong(prv->patches[i].mat)].c.value; + l->data.tri.xyz = prv->patchvertexes + (unsigned int)LittleLong(prv->patches[i].mode0.firstvert); + l->data.tri.indexes = silly; + + v1 = l->data.tri.xyz[l->data.tri.indexes[0]]; + v2 = l->data.tri.xyz[l->data.tri.indexes[1]]; + v3 = l->data.tri.xyz[l->data.tri.indexes[2]]; + + VectorCopy(v1, l->mins); + VectorCopy(v1, l->maxs); + AddPointToBounds(v2, l->mins, l->maxs); + AddPointToBounds(v3, l->mins, l->maxs); + l++; + silly+=3; + } + } + } + } + + //now we have enough storage, spit them out providing bounds info. + for (i = 0; i < numbrushes; i++) + { + q2cbrush_t *b = &prv->brushes[firstbrush+i]; + l->type = BIH_BRUSH; + l->data.brush = b; + + l->data.contents = b->contents; + VectorCopy(b->absmins, l->mins); + VectorCopy(b->absmaxs, l->maxs); + l++; + } + modfuncs->BIH_Build(mod, bihleaf, l-bihleaf); + plugfuncs->Free(bihleaf); +} + +static qboolean CODBSP_LoadInlineModels (model_t *wmod, qbyte *mod_base, lump_t *l) +{ +// codbspinfo_t *prv = (codbspinfo_t*)wmod->meshinfo; + struct codinlinemodel_s{ + vec3_t mins; + vec3_t maxs; + unsigned int firstsurf; + unsigned int numsurfs; + unsigned int firstleafpatch; //seems to match lump 23, + unsigned int numleafpatches; + unsigned int firstbrush; + unsigned int numbrushes; + } *in = (void*)((qbyte*)mod_base + l->fileofs); + size_t count = l->filelen / sizeof(*in); + size_t i, j; + if (l->filelen % sizeof(*in) || count < 1) + { + Con_Printf (CON_ERROR "CODBSP_LoadInlineModels: funny lump size\n"); + return false; + } + + for (i = 0; i < count; i++, in++) + { + char name[MAX_QPATH]; + model_t *mod; + if (i) + { //submodels + Q_snprintfz (name, sizeof(name), "*%u:%s", (unsigned int)i, wmod->publicname); + mod = modfuncs->BeginSubmodelLoad(name); + *mod = *wmod; + mod->archive = NULL; + mod->entities_raw = NULL; + mod->submodelof = wmod; + Q_strlcpy(mod->publicname, name, sizeof(mod->publicname)); + Q_snprintfz (mod->name, sizeof(mod->name), "*%u:%s", (unsigned int)i, wmod->name); + memset(&mod->memgroup, 0, sizeof(mod->memgroup)); + } + else //handle the world model here too + mod = wmod; + + mod->hulls[0].firstclipnode = i?-1:0; + for (j = 1; j < countof(mod->hulls); j++) + mod->hulls[j].firstclipnode = -1; //no nodes, + mod->nodes = mod->rootnode = NULL; + mod->leafs = NULL; + + mod->nummodelsurfaces = LittleLong(in->numsurfs); + mod->firstmodelsurface = LittleLong(in->firstsurf); + + VectorCopy(in->mins, mod->mins); + VectorCopy(in->maxs, mod->maxs); + CODBSP_BuildBIH(mod, LittleLong(in->firstbrush), LittleLong(in->numbrushes), LittleLong(in->firstleafpatch), LittleLong(in->numleafpatches)); + + memset(&mod->batches, 0, sizeof(mod->batches)); + mod->vbos = NULL; + +#ifdef HAVE_CLIENT +// mod->radius = RadiusFromBounds (mod->mins, mod->maxs); + +// if (qrenderer != QR_NONE) + { + builddata_t *bd = plugfuncs->Malloc(sizeof(*bd)); + bd->buildfunc = CODBSP_BuildSurfMesh; + bd->paintlightmaps = false; //q3like with prebaked lightmaps. + threadfuncs->AddWork(WG_MAIN, CODBSP_GenerateMaterials, mod, bd, 0, 0); + } +#endif + + if (mod != wmod) + modfuncs->EndSubmodelLoad(mod, MLS_LOADED); + } + return true; +} + +static qboolean QDECL Mod_LoadCodBSP(struct model_s *mod, void *buffer, size_t fsize) +{ + codbspinfo_t *prv; + qboolean okay = true; + int i; + int ver = LittleLong(((int*)buffer)[1]); + lump_t lumps[max((int)COD1LUMP_COUNT, (int)COD2LUMP_COUNT)]; + + mod->fromgame = fg_new; +#ifdef HAVE_SERVER + mod->funcs.FatPVS = CODBSP_FatPVS; + mod->funcs.EdictInFatPVS = CODBSP_EdictInFatPVS; + mod->funcs.FindTouchedLeafs = CODBSP_FindTouchedLeafs; +#endif +#ifdef HAVE_CLIENT + mod->funcs.LightPointValues = CODBSP_LightPointValues; +// mod->funcs.StainNode = CODBSP_StainNode; +// mod->funcs.MarkLights = CODBSP_MarkLights; +// mod->funcs.GenerateShadowMesh = CODBSP_GenerateShadowMesh; +#endif + mod->funcs.ClusterPVS = CODBSP_ClusterPVS; +// mod->funcs.ClusterPHS = CODBSP_ClusterPHS; + mod->funcs.ClusterForPoint = CODBSP_ClusterForPoint; +// mod->funcs.SetAreaPortalState = CODBSP_SetAreaPortalState; +// mod->funcs.AreasConnected = CODBSP_AreasConnected; +// mod->funcs.LoadAreaPortalBlob = CODBSP_LoadAreaPortalBlob; +// mod->funcs.SaveAreaPortalBlob = CODBSP_SaveAreaPortalBlob; + mod->funcs.PrepareFrame = CODBSP_PrepareFrame; + mod->funcs.InfoForPoint = CODBSP_InfoForPoint; + + if (ver == COD1BSP_VERSION) + { + memcpy(lumps, (char*)buffer+8, sizeof(*lumps)*COD1LUMP_COUNT); + for (i = 0; i < COD1LUMP_COUNT; i++) + { + int ffs = lumps[i].filelen; //ffs + lumps[i].filelen = LittleLong(lumps[i].fileofs); + lumps[i].fileofs = LittleLong(ffs); + if (lumps[i].filelen && lumps[i].fileofs+(size_t)lumps[i].filelen > fsize) + { + Con_Printf(CON_ERROR"Truncated BSP file\n"); + return false; + } + } + + mod->meshinfo = prv = plugfuncs->GMalloc(&mod->memgroup, sizeof(codbspinfo_t)); + prv->codbspver = ver; + + //basic trisoup info + okay = okay && CODBSP_LoadShaders(mod, buffer, &lumps[COD1LUMP_MATERIALS]); + okay = okay && COD1BSP_LoadLightmap(mod, buffer, &lumps[COD1LUMP_LIGHTMAPS]); + okay = okay && COD1BSP_LoadSoupVertices(mod, buffer, &lumps[COD1LUMP_SOUPVERTS]); + okay = okay && CODBSP_LoadSoupIndexes(mod, buffer, &lumps[COD1LUMP_SOUPINDEXES]); + okay = okay && CODBSP_LoadSoups(mod, buffer, &lumps[COD1LUMP_SOUPS]); + + //gamecode needs to know what's around + okay = okay && CODBSP_LoadLights(mod, buffer, &lumps[COD1LUMP_LIGHTS]); + okay = okay && CODBSP_LoadLightIndexes(mod, buffer, &lumps[COD1LUMP_LIGHTINDEXES]); + okay = okay && CODBSP_LoadEntities(mod, buffer, &lumps[COD1LUMP_ENTITIES]); + + //basic collision + okay = okay && CODBSP_LoadPlanes(mod, buffer, &lumps[COD1LUMP_PLANES]); + okay = okay && CODBSP_LoadBrushSides(mod, buffer, &lumps[COD1LUMP_BRUSHSIDES]); + okay = okay && CODBSP_LoadBrushes(mod, buffer, &lumps[COD1LUMP_BRUSHES]); + //okay = okay && CODBSP_LoadLeafBrushes(mod, buffer, &lumps[COD1LUMP_LEAFBRUSHES]); + + //patch collision... + okay = okay && CODBSP_LoadPatchVertexes(mod, buffer, &lumps[COD1LUMP_COLLISIONVERTS]); + okay = okay && CODBSP_LoadPatchIndexes(mod, buffer, &lumps[COD1LUMP_COLLISIONINDEXES]); + okay = okay && CODBSP_LoadPatchCollision(mod, buffer, &lumps[COD1LUMP_PATCHCOLLISION]); + okay = okay && CODBSP_LoadLeafPatches(mod, buffer, &lumps[COD1LUMP_LEAFPATCHES]); + + //seems like cod is a portal engine? + //okay = okay && CODBSP_LoadAABBs(mod, buffer, &lumps[COD1LUMP_AABBTREES]); + //okay = okay && CODBSP_LoadCells(mod, buffer, &lumps[COD1LUMP_CELLS]); + + //but we still have pvs with its clusters+areas that are node based (also required to determine which 'cell' we're inside). + okay = okay && CODBSP_LoadVisibility(mod, buffer, &lumps[COD1LUMP_VISIBILITY]); + okay = okay && CODBSP_LoadLeafs(mod, buffer, &lumps[COD1LUMP_LEAFS]); + okay = okay && CODBSP_LoadNodes(mod, buffer, &lumps[COD1LUMP_NODES]); + + okay = okay && CODBSP_LoadInlineModels(mod, buffer, &lumps[COD1LUMP_MODELS]); + } + else if (ver == COD2BSP_VERSION) + { + memcpy(lumps, (char*)buffer+8, sizeof(*lumps)*COD2LUMP_COUNT); + for (i = 0; i < COD2LUMP_COUNT; i++) + { + int ffs = lumps[i].filelen; //ffs + lumps[i].filelen = LittleLong(lumps[i].fileofs); + lumps[i].fileofs = LittleLong(ffs); + if (lumps[i].filelen && lumps[i].fileofs+(size_t)lumps[i].filelen > fsize) + { + Con_Printf(CON_ERROR"Truncated BSP file\n"); + return false; + } + } + + mod->meshinfo = prv = plugfuncs->GMalloc(&mod->memgroup, sizeof(codbspinfo_t)); + prv->codbspver = ver; + + //basic trisoup info + okay = okay && CODBSP_LoadShaders(mod, buffer, &lumps[COD2LUMP_MATERIALS]); + okay = okay && COD2BSP_LoadLightmap(mod, buffer, &lumps[COD2LUMP_LIGHTMAPS]); + okay = okay && COD2BSP_LoadSoupVertices(mod, buffer, &lumps[COD2LUMP_SOUPVERTS]); + okay = okay && CODBSP_LoadSoupIndexes(mod, buffer, &lumps[COD2LUMP_SOUPINDEXES]); + okay = okay && CODBSP_LoadSoups(mod, buffer, &lumps[COD2LUMP_SOUPS]); + + //gamecode needs to know what's around + okay = okay && CODBSP_LoadEntities(mod, buffer, &lumps[COD2LUMP_ENTITIES]); + + //basic collision + okay = okay && CODBSP_LoadPlanes(mod, buffer, &lumps[COD2LUMP_PLANES]); + okay = okay && CODBSP_LoadBrushSides(mod, buffer, &lumps[COD2LUMP_BRUSHSIDES]); + okay = okay && CODBSP_LoadBrushes(mod, buffer, &lumps[COD2LUMP_BRUSHES]); + //okay = okay && CODBSP_LoadLeafBrushes(mod, buffer, &lumps[COD2LUMP_LEAFBRUSHES]); + + //patch collision... +// okay = okay && CODBSP_LoadPatchVertexes(mod, buffer, &lumps[COD1LUMP_COLLISIONVERTS]); +// okay = okay && CODBSP_LoadPatchIndexes(mod, buffer, &lumps[COD1LUMP_COLLISIONINDEXES]); +// okay = okay && CODBSP_LoadPatchCollision(mod, buffer, &lumps[COD2LUMP_PATCHCOLLISION]); +// okay = okay && CODBSP_LoadLeafPatches(mod, buffer, &lumps[COD2LUMP_LEAFPATCHES]); + + //seems like cod is a portal engine? + //okay = okay && CODBSP_LoadAABBs(mod, buffer, &lumps[COD2LUMP_AABBTREES]); + //okay = okay && CODBSP_LoadCells(mod, buffer, &lumps[COD2LUMP_CELLS]); + + //but we still have pvs with its clusters+areas that are node based (also required to determine which 'cell' we're inside). + okay = okay && CODBSP_LoadVisibility(mod, buffer, &lumps[COD2LUMP_VISIBILITY]); + okay = okay && CODBSP_LoadLeafs(mod, buffer, &lumps[COD2LUMP_LEAFS]); + okay = okay && CODBSP_LoadNodes(mod, buffer, &lumps[COD2LUMP_NODES]); + + okay = okay && CODBSP_LoadInlineModels(mod, buffer, &lumps[COD2LUMP_MODELS]); + } + else + { + Con_Printf(CON_ERROR"Bad COD Version...\n"); //should have already been checked, so this ain't possible. + okay = false; + } + return okay; +} + +qboolean CODBSP_Init(void) +{ + filefuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*filefuncs)); + modfuncs = plugfuncs->GetEngineInterface(plugmodfuncs_name, sizeof(*modfuncs)); + threadfuncs = plugfuncs->GetEngineInterface(plugthreadfuncs_name, sizeof(*threadfuncs)); + if (modfuncs && modfuncs->version != MODPLUGFUNCS_VERSION) + modfuncs = NULL; + + if (modfuncs && filefuncs && threadfuncs) + { + modfuncs->RegisterModelFormatMagic("CoD(1) Maps", "IBSP\x3b\0\0\0",8, Mod_LoadCodBSP); + modfuncs->RegisterModelFormatMagic("CoD2 Maps", "IBSP\x04\0\0\0",8, Mod_LoadCodBSP); + return true; + } + return false; +} \ No newline at end of file diff --git a/plugins/cod/codiwi.c b/plugins/cod/codiwi.c new file mode 100644 index 000000000..4773435e9 --- /dev/null +++ b/plugins/cod/codiwi.c @@ -0,0 +1,119 @@ +#include "../plugin.h" + +static plugimagefuncs_t *imagefuncs; +static struct pendingtextureinfo *Image_ReadIWIFile(unsigned int imgflags, const char *fname, qbyte *filedata, size_t filesize) +{ + if (filesize >= 12 && filedata[0]=='I'&&filedata[1]=='W'&&filedata[2]=='i' && (filedata[3]==5/*cod2*/||filedata[3]==6/*cod4*/)) + { + static const enum uploadfmt fmts[] = {PTI_INVALID/*0*/, PTI_RGBA8/*1*/, PTI_RGB8/*2*/, PTI_L8A8/*3, VALIDATE!*/, PTI_INVALID/*4,PTI_A8*/, PTI_INVALID/*5*/,PTI_INVALID/*6*/,PTI_INVALID/*7*/,PTI_INVALID/*8*/,PTI_INVALID/*9*/,PTI_INVALID/*10*/,PTI_BC1_RGBA/*11*/, PTI_BC2_RGBA/*12*/, PTI_BC3_RGBA/*13*/}; + enum uploadfmt fmt = PTI_INVALID; + unsigned int bb,bw,bh,bd; + unsigned int iw,ih,id, l; + struct pendingtextureinfo *mips; + unsigned int offsets[4]; + qbyte *end = filedata+filesize; + enum { + IWI_STANDARD=0, + IWI_MIPLESS=3, + IWI_CUBEMAP=6, +// IWI_NORMALMAP=0x20, +// IWI_UNKNOWN=0x40, +// IWI_UNKNOWN=0x80, + } usage = filedata[5]; + if (filedata[4] < countof(fmts)) + fmt = fmts[filedata[4]]; + if (fmt == PTI_INVALID) + { //bail. with warning + Con_Printf(CON_WARNING"Image_ReadIWIFile(%s): unsupported iwi pixelformat %x\n", fname, filedata[4]); + return NULL; + } + iw = filedata[6] | (filedata[7]<<8); + ih = filedata[8] | (filedata[9]<<8); + id = filedata[10] | (filedata[11]<<8); + + imagefuncs->BlockSizeForEncoding(fmt, &bb, &bw, &bh, &bd); + if (!bb) + { + Con_Printf(CON_WARNING"Image_ReadIWIFile(%s): unsupported fte pixelformat %x(%i)\n", fname, filedata[4], fmt); + return NULL; + } + + mips = plugfuncs->Malloc(sizeof(*mips)); + mips->encoding = fmt; + if ((filedata[5]&0xf) == IWI_CUBEMAP && id==1) + { + mips->type = PTI_CUBE; + id *= 6; + } + else + mips->type = (id>1)?PTI_3D:PTI_2D; + mips->extrafree = filedata; + filedata += 12; + for (l = 0; l < countof(offsets); l++, filedata+=4) + offsets[l] = filedata[0] | (filedata[1]<<8) | (filedata[2]<<16) | (filedata[3]<<24); //dunno what this 4 values are for. looks like descending ends? + if (mips->type != PTI_2D || (usage&0xf)==IWI_MIPLESS) + mips->mipcount = l = 1; + else for (l = 0; l < countof(mips->mip); l++) + { + if ((iw >> l) || (ih >> l) || (id >> l)) + mips->mipcount++; + else + break; + } + //these are smallest to biggest. + while (l --> 0) + { + unsigned int w = iw>>l; + unsigned int h = ih>>l; + unsigned int d = (mips->type==PTI_3D)?id>>l:id; + size_t datasize; + if (!w && !h && ((mips->type==PTI_3D)?!d:true)) + break; + if (!w) + w = 1; + if (!h) + h = 1; + if (!d) + d = 1; + datasize = ((w+bw-1)/bw) * ((h+bh-1)/bh) * ((d+bd-1)/bd) * bb; + + if (filedata + datasize > end) + { + Con_Printf(CON_WARNING"%s: truncated\n", fname); + Con_Printf("%s: %#x\n", fname, usage); + plugfuncs->Free(mips); + return NULL; //doesn't fit... + } + mips->mip[l].width = w; + mips->mip[l].height = h; + mips->mip[l].depth = d; + mips->mip[l].data = filedata; + mips->mip[l].datasize = datasize; + mips->mip[l].needfree = false; + filedata += datasize; + } + if (filedata != end) + { + Con_Printf(CON_WARNING"%s: trailing data\n", fname); + Con_Printf("%s: %#x\n", fname, usage); + plugfuncs->Free(mips); + return NULL; //doesn't fit... + } + return mips; + } + return NULL; +} +static plugimageloaderfuncs_t iwifuncs = +{ + "InfinityWard Image", + sizeof(struct pendingtextureinfo), + false, + Image_ReadIWIFile, +}; +qboolean IWI_Init(void) +{ + imagefuncs = plugfuncs->GetEngineInterface(plugimagefuncs_name, sizeof(*imagefuncs)); + if (!imagefuncs) + return false; + return plugfuncs->ExportInterface(plugimageloaderfuncs_name, &iwifuncs, sizeof(iwifuncs)); +} diff --git a/plugins/cod/codmat.c b/plugins/cod/codmat.c new file mode 100644 index 000000000..0bed015e1 --- /dev/null +++ b/plugins/cod/codmat.c @@ -0,0 +1,244 @@ +#include "../plugin.h" +#include "shader.h" +typedef struct shaderparsestate_s parsestate_t; +static plugfsfuncs_t *fsfuncs; + +static qboolean COD2_DecodeMaterial(parsestate_t *ps, const char *filename, void (*LoadMaterialString)(parsestate_t *ps, const char *script)) +{ + size_t sz; + qbyte *base = fsfuncs->LoadFile(va("materials/%s", filename), &sz); + struct + { + unsigned int ofs_materialname; //usually matches filename. dunno why this needs to be here. for aliases? does the qbsp read this instead? + unsigned int ofs_texturename; //simplification for tools? dunno. + unsigned int z1; + qbyte unk10; + qbyte sort; //sort: + //0x00: distortion + //0x01: opaquewater + //0x02: boathull + //0x03: opaque + //0x04: sky + //0x04: skybox_sunmoon + //0x06: skybox_clouds + //0x08: decal_bottom1 + //0x09: decal_bottom2 + //0x0a: decal_bottom3 + //0x0b: decal_world + //0x0c: decal_middle1 + //0x0d: decal_middle2 + //0x0e: decal_middle3 + //0x0f: decal_gunimpact + //0x10: decal_top1 + //0x11: decal_top2 + //0x12: decal_top3 + //0x12: decal_multiplicative + //0x14: banner + //0x15: hair + //0x16: underwater + //0x17: transparentwater + //0x18: corona + //0x19: windowinside + //0x1a: windowoutside + //0x1b: blend + //0x1c: viewmodel + qbyte unk12; + qbyte unk13; + unsigned int z2; + unsigned short unk2[2]; + unsigned int unk3; + unsigned short width, height; + unsigned int z3; + unsigned int surfaceflags; //lower bits seem like they might be wrong. upper bits are consistentish with cod1 surfaceflags though. + unsigned int contentbits; //pure guess. probably wrong. + unsigned int blendbits; //0x00ff00ff bits are src/dst color/alpha blendfuncs matching the D3DBLEND enum. + unsigned int unk7; + unsigned int unk8; + unsigned int ofs_program; + unsigned int ofs_table; //0x44 + unsigned int ofs_table_end; + + //regarding the various unknowns, we can expect editor locale+usage (which are useless to us), blendfunc+alphafunc++cullface+depthtest+depthwrite+polygonoffset, maybe some tessellation settings and envmapping stuff. + + struct + { + unsigned int ofs_sampler; + unsigned int flags; //maybe? 0x200 for rgb, 0x300 for normals, 0x400 for spec...? there's probably clamping options here. + //&7==0: default + //&7==1: nearest... or trilinear?!? + //&7==2: linear + //&7==3: ??? + //&7==4: ??? + //&7==5: ??? + //&7==6: bilinear + //&7==7: anisotropic + unsigned int ofs_texname; + } maps[1]; + } *header = (void*)base; + unsigned int m; + if (!base) + return false; //nope, bad filename + if (header->ofs_table == 0x44) //make sure we know what it is... + { + size_t ofs = 0; + char shad[8192]; + const char *srcfac, *dstfac; + + //no initial { cos we're weird. + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\n"),ofs+=strlen(shad+ofs); //not actually useful to us. maybe for the hud so it doesn't have to wait for the image data too? + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\t//material='%s'\n\t//tooltex='%s'\n", base+header->ofs_materialname, base+header->ofs_texturename),ofs+=strlen(shad+ofs); + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\t//z1=%#x z2=%#x z3=%#x\n\t//unk1=%#x,%#x,%#x,%#x\n\t//unk2=%#x,%#x\n\t//unk3=%#x\n\t//surfaceflags=%#x\n\t//contentbits=%#x\n\t//unk6=%#x\n\t//unk7=%#x\n\t//unk8=%#x\n", header->z1, header->z2, header->z3, header->unk10,header->sort,header->unk12,header->unk13, header->unk2[0],header->unk2[1], header->unk3, header->surfaceflags, header->contentbits, header->blendbits, header->unk7, header->unk8),ofs+=strlen(shad+ofs); + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\timagesize %i %i\n", header->width, header->height),ofs+=strlen(shad+ofs); //not actually useful to us. maybe for the hud so it doesn't have to wait for the image data too? + + for (m = 0; m < (header->ofs_table_end-header->ofs_table)/sizeof(header->maps[0]); m++) + { + const char *sampler = base+header->maps[m].ofs_sampler; + if (!strcmp(sampler, "colorMap")) + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\tdiffusemap images/%s.iwi //%#x\n", base+header->maps[m].ofs_texname, header->maps[m].flags),ofs+=strlen(shad+ofs); + else if (!strcmp(sampler, "normalMap")) + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\tnormalmap images/%s.iwi //%#x\n", base+header->maps[m].ofs_texname, header->maps[m].flags),ofs+=strlen(shad+ofs); + else if (!strcmp(sampler, "detailMap")) + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\tdisplacementmap images/%s.iwi //%#x\n", base+header->maps[m].ofs_texname, header->maps[m].flags),ofs+=strlen(shad+ofs); //hack. might as well use this one as detail. + else if (!strcmp(sampler, "specularMap")) + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\tspecularmap images/%s.iwi //%#x\n", base+header->maps[m].ofs_texname, header->maps[m].flags),ofs+=strlen(shad+ofs); + else + Con_Printf("\t%s:%#x<-%s\n", base+header->maps[m].ofs_sampler, header->maps[m].flags, base+header->maps[m].ofs_texname); + } + + //spit out a pass... + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\t{\n"),ofs+=strlen(shad+ofs); + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\t\tprogram %s\n", base+header->ofs_program),ofs+=strlen(shad+ofs); + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\t\tmap $diffuse\n"),ofs+=strlen(shad+ofs); //just in case. + + switch((header->blendbits>>0)&0xf) + { //source factors + default: + case 1: srcfac="zero"; break; + case 2: srcfac="one"; break; + case 3: srcfac="src_color"; break; + case 4: srcfac="one_minus_src_color"; break; + case 5: srcfac="src_alpha"; break; + case 6: srcfac="one_minus_src_alpha"; break; + case 7: srcfac="dst_alpha"; break; + case 8: srcfac="one_minus_dst_alpha"; break; + case 9: srcfac="dst_color"; break; + case 10: srcfac="one_minus_dst_color"; break; + case 11: srcfac="src_alpha_sat"; break; + case 12: srcfac="both_src_alpha"; break; + case 13: srcfac="both_one_minus_src_alpha"; break; + case 14: srcfac="blend_factor"; break; + case 15: srcfac="one_minus_blend_factor"; break; + } + switch((header->blendbits>>4)&0xf) + { //dest factors + default: + case 1: dstfac="zero"; break; + case 2: dstfac="one"; break; + case 3: dstfac="src_color"; break; + case 4: dstfac="one_minus_src_color"; break; + case 5: dstfac="src_alpha"; break; + case 6: dstfac="one_minus_src_alpha"; break; + case 7: dstfac="dst_alpha"; break; + case 8: dstfac="one_minus_dst_alpha"; break; + case 9: dstfac="dst_color"; break; + case 10: dstfac="one_minus_dst_color"; break; + case 11: dstfac="src_alpha_sat"; break; + case 12: dstfac="both_src_alpha"; break; + case 13: dstfac="both_one_minus_src_alpha"; break; + case 14: dstfac="blend_factor"; break; + case 15: dstfac="one_minus_blend_factor"; break; + } + + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\t\tblendfunc %s %s\n", srcfac, dstfac),ofs+=strlen(shad+ofs); //just in case. + + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "\t}\n"),ofs+=strlen(shad+ofs); + + Q_snprintf(shad+ofs,sizeof(shad)-ofs, "}\n"); + plugfuncs->Free(base); + LoadMaterialString(ps, shad); + return true; + } + else + Con_Printf(CON_WARNING"%s doesn't seem to be a material? table=%#x..%#x\n", filename, header->ofs_table, header->ofs_table_end); + plugfuncs->Free(base); + return false; +} + +static qboolean SType_LoadShader(parsestate_t *ps, const char *filename, void (*LoadMaterialString)(parsestate_t *ps, const char *script)) +{ + char stypefname[MAX_QPATH]; + char *path; + const char *sep; + const char *at; + size_t pre; + char *base, *in, *out; + + sep = strrchr(filename, '/'); + if (!sep++) + return COD2_DecodeMaterial(ps, filename, LoadMaterialString); + at = strrchr(sep, '@'); + if (!at) + return false; //nope, no shadertype specified. depend on the engine/loader/etc defaults. + + if (!strncmp(filename, "skins/", 5)) + path = "shadertypes/model/"; + else if (!strncmp(filename, "textures/", 9)) + path = "shadertypes/world/"; + else if (!strncmp(filename, "gfx/", 4)) + path = "shadertypes/2d/"; + else + return false; //nope, not gonna try. + + pre = strlen(path); + if (pre+(at-sep)+7 > sizeof(stypefname)) + return false; //nope, too long... + memcpy(stypefname, path, pre); + memcpy(stypefname+pre, sep, at-sep); + memcpy(stypefname+pre+(at-sep), ".stype", 7); + + in = out = base = fsfuncs->LoadFile(stypefname, &pre); + if (!base) + return false; //nope, bad filename + //yay we got something, but we need to fix up the $texturename strings to $diffuse because $reasons + while (*in) + { + if (*in == '$' && !strncmp(in, "$texturename", 12)) + { + memcpy(out, "$diffuse", 8); + out += 8; + in += 12; + } + else + *out++ = *in++; + } + *out = 0; + + //now hand over the fteised shader script. + in = base; + while(*in == ' ' || *in == '\t' || *in == '\n' || *in == '\r') + in++; + if (*in == '{') + in++; + LoadMaterialString(ps, in); + plugfuncs->Free(base); //done with it now. + return true; +} + +/*static struct sbuiltin_s codprograms[] = +{ + {QR_NONE} +};*/ +static plugmaterialloaderfuncs_t stypefuncs = +{ + "Cod Shader Types", + SType_LoadShader, + +// codprograms, +}; +qboolean STypes_Init(void) +{ + fsfuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs)); + if (!fsfuncs) + return false; + return plugfuncs->ExportInterface(plugmaterialloaderfuncs_name, &stypefuncs, sizeof(stypefuncs)); +} diff --git a/plugins/cod/codmod.c b/plugins/cod/codmod.c new file mode 100644 index 000000000..f4e47ebd0 --- /dev/null +++ b/plugins/cod/codmod.c @@ -0,0 +1,955 @@ +#include "../plugin.h" +#include "../engine/common/com_mesh.h" +static plugfsfuncs_t *filefuncs; +static plugmodfuncs_t *modfuncs; + +//Utility functions. silly plugins. +float Length(const vec3_t v) {return sqrt(DotProduct(v,v));} +float RadiusFromBounds (const vec3_t mins, const vec3_t maxs) +{ + int i; + vec3_t corner; + for (i=0 ; i<3 ; i++) + corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]); + return Length(corner); +} + + + +struct fstream_s +{ + void *start; + size_t len; + size_t ofs; + + size_t numbones; + galiasbone_t *bones; + float *basepose; +}; +struct lod_s +{ + float coverage; + const char *name; + unsigned short numtex; + const char *tex[64]; +}; + +static qboolean ReadEOF(struct fstream_s *f) +{ + return (f->ofs >= f->len); +} +static qbyte ReadByte(struct fstream_s *f) +{ + if (f->ofs+1 > f->len) + { + f->ofs++; + return 0; + } + return ((qbyte*)f->start)[f->ofs++]; +} +static const char *ReadBytes(struct fstream_s *f, size_t len) +{ + f->ofs+=len; + if (f->ofs > f->len) + return NULL; + return &((const char*)f->start)[f->ofs-len]; +} +static const char *ReadString(struct fstream_s *f) +{ + size_t len; + for (len = 0; f->ofs+len < f->len && ((qbyte*)f->start)[f->ofs+len]; len++) + ; + len++; //for the null + f->ofs += len; + return &((const qbyte*)f->start)[f->ofs-len]; +} +static unsigned short ReadUInt16(struct fstream_s *f) +{ + unsigned short r; + r = ReadByte(f); + r|= ReadByte(f) << 8; + return r; +} +static short ReadSInt16(struct fstream_s *f) +{ + return (signed short)ReadUInt16(f); +} +static unsigned int ReadUInt32(struct fstream_s *f) +{ + unsigned int r; + r = ReadByte(f); + r|= ReadByte(f) << 8; + r|= ReadByte(f) << 16; + r|= ReadByte(f) << 24; + return r; +} +static float ReadFloat(struct fstream_s *f) +{ + union { + unsigned int u; + float f; + } r; + r.u = ReadByte(f); + r.u|= ReadByte(f) << 8; + r.u|= ReadByte(f) << 16; + r.u|= ReadByte(f) << 24; + return r.f; +} +static qboolean Mod_XModel_LoadPart (struct model_s *mod, struct fstream_s *f) +{ + unsigned short ver = ReadUInt16(f); + unsigned short b, nboner = ReadUInt16(f); + unsigned short nbonea = ReadUInt16(f); + float rel[12]; + switch(ver) + { + case 0x0e: + break; + case 0x14: + break; + default: + Con_Printf(CON_ERROR"%s: Unknown version %#x\n", mod->name, ver); + return false; + } +// Con_Printf("%s: version %x rb:%i ab:%i\n", mod->name, ver, nboner, nbonea); + f->numbones = nbonea+nboner; + f->basepose = plugfuncs->GMalloc(&mod->memgroup, sizeof(*f->basepose)*12 * f->numbones); + f->bones = plugfuncs->GMalloc(&mod->memgroup, sizeof(*f->bones) * f->numbones); + + for (b = 0; b < nbonea; b++) + { //root bones, with identity position. for some reason. + f->bones[b].parent = -1; + + VectorClear(f->bones[b].ref.org); + Vector4Set(f->bones[b].ref.quat, 0, 0, 0, 1); + VectorSet(f->bones[b].ref.scale, 1, 1, 1); + modfuncs->GenMatrixPosQuat4Scale(f->bones[b].ref.org, f->bones[b].ref.quat, f->bones[b].ref.scale, f->basepose + b*12); + } + for (; b < f->numbones; b++) + { + f->bones[b].parent = ReadByte(f); + + if (f->bones[b].parent >= b) + Con_Printf(CON_ERROR"b%i (%s) has parent %i\n", b, f->bones[b].name, f->bones[b].parent); + + f->bones[b].ref.org[0] = ReadFloat(f); + f->bones[b].ref.org[1] = ReadFloat(f); + f->bones[b].ref.org[2] = ReadFloat(f); + + f->bones[b].ref.quat[0] = ReadSInt16(f)/32767.0f; + f->bones[b].ref.quat[1] = ReadSInt16(f)/32767.0f; + f->bones[b].ref.quat[2] = ReadSInt16(f)/32767.0f; + f->bones[b].ref.quat[3] = 1.0-DotProduct(f->bones[b].ref.quat,f->bones[b].ref.quat); //reconstruct the w part. + if (f->bones[b].ref.quat[3]>0) + f->bones[b].ref.quat[3] = sqrt(f->bones[b].ref.quat[3]); + else + f->bones[b].ref.quat[3] = 0; + + VectorSet(f->bones[b].ref.scale, 1, 1, 1); + + modfuncs->GenMatrixPosQuat4Scale(f->bones[b].ref.org, f->bones[b].ref.quat, f->bones[b].ref.scale, rel); + modfuncs->ConcatTransforms((void*)(f->basepose + f->bones[b].parent*12), (void*)rel, (void*)(f->basepose + b*12)); + +// Con_Printf("b%i: p:%i, [%f %f %f] [%f %f %f %f]\n", b, f->bones[b].parent, pos[0],pos[1],pos[2], quat[0],quat[1],quat[2],quat[3]); + } + for (b = 0; b < f->numbones; b++) + { + vec3_t mins, maxs; + const char *n = ReadString(f); + if (ver >= 0x14) + { //omitted. + VectorClear(mins); + VectorClear(maxs); + } + else + { + mins[0] = ReadFloat(f); + mins[1] = ReadFloat(f); + mins[2] = ReadFloat(f); + maxs[0] = ReadFloat(f); + maxs[1] = ReadFloat(f); + maxs[2] = ReadFloat(f); + } + + //hack... I assume the (game)code does a skel_setbone so this doesn't actually matter? + if (!strcmp(n, "torso_stabilizer")) + Vector4Set(f->bones[b].ref.quat, 0,0,0,1); + + Q_strlcpy(f->bones[b].name, n, sizeof(f->bones[b].name)); + modfuncs->M3x4_Invert(f->basepose+12*b, f->bones[b].inverse); + +// if (ver >= 0x14) +// Con_Printf("b%i: %-20s\n", b, n); +// else +// Con_Printf("b%i: %-20s [%f %f %f] [%f %f %f]\n", b, n, mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); + } + for (b = 0; b < f->numbones; b++) + f->bones[b].group = ReadByte(f); //presumed. + return true; +} +static qboolean Mod_XModel_LoadSurfs (struct model_s *mod, struct fstream_s *f, struct lod_s *lod, float mincov) +{ + galiasinfo_t *surf; + galiasskin_t *skins; + skinframe_t *skinframe; + unsigned short ver = ReadUInt16(f); + unsigned short n, nsurfs = ReadUInt16(f); + switch(ver) + { + case 0x0e: + break; + case 0x14: + break; + default: + Con_Printf("%s: Unknown version %#x\n", mod->name, ver); + return false; + } + + if (!nsurfs) + return true; //nothing to do... + + surf = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf)*nsurfs + + sizeof(*skins)*nsurfs + + sizeof(*skinframe)*nsurfs); + skins = (galiasskin_t*)(surf+nsurfs); + skinframe = (skinframe_t*)(skins+nsurfs); + for (n = 0; n < nsurfs; n++) + { + surf[n].numbones = f->numbones; + surf[n].ofsbones = f->bones; + surf[n].baseframeofs = f->basepose; + + surf[n].mindist = mincov; + surf[n].maxdist = lod->coverage; + surf[n].shares_verts = n; + surf[n].shares_bones = 0; + surf[n].nextsurf = &surf[n+1]; + surf[n].ofsskins = &skins[n]; + surf[n].numskins = 1; + skins[n].skinspeed = 0.1; + skins[n].frame = &skinframe[n]; + skins[n].numframes = 1; + Q_snprintf(surf[n].surfacename, sizeof(surf[n].surfacename), "%s/%i", lod->name, n); + Q_strlcpy(skins[n].name, lod->name, sizeof(skins[n].name)); + if (n < lod->numtex) + Q_snprintf(skinframe[n].shadername, sizeof(skinframe[n].shadername), (ver==0x0e)?"skins/%s":"%s", lod->tex[n]); + } + surf[nsurfs-1].nextsurf = mod->meshinfo; + mod->meshinfo = surf; + for(surf = surf[nsurfs-1].nextsurf; surf; surf = surf->nextsurf) + surf->shares_verts += nsurfs; + surf = mod->meshinfo; + + for (n = 0; n < nsurfs; n++, surf++) + { + int flags = ReadByte(f); + int v, nverts = ReadUInt16(f); + int t, ntris = ReadUInt16(f); + int r, runs = (ver==0xe)?ReadUInt16(f):0; + unsigned short wcount = 0; + qbyte run; + unsigned int idx1, idx2, idx3; + + unsigned short boneidx = ReadUInt16(f); + qboolean boney = boneidx == (unsigned short)~0u; + int exweights = 0; + unsigned short *vw = NULL; +// unsigned short bunk2; + + vec3_t norm; + vec4_t xyzw; + const float *matrix; + + (void)flags; + + if (boney) + { + if (ver == 0x0e) + exweights = ReadUInt16(f); + /*bunk2 =*/ ReadUInt16(f); //seems to be vaguely related to vert/triangle counts. + } + + surf->numverts = nverts; + + if (ver == 0xe) + { //cod1 + surf->ofs_indexes = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_indexes ) * ntris*3); + for(t = 0, r = 0; r < runs; r++) + { + run = ReadByte(f); + idx1 = ReadUInt16(f); + idx2 = ReadUInt16(f); + run-=2; + + for (;;) + { //strip + if(!run--) break; + idx3 = ReadUInt16(f); + if (idx1 != idx2 && idx1 != idx3 && idx2 != idx3 && t < ntris) + { + surf->ofs_indexes[t*3+0] = idx1; + surf->ofs_indexes[t*3+1] = idx2; + surf->ofs_indexes[t*3+2] = idx3; + t++; + } + idx1 = idx2; + idx2 = idx3; + + //alternating triangles flip the order + if(!run--) break; + idx3 = ReadUInt16(f); + if (idx1 != idx2 && idx1 != idx3 && idx2 != idx3 && t < ntris) + { + surf->ofs_indexes[t*3+2] = idx1; + surf->ofs_indexes[t*3+1] = idx2; + surf->ofs_indexes[t*3+0] = idx3; + t++; + } + idx1 = idx2; + idx2 = idx3; + } + } + + if (ntris != t) + { + Con_Printf(CON_ERROR"Expected %i tris, got %i from %i runs\n", ntris, t, r); + return false; + } + surf->numindexes = t*3; + + //lazy and a bit slower. + surf->ofs_st_array = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_st_array ) * surf->numverts); + surf->ofs_skel_xyz = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_xyz ) * surf->numverts); + surf->ofs_skel_norm = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_norm ) * surf->numverts); + if (boney || f->numbones>0) + { + surf->ofs_skel_idx = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_idx ) * surf->numverts); + surf->ofs_skel_weight = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_weight ) * surf->numverts); + vw = memset(alloca(sizeof(*vw)*nverts), 0, sizeof(*vw)*nverts); + } + boneidx = min(boneidx,f->numbones-1); + + for (v = 0; v < nverts; v++) + { + norm[0] = ReadFloat(f); + norm[1] = ReadFloat(f); + norm[2] = ReadFloat(f); + surf->ofs_st_array[v][0] = ReadFloat(f); + surf->ofs_st_array[v][1] = ReadFloat(f); + if (boney) + { + wcount = ReadUInt16(f); + boneidx = ReadUInt16(f); + boneidx = min(boneidx,f->numbones-1); + } + + xyzw[0] = ReadFloat(f); + xyzw[1] = ReadFloat(f); + xyzw[2] = ReadFloat(f); + if (wcount) + { + xyzw[3] = ReadFloat(f); + VectorScale(xyzw, xyzw[3], xyzw); + } + else + xyzw[3] = 1; + + if (surf->ofs_skel_idx) + { + vw[v] = wcount; //urgh. + surf->ofs_skel_idx[v][0] = surf->ofs_skel_idx[v][1] = surf->ofs_skel_idx[v][2] = surf->ofs_skel_idx[v][3] = boneidx; //set all of them, cache might thank us. + surf->ofs_skel_weight[v][0] = xyzw[3]; + } + + //calculate the correct position in the base pose + matrix = f->basepose + boneidx*12; + surf->ofs_skel_xyz[ v][0] = xyzw[0] * matrix[0] + xyzw[1] * matrix[1] + xyzw[2] * matrix[ 2] + xyzw[3] * matrix[ 3]; + surf->ofs_skel_xyz[ v][1] = xyzw[0] * matrix[4] + xyzw[1] * matrix[5] + xyzw[2] * matrix[ 6] + xyzw[3] * matrix[ 7]; + surf->ofs_skel_xyz[ v][2] = xyzw[0] * matrix[8] + xyzw[1] * matrix[9] + xyzw[2] * matrix[10] + xyzw[3] * matrix[11]; + surf->ofs_skel_norm[v][0] = norm[0] * matrix[0] + norm[1] * matrix[1] + norm[2] * matrix[ 2]; + surf->ofs_skel_norm[v][1] = norm[0] * matrix[4] + norm[1] * matrix[5] + norm[2] * matrix[ 6]; + surf->ofs_skel_norm[v][2] = norm[0] * matrix[8] + norm[1] * matrix[9] + norm[2] * matrix[10]; + } + if (vw) + { + float lowestv; + int lowesti, j; + for (v = 0; v < nverts; v++) + { + for (; vw[v] > 0; vw[v]--) + { + if (--exweights < 0) + break; + boneidx = ReadUInt16(f); + boneidx = min(boneidx,f->numbones-1); + xyzw[0] = ReadFloat(f); + xyzw[1] = ReadFloat(f); + xyzw[2] = ReadFloat(f); + xyzw[3] = ReadFloat(f); + VectorScale(xyzw, xyzw[3], xyzw); + + matrix = f->basepose + boneidx*12; + surf->ofs_skel_xyz[ v][0] += xyzw[0] * matrix[0] + xyzw[1] * matrix[1] + xyzw[2] * matrix[ 2] + xyzw[3] * matrix[ 3]; + surf->ofs_skel_xyz[ v][1] += xyzw[0] * matrix[4] + xyzw[1] * matrix[5] + xyzw[2] * matrix[ 6] + xyzw[3] * matrix[ 7]; + surf->ofs_skel_xyz[ v][2] += xyzw[0] * matrix[8] + xyzw[1] * matrix[9] + xyzw[2] * matrix[10] + xyzw[3] * matrix[11]; + + lowesti = 0; + lowestv = surf->ofs_skel_weight[v][0]; + for (j = 1; j < countof(surf->ofs_skel_idx[v]); j++) + { + if (surf->ofs_skel_weight[v][j] < lowestv) + lowestv = surf->ofs_skel_weight[v][lowesti=j]; + } + if (lowestv < xyzw[3]) + { + surf->ofs_skel_idx[v][lowesti] = boneidx; + surf->ofs_skel_weight[v][lowesti] = xyzw[3]; + } + } + //compensate for any missing weights + xyzw[3] = surf->ofs_skel_weight[v][0]+surf->ofs_skel_weight[v][1]+surf->ofs_skel_weight[v][2]+surf->ofs_skel_weight[v][3]; + if (xyzw[3]>0) + Vector4Scale(surf->ofs_skel_weight[v], 1/xyzw[3], surf->ofs_skel_weight[v]); + } + } + if (exweights) + { //something was misread + surf->numverts = 0; + surf->numindexes = 0; + return false; + } + + //compute the tangents that are not stored in the file. + surf->ofs_skel_svect = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_svect ) * surf->numverts); + surf->ofs_skel_tvect = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_tvect ) * surf->numverts); + modfuncs->AccumulateTextureVectors(surf->ofs_skel_xyz, surf->ofs_st_array, surf->ofs_skel_norm, surf->ofs_skel_svect, surf->ofs_skel_tvect, surf->ofs_indexes, surf->numindexes, false); + modfuncs->NormaliseTextureVectors(surf->ofs_skel_norm, surf->ofs_skel_svect, surf->ofs_skel_tvect, surf->numverts, false); + } + else if (ver == 0x14) + { //cod2 + surf->ofs_st_array = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_st_array ) * surf->numverts); + surf->ofs_skel_xyz = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_xyz ) * surf->numverts); + surf->ofs_skel_norm = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_norm ) * surf->numverts); + surf->ofs_skel_svect = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_svect ) * surf->numverts); + surf->ofs_skel_tvect = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_tvect ) * surf->numverts); + surf->ofs_rgbaub = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_rgbaub ) * surf->numverts); + + if (boney || f->numbones) + { + surf->ofs_skel_idx = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_idx ) * surf->numverts); + surf->ofs_skel_weight = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_skel_weight ) * surf->numverts); + } + boneidx = min(boneidx,f->numbones-1); + + for (v = 0; v < nverts; v++) + { + surf->ofs_skel_norm[v][0] = ReadFloat(f); + surf->ofs_skel_norm[v][1] = ReadFloat(f); + surf->ofs_skel_norm[v][2] = ReadFloat(f); + surf->ofs_rgbaub[v][0] = ReadByte(f); + surf->ofs_rgbaub[v][1] = ReadByte(f); + surf->ofs_rgbaub[v][2] = ReadByte(f); + surf->ofs_rgbaub[v][3] = ReadByte(f); + surf->ofs_st_array[v][0] = ReadFloat(f); + surf->ofs_st_array[v][1] = ReadFloat(f); + surf->ofs_skel_svect[v][0] = ReadFloat(f); + surf->ofs_skel_svect[v][1] = ReadFloat(f); + surf->ofs_skel_svect[v][2] = ReadFloat(f); + surf->ofs_skel_tvect[v][0] = ReadFloat(f); + surf->ofs_skel_tvect[v][1] = ReadFloat(f); + surf->ofs_skel_tvect[v][2] = ReadFloat(f); + if (boney) + { + int w; + wcount = ReadByte(f); + boneidx = ReadUInt16(f); + boneidx = min(boneidx,f->numbones-1); + xyzw[0] = ReadFloat(f); + xyzw[1] = ReadFloat(f); + xyzw[2] = ReadFloat(f); + xyzw[3] = wcount?ReadByte(f)/255.f:1; + VectorScale(xyzw, xyzw[3], xyzw); + matrix = f->basepose + boneidx*12; + surf->ofs_skel_xyz[ v][0] = xyzw[0] * matrix[0] + xyzw[1] * matrix[1] + xyzw[2] * matrix[ 2] + xyzw[3] * matrix[ 3]; + surf->ofs_skel_xyz[ v][1] = xyzw[0] * matrix[4] + xyzw[1] * matrix[5] + xyzw[2] * matrix[ 6] + xyzw[3] * matrix[ 7]; + surf->ofs_skel_xyz[ v][2] = xyzw[0] * matrix[8] + xyzw[1] * matrix[9] + xyzw[2] * matrix[10] + xyzw[3] * matrix[11]; + + surf->ofs_skel_idx[v][0] = surf->ofs_skel_idx[v][1] = surf->ofs_skel_idx[v][2] = surf->ofs_skel_idx[v][3] = boneidx; + surf->ofs_skel_weight[v][0] = xyzw[3]; surf->ofs_skel_weight[v][1] = surf->ofs_skel_weight[v][2] = surf->ofs_skel_weight[v][3] = 0; + + for (w = 1; w <= wcount; w++) + { + boneidx = ReadUInt16(f); + boneidx = min(boneidx,f->numbones-1); + xyzw[0] = ReadFloat(f); + xyzw[1] = ReadFloat(f); + xyzw[2] = ReadFloat(f); + xyzw[3] = ReadUInt16(f)/65535.f; + VectorScale(xyzw, xyzw[3], xyzw); + matrix = f->basepose + boneidx*12; + surf->ofs_skel_xyz[ v][0] += xyzw[0] * matrix[0] + xyzw[1] * matrix[1] + xyzw[2] * matrix[ 2] + xyzw[3] * matrix[ 3]; + surf->ofs_skel_xyz[ v][1] += xyzw[0] * matrix[4] + xyzw[1] * matrix[5] + xyzw[2] * matrix[ 6] + xyzw[3] * matrix[ 7]; + surf->ofs_skel_xyz[ v][2] += xyzw[0] * matrix[8] + xyzw[1] * matrix[9] + xyzw[2] * matrix[10] + xyzw[3] * matrix[11]; + if (w < 4) + { + surf->ofs_skel_idx[v][w] = boneidx; + surf->ofs_skel_weight[v][w] = xyzw[3]; + } + } + + //compensate for any missing weights + xyzw[3] = surf->ofs_skel_weight[v][0]+surf->ofs_skel_weight[v][1]+surf->ofs_skel_weight[v][2]+surf->ofs_skel_weight[v][3]; + if (xyzw[3]>0&&xyzw[3]!=1) + Vector4Scale(surf->ofs_skel_weight[v], 1/xyzw[3], surf->ofs_skel_weight[v]); + } + else + { + surf->ofs_skel_xyz[v][0] = ReadFloat(f); + surf->ofs_skel_xyz[v][1] = ReadFloat(f); + surf->ofs_skel_xyz[v][2] = ReadFloat(f); + + if (surf->ofs_skel_idx) + { + surf->ofs_skel_idx[v][0] = surf->ofs_skel_idx[v][1] = surf->ofs_skel_idx[v][2] = surf->ofs_skel_idx[v][3] = boneidx; + surf->ofs_skel_weight[v][0] = 1; surf->ofs_skel_weight[v][1] = surf->ofs_skel_weight[v][2] = surf->ofs_skel_weight[v][3] = 0; + } + } + } + + //indexes moved to AFTER. also triangles instead of weird strips. much nicer. + surf->numindexes = ntris*3; + surf->ofs_indexes = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf->ofs_indexes ) * surf->numindexes); + for (v = 0; v < ntris*3; v++) + surf->ofs_indexes[v] = ReadUInt16(f); + } + } + return true; +} + +struct codbone_s +{ //source data is the number of samples, the pose index for each sample and THEN the actual samples at the specified sparse poses. +// const char *name; + unsigned int numquats; + qboolean flip; + struct + { + unsigned short ts; + signed short v[3]; + } *quats; + unsigned int numcoords; + struct + { + unsigned int ts; + vec3_t v; + } *coord; +}; + +static void Mod_XAnim_LoadQuats (struct model_s *mod, struct fstream_s *f, struct codbone_s *b, unsigned int maxts, qboolean flipquat, qboolean zonly) +{ + unsigned int i, n = ReadUInt16(f); + b->flip = flipquat; + if (n) + { + b->numquats = n; + b->quats = plugfuncs->GMalloc(&mod->memgroup, sizeof(*b->quats)*n); + if (n == 1 || n == maxts) + for (i = 0; i < n; i++) + b->quats[i].ts = i; + else if (maxts > 0xff) + for (i = 0; i < n; i++) + b->quats[i].ts = ReadUInt16(f); + else + for (i = 0; i < n; i++) + b->quats[i].ts = ReadByte(f); + + for (i = 0; i < n; i++) + { + b->quats[i].v[0] = zonly?0:ReadSInt16(f); + b->quats[i].v[1] = zonly?0:ReadSInt16(f); + b->quats[i].v[2] = ReadSInt16(f); + } + } +} +static void Mod_XAnim_LoadCoords (struct model_s *mod, struct fstream_s *f, struct codbone_s *b, unsigned int maxts) +{ + unsigned int i, n = ReadUInt16(f); + if (n) + { + b->numcoords = n; + b->coord = plugfuncs->GMalloc(&mod->memgroup, sizeof(*b->coord)*n); + if (n == 1 || n == maxts) + for (i = 0; i < n; i++) + b->coord[i].ts = i; + else if (maxts > 0xff) + for (i = 0; i < n; i++) + b->coord[i].ts = ReadUInt16(f); + else + for (i = 0; i < n; i++) + b->coord[i].ts = ReadByte(f); + + for (i = 0; i < n; i++) + { + b->coord[i].v[0] = ReadFloat(f); + b->coord[i].v[1] = ReadFloat(f); + b->coord[i].v[2] = ReadFloat(f); + } + } +} +static float *QDECL Mod_XAnim_GetRawBones(const struct galiasinfo_s *animmesh, const struct galiasanimation_s *a, float time, float *bonematrixstorage, const struct galiasbone_s *boneinf, int numbones) +{ + const struct codbone_s *bone = (const void*)(a+1); + size_t b, i, j; + int ts; + float frac; + galiasrefpose_t ref; + time *= a->rate; + ts = time; + frac = time - ts; //FIXME: negative time! + if (a->loop) + { + ts %= a->numposes; + if (ts < 0) + ts += a->numposes; + } + ts = bound(0, ts, a->numposes); + if (!boneinf) + { //our own bones?!? that'll be a horrible mess, but if you're really asking for that... + boneinf = animmesh->ofsbones; + numbones = min(numbones,animmesh->numbones); + } + + for (b = 0; b < numbones; b++, boneinf++) + { + ref = boneinf->ref; + for (j = 0; j < animmesh->numbones; j++) + { + if (!strcmp(boneinf->name, animmesh->ofsbones[j].name)) + { //okay this is the one we want. replace the reference pose with the animated data. + bone = (const struct codbone_s*)(a+1) + j; + for (i = 0; i < bone->numquats; i++) + if (ts < bone->quats[i].ts) + break; //we flew too close to the sun! + if (!i--) + ; //use the value from the model. + else if (i < bone->numquats-1 && bone->quats[i+1].ts == ts+1) + { + vec4_t oq,nq; + VectorScale(bone->quats[i].v, 1.f/32767, oq); + oq[3] = 1 - DotProduct(oq,oq); + if (oq[3] > 0) + oq[3] = sqrt(oq[3]); + + VectorScale(bone->quats[i+1].v, 1.f/32767, nq); + nq[3] = 1 - DotProduct(nq,nq); + if (nq[3] > 0) + nq[3] = sqrt(nq[3]); + + modfuncs->QuaternionSlerp(oq, nq, frac, ref.quat); + } + else + { + VectorScale(bone->quats[i].v, 1.f/32767, ref.quat); + ref.quat[3] = 1 - DotProduct(ref.quat,ref.quat); + if (ref.quat[3] > 0) + ref.quat[3] = sqrt(ref.quat[3]); + } + + for (i = 0; i < bone->numcoords; i++) + if (ts < bone->coord[i].ts) + break; + if (!i--) + ; //use the value from the model. + else if (i < bone->numcoords-1 && bone->coord[i+1].ts == ts+1) + VectorInterpolate(bone->coord[i].v, frac, bone->coord[i+1].v, ref.org); + else + VectorCopy(bone->coord[i].v, ref.org); + + break; //don't look for others. + } + } + modfuncs->GenMatrixPosQuat4Scale(ref.org, ref.quat, ref.scale, bonematrixstorage + b*12); + } + return bonematrixstorage; +} +static int Mod_XAnim_CompareEvents (const void *av, const void *bv) +{ + const galiasevent_t *a=av; + const galiasevent_t *b=bv; + return b->timestamp-a->timestamp; +} +static qboolean Mod_XAnim_Load (struct model_s *mod, void *buffer, size_t fsize) +{ + struct fstream_s f = {buffer, fsize}; + unsigned short ver = ReadUInt16(&f); + unsigned short numposes = ReadUInt16(&f); + unsigned short numabones=0, numrbones = ReadUInt16(&f); + unsigned char flags = ReadByte(&f); + unsigned short framerate = ReadUInt16(&f); + unsigned int i; + const qbyte *flip, *tiny; + struct codbone_s *bone; + galiasinfo_t *surf; + galiasanimation_t *anim; + galiasbone_t *gbones; + int numev; + switch(ver) + { + case 0x0e: //cod1 + break; + case 0x14: + break; + default: + Con_Printf(CON_ERROR"%s: unknown version %x\n", mod->name, ver); + return false; + } +// Con_Printf(CON_DEBUG"Poses:%i bones:%i flags:%i rate:%i\n", numposes, numrbones, flags, framerate); + + if (flags & 2) + numabones = 1; + + mod->type = mod_alias; + mod->fromgame = fg_new; + mod->meshinfo = surf = plugfuncs->GMalloc(&mod->memgroup, sizeof(*surf) + sizeof(*anim) + sizeof(*bone)*(numabones+numrbones)); + surf->numbones = numabones+numrbones; + surf->ofsbones = gbones = plugfuncs->GMalloc(&mod->memgroup, sizeof(*gbones)*(numabones+numrbones)); + + surf->numanimations = 1; + surf->ofsanimations = anim = (void*)(surf+1); + + anim->loop = !!(flags&1); + anim->numposes = numposes; + anim->skeltype = SKEL_RELATIVE; //no parents info stored here, so these are screwy unless you're importing them into a model that DOES provide the parents info for you. + bone = (void*)(anim+1); + anim->rate = framerate; + Q_strlcpy(anim->name, mod->name, sizeof(anim->name)); + anim->GetRawBones = Mod_XAnim_GetRawBones; + + for (i = 0; i < numabones; i++) + { + gbones[i].parent = -1; + VectorClear(gbones[i].ref.org); + Vector4Set(gbones[i].ref.quat,0,0,0,1); + VectorSet(gbones[i].ref.scale,1,1,1); + Q_strlcpy(gbones[i].name, "tag_origin", sizeof(gbones[i].name)); + + Mod_XAnim_LoadQuats(mod, &f, &bone[i], numposes, false, true); + Mod_XAnim_LoadCoords(mod, &f, &bone[i], numposes); + } + flip = ReadBytes(&f, (numrbones+7)>>3); + tiny = ReadBytes(&f, (numrbones+7)>>3); + if (anim->loop) + numposes++; //is this the right place? + for (i = 0; i < numrbones; i++) + { + const char *n = ReadString(&f); + gbones[numabones+i].parent = -1; //no information. oh noes. + VectorClear(gbones[i].ref.org); + Vector4Set(gbones[i].ref.quat,0,0,0,1); + VectorSet(gbones[i].ref.scale,1,1,1); + Q_strlcpy(gbones[numabones+i].name, n, sizeof(gbones[numabones+i].name)); +// Con_Printf(CON_DEBUG"Part %i = %s (%i %i)\n", numabones+i, bone[numabones+i].name, !!(flip[i>>3]&(1u<<(i&7))), !!(tiny[i>>3]&(1u<<(i&7)))); + } + for (i = 0; i < numrbones; i++) + { +// Con_Printf(CON_WARNING"%s:\n", gbones[numabones+i].name); + Mod_XAnim_LoadQuats(mod, &f, &bone[numabones+i], numposes, !!(flip[i>>3]&(1u<<(i&7))), !!(tiny[i>>3]&(1u<<(i&7)))); + Mod_XAnim_LoadCoords(mod, &f, &bone[numabones+i], numposes); + } + if (anim->loop) + numposes--; + + numev = ReadByte(&f); + if (numev) + { + anim->events = plugfuncs->GMalloc(&mod->memgroup, sizeof(*anim->events)*(numev)); + for (i = 0; i < numev; i++) + { + const char *txt = ReadString(&f); + anim->events[i].code = 0; //this format doesn't provide event ids, just pure strings. + anim->events[i].data = strcpy(plugfuncs->GMalloc(&mod->memgroup, strlen(txt)+1), txt); + + anim->events[i].timestamp = ReadUInt16(&f) / (float)numposes; + } + qsort(anim->events, numev, sizeof(*anim->events), Mod_XAnim_CompareEvents); //make sure they're sorted by timestamps. + for (i = 0; i < numev-1; i++) + anim->events[i].next = &anim->events[i+1]; + } + + if (f.ofs != f.len) + Con_Printf(CON_WARNING"Misread %s (%u bytes of %u)\n", mod->name, (unsigned)f.ofs, (unsigned)f.len); + return true; +} +qboolean QDECL Mod_XModel_Load (struct model_s *mod, void *buffer, size_t fsize) +{ + struct fstream_s f = {buffer, fsize}; + struct fstream_s pf = {NULL,0}; + unsigned short ver; + unsigned i; + unsigned int clod, clodnsurf; + unsigned short t; + struct lod_s lod[4]; + float mincov = 0; + int nlod; + + //I fucking hate this. + if (!strncmp(mod->publicname, "xanim/", 6)) //anims are not linked in any way, we treat them as separate precachable models so that gamecode can selectively load them. + return Mod_XAnim_Load(mod, buffer, fsize); + if (!strncmp(mod->publicname, "xmodelparts/", 12)) //loaded as part of models. don't get confused. + return false; + if (!strncmp(mod->publicname, "xmodelsurfs/", 12)) //loaded as part of models. don't get confused. + return false; + //"xmodels/" is okay though! + + ver = ReadUInt16(&f); + switch(ver) + { + case 0x0e: //cod1 + nlod = 3; + break; + case 0x14: //cod2 + /*type =*/ ReadByte(&f); + nlod = 4; + break; + default: + Con_Printf(CON_ERROR"%s: Unknown version %x\n", mod->name, ver); + return false; + } + mod->mins[0] = ReadFloat(&f); + mod->mins[1] = ReadFloat(&f); + mod->mins[2] = ReadFloat(&f); + mod->maxs[0] = ReadFloat(&f); + mod->maxs[1] = ReadFloat(&f); + mod->maxs[2] = ReadFloat(&f); + for (i = 0; i < nlod; i++) + { + lod[i].coverage = ReadFloat(&f); //coverage + lod[i].name = ReadString(&f); + } + clod = ReadUInt32(&f); + clodnsurf = ReadUInt32(&f); + (void)clod; + for (i = 0; i < clodnsurf && !ReadEOF(&f); i++) + { + unsigned int t, ntris = ReadUInt32(&f); + for (t = 0; t < ntris && !ReadEOF(&f); t++) + { + vec4_t p, sd, td; + //plane + p[0] = ReadFloat(&f); + p[1] = ReadFloat(&f); + p[2] = ReadFloat(&f); + p[3] = ReadFloat(&f); + + //?splane? + sd[0] = ReadFloat(&f); + sd[1] = ReadFloat(&f); + sd[2] = ReadFloat(&f); + sd[3] = ReadFloat(&f); + + //?tplane? + td[0] = ReadFloat(&f); + td[1] = ReadFloat(&f); + td[2] = ReadFloat(&f); + td[3] = ReadFloat(&f); + + (void)p; + (void)sd; + (void)td; + +// Con_Printf("%i: %f %f %f %f : %f %f %f %f : %f %f %f %f\n", t, p[0],p[1],p[2],p[3], sd[0],sd[1],sd[2],sd[3], td[0],td[1],td[2],td[3]); + //dunno how to use this for collision... a triangle has 3 side faces AND a front! + } + //bounds + ReadFloat(&f); + ReadFloat(&f); + ReadFloat(&f); + ReadFloat(&f); + ReadFloat(&f); + ReadFloat(&f); + /*boneidx = */ ReadUInt32(&f); + /*contents = */ ReadUInt32(&f); + /*surfaceflags = */ ReadUInt32(&f); + } + + pf.ofs = 0; + pf.start = filefuncs->LoadFile(va("xmodelparts/%s", lod[0].name), &pf.len); + if (pf.start) + { + if (!ReadEOF(&pf) && Mod_XModel_LoadPart(mod, &pf)) + { + if (pf.ofs != pf.len) + Con_Printf(CON_WARNING"Misread xmodelparts/%s (%u bytes of %u)\n", lod[0].name, (unsigned)f.ofs, (unsigned)f.len); + } + else + return false; + plugfuncs->Free(pf.start); + } + + for (i = 0; i < nlod; i++) + { + if (!*lod[i].name) + break; + lod[i].numtex = ReadUInt16(&f); + for (t = 0; t < lod[i].numtex; t++) + lod[i].tex[t] = ReadString(&f); + pf.ofs = 0; + pf.start = filefuncs->LoadFile(va("xmodelsurfs/%s", lod[i].name), &pf.len); + if (pf.start) + { + if (!ReadEOF(&pf) && Mod_XModel_LoadSurfs(mod, &pf, &lod[i], mincov)) + { + if (pf.ofs != pf.len) + Con_Printf(CON_WARNING"Misread xmodelsurfs/%s (%u bytes of %u)\n", lod[i].name, (unsigned)f.ofs, (unsigned)f.len); + } + else + return false; + mincov = lod[i].coverage; + plugfuncs->Free(pf.start); + } + } + + + mod->type = mod_alias; + mod->fromgame = fg_new; + mod->radius = RadiusFromBounds(mod->mins, mod->maxs); +// if (mod->meshinfo) +// modfuncs->BIH_BuildAlias(mod, mod->meshinfo); + return !!mod->meshinfo; +} + +static qboolean XMODEL_Init(void) +{ + filefuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*filefuncs)); + modfuncs = plugfuncs->GetEngineInterface(plugmodfuncs_name, sizeof(*modfuncs)); + if (modfuncs && modfuncs->version != MODPLUGFUNCS_VERSION) + modfuncs = NULL; + + if (modfuncs && filefuncs) + { + modfuncs->RegisterModelFormatMagic("XMODEL", "\x0e\0",2, Mod_XModel_Load); //cod1 + modfuncs->RegisterModelFormatMagic("XMODEL", "\x14\0",2, Mod_XModel_Load); //cod2 + return true; + } + return false; +} + + +qboolean CODBSP_Init(void); +qboolean STypes_Init(void); +qboolean IWI_Init(void); +//qboolean IWFF_Init(void); + +qboolean Plug_Init(void) +{ + qboolean somethingisokay = false; + char plugname[128]; + strcpy(plugname, "cod"); + plugfuncs->GetPluginName(0, plugname, sizeof(plugname)); + + if (!STypes_Init()) Con_Printf(CON_ERROR"%s: Shader Types support unavailable\n", plugname); else somethingisokay = true; + if (!IWI_Init()) Con_Printf(CON_ERROR"%s: IWI support unavailable\n", plugname); else somethingisokay = true; + if (!XMODEL_Init()) Con_Printf(CON_ERROR"%s: XModel support unavailable\n", plugname); else somethingisokay = true; + if (!CODBSP_Init()) Con_Printf(CON_ERROR"%s: CODBSP support unavailable\n", plugname); else somethingisokay = true; +// if (!IWFF_Init()) Con_Printf(CON_ERROR"%s: FF support unavailable\n", plugname); else somethingisokay = true; + return somethingisokay; +}