#include "quakedef.h" #ifdef MAP_PROC #ifndef SERVERONLY #include "shader.h" #endif #include "com_mesh.h" //FIXME: shadowmaps should build a cache of the nearby area surfaces and flag those models as RF_NOSHADOW or something //fixme: merge areas and static ents too somehow. void Mod_SetParent (mnode_t *node, mnode_t *parent); static int D3_ClusterForPoint (struct model_s *model, vec3_t point); #ifndef SERVERONLY void ModD3_GenAreaVBO(void *ctx, void *data, size_t a, size_t b) { model_t *sub = ctx; BE_GenBrushModelVBO(sub); } static void R_BuildDefaultTexnums_Doom3(shader_t *shader) { extern qboolean r_loadbumpmapping; extern cvar_t gl_specular; extern cvar_t r_fb_bmodels; char *h; char imagename[MAX_QPATH]; char mapname[MAX_QPATH]; char *subpath = NULL; texnums_t *tex; unsigned int a, aframes; unsigned int imageflags = 0; strcpy(imagename, shader->name); h = strchr(imagename, '#'); if (h) *h = 0; if (*imagename == '/' || strchr(imagename, ':')) { //this is not security. this is anti-spam for the verbose security in the filesystem code. Con_Printf("Warning: shader has absolute path: %s\n", shader->name); *imagename = 0; } tex = shader->defaulttextures; aframes = max(1, shader->numdefaulttextures); //if any were specified explicitly, replicate that into all. //this means animmap can be used, with any explicit textures overriding all. for (a = 1; a < aframes; a++) { if (!TEXVALID(tex[a].base)) tex[a].base = tex[0].base; if (!TEXVALID(tex[a].bump)) tex[a].bump = tex[0].bump; if (!TEXVALID(tex[a].fullbright)) tex[a].fullbright = tex[0].fullbright; if (!TEXVALID(tex[a].specular)) tex[a].specular = tex[0].specular; if (!TEXVALID(tex[a].loweroverlay)) tex[a].loweroverlay = tex[0].loweroverlay; if (!TEXVALID(tex[a].upperoverlay)) tex[a].upperoverlay = tex[0].upperoverlay; if (!TEXVALID(tex[a].reflectmask)) tex[a].reflectmask = tex[0].reflectmask; if (!TEXVALID(tex[a].reflectcube)) tex[a].reflectcube = tex[0].reflectcube; } for (a = 0; a < aframes; a++, tex++) { COM_StripExtension(tex->mapname, mapname, sizeof(mapname)); if (!TEXVALID(tex->base)) { /*dlights/realtime lighting needs some stuff*/ if (!TEXVALID(tex->base) && *tex->mapname)// && (shader->flags & SHADER_HASDIFFUSE)) tex->base = R_LoadHiResTexture(tex->mapname, NULL, 0); if (!TEXVALID(tex->base)) tex->base = R_LoadHiResTexture(va("%s_d", imagename), subpath, (*imagename=='{')?0:IF_NOALPHA); } imageflags |= IF_LOWPRIORITY; COM_StripExtension(imagename, imagename, sizeof(imagename)); if (!TEXVALID(tex->bump)) { if ((shader->flags & SHADER_HASNORMALMAP) && r_loadbumpmapping) { if (!TEXVALID(tex->bump) && *mapname && (shader->flags & SHADER_HASNORMALMAP)) tex->bump = R_LoadHiResTexture(va("%s_local", mapname), NULL, imageflags|IF_TRYBUMP|IF_NOSRGB); if (!TEXVALID(tex->bump)) tex->bump = R_LoadHiResTexture(va("%s_local", imagename), subpath, imageflags|IF_TRYBUMP|IF_NOSRGB); } } if (!TEXVALID(tex->loweroverlay)) { if (shader->flags & SHADER_HASTOPBOTTOM) { if (!TEXVALID(tex->loweroverlay) && *mapname) tex->loweroverlay = R_LoadHiResTexture(va("%s_pants", mapname), NULL, imageflags); if (!TEXVALID(tex->loweroverlay)) tex->loweroverlay = R_LoadHiResTexture(va("%s_pants", imagename), subpath, imageflags); /*how rude*/ } } if (!TEXVALID(tex->upperoverlay)) { if (shader->flags & SHADER_HASTOPBOTTOM) { if (!TEXVALID(tex->upperoverlay) && *mapname) tex->upperoverlay = R_LoadHiResTexture(va("%s_shirt", mapname), NULL, imageflags); if (!TEXVALID(tex->upperoverlay)) tex->upperoverlay = R_LoadHiResTexture(va("%s_shirt", imagename), subpath, imageflags); } } if (!TEXVALID(tex->specular)) { if ((shader->flags & SHADER_HASGLOSS) && gl_specular.value) { if (!TEXVALID(tex->specular) && *mapname) tex->specular = R_LoadHiResTexture(va("%s_s", mapname), NULL, imageflags); if (!TEXVALID(tex->specular)) tex->specular = R_LoadHiResTexture(va("%s_s", imagename), subpath, imageflags); } } if (!TEXVALID(tex->fullbright)) { if ((shader->flags & SHADER_HASFULLBRIGHT) && r_fb_bmodels.value && gl_load24bit.value) { if (!TEXVALID(tex->fullbright) && *mapname) tex->fullbright = R_LoadHiResTexture(va("%s_luma:%s_glow", mapname, mapname), NULL, imageflags); if (!TEXVALID(tex->fullbright)) tex->fullbright = R_LoadHiResTexture(va("%s_luma:%s_glow", imagename, imagename), subpath, imageflags); } } } } static qboolean Mod_LoadMap_Proc(model_t *model, char *data) { char token[256]; int ver = 0; data = COM_ParseOut(data, token, sizeof(token)); if (!strcmp(token, "mapProcFile003")) ver = 3; if (!strcmp(token, "PROC")) { data = COM_ParseOut(data, token, sizeof(token)); ver = atoi(token); } if (ver != 3 && ver != 4) { Con_Printf("proc format not compatible %s\n", token); return false; } /*FIXME: add sanity checks*/ if (ver == 4) { data = COM_ParseOut(data, token, sizeof(token)); } while(1) { data = COM_ParseOut(data, token, sizeof(token)); if (!data) break; else if (!strcmp(token, "model")) { batch_t *b; mesh_t *m, **ml; model_t *sub; float f; int numsurfs, surf; int numverts, v, j; int numindicies; char *vdata; data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "{")) return false; data = COM_ParseOut(data, token, sizeof(token)); sub = Mod_FindName(va("*%s", token)); data = COM_ParseOut(data, token, sizeof(token)); numsurfs = atoi(token); if (numsurfs < 0 || numsurfs > 10000) return false; if (numsurfs) { b = ZG_Malloc(&model->memgroup, sizeof(*b) * numsurfs); m = ZG_Malloc(&model->memgroup, sizeof(*m) * numsurfs); ml = ZG_Malloc(&model->memgroup, sizeof(*ml) * numsurfs); } else { b = NULL; m = NULL; ml = NULL; } sub->numsurfaces = numsurfs; //ver4 may have a 'sky' field here vdata = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "{") && strcmp(token, "}")) { //sky = atoi(token); data = vdata; } sub->mins[0] = 99999999; sub->mins[1] = 99999999; sub->mins[2] = 99999999; sub->maxs[0] = -99999999; sub->maxs[1] = -99999999; sub->maxs[2] = -99999999; for (surf = 0; surf < numsurfs; surf++) { data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "{")) break; if (!data) return false; b[surf].maxmeshes = 1; b[surf].mesh = &ml[surf]; ml[surf] = &m[surf]; b[surf].lightmap[0] = -1; b[surf].lightmap[1] = -1; b[surf].lightmap[2] = -1; b[surf].lightmap[3] = -1; b[surf].lmlightstyle[0] = 0; b[surf].lmlightstyle[1] = INVALID_LIGHTSTYLE; b[surf].lmlightstyle[2] = INVALID_LIGHTSTYLE; b[surf].lmlightstyle[3] = INVALID_LIGHTSTYLE; data = COM_ParseOut(data, token, sizeof(token)); b[surf].shader = R_RegisterShader_Vertex(token); R_BuildDefaultTexnums_Doom3(b[surf].shader); data = COM_ParseOut(data, token, sizeof(token)); numverts = atoi(token); data = COM_ParseOut(data, token, sizeof(token)); numindicies = atoi(token); b[surf].next = sub->batches[b[surf].shader->sort]; sub->batches[b[surf].shader->sort] = &b[surf]; m[surf].numvertexes = numverts; m[surf].numindexes = numindicies; vdata = ZG_Malloc(&sub->memgroup, numverts * (sizeof(vecV_t) + sizeof(vec2_t) + sizeof(vec3_t)*3 + sizeof(vec4_t)) + numindicies * sizeof(index_t)); m[surf].colors4f_array[0] = (vec4_t*)vdata;vdata += sizeof(vec4_t)*numverts; m[surf].xyz_array = (vecV_t*)vdata;vdata += sizeof(vecV_t)*numverts; m[surf].st_array = (vec2_t*)vdata;vdata += sizeof(vec2_t)*numverts; m[surf].normals_array = (vec3_t*)vdata;vdata += sizeof(vec3_t)*numverts; m[surf].snormals_array = (vec3_t*)vdata;vdata += sizeof(vec3_t)*numverts; m[surf].tnormals_array = (vec3_t*)vdata;vdata += sizeof(vec3_t)*numverts; m[surf].indexes = (index_t*)vdata; for (v = 0; v < numverts; v++) { data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "(")) return false; data = COM_ParseOut(data, token, sizeof(token)); m[surf].xyz_array[v][0] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); m[surf].xyz_array[v][1] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); m[surf].xyz_array[v][2] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); m[surf].st_array[v][0] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); m[surf].st_array[v][1] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); m[surf].normals_array[v][0] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); m[surf].normals_array[v][1] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); m[surf].normals_array[v][2] = atof(token); for (j = 0; j < 3; j++) { f = m[surf].xyz_array[v][j]; if (f > sub->maxs[j]) sub->maxs[j] = f; if (f < sub->mins[j]) sub->mins[j] = f; } m[surf].colors4f_array[0][v][0] = 1; m[surf].colors4f_array[0][v][1] = 1; m[surf].colors4f_array[0][v][2] = 1; m[surf].colors4f_array[0][v][3] = 1; data = COM_ParseOut(data, token, sizeof(token)); /*if its not closed yet, there's an optional colour value*/ if (strcmp(token, ")")) { m[surf].colors4f_array[0][v][0] = atof(token)/255; data = COM_ParseOut(data, token, sizeof(token)); m[surf].colors4f_array[0][v][1] = atof(token)/255; data = COM_ParseOut(data, token, sizeof(token)); m[surf].colors4f_array[0][v][2] = atof(token)/255; data = COM_ParseOut(data, token, sizeof(token)); m[surf].colors4f_array[0][v][3] = atof(token)/255; data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, ")")) return false; } } for (v = 0; v < numindicies; v++) { data = COM_ParseOut(data, token, sizeof(token)); m[surf].indexes[v] = atoi(token); } //generate the s+t vectors according to the normals that we just parsed. R_Generate_Mesh_ST_Vectors(&m[surf]); data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "}")) return false; } data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "}")) return false; // sub->loadstate = MLS_LOADED; sub->fromgame = fg_doom3; sub->type = mod_brush; sub->lightmaps.surfstyles = 1; COM_AddWork(WG_MAIN, ModD3_GenAreaVBO, sub, NULL, MLS_LOADED, 0); COM_AddWork(WG_MAIN, Mod_ModelLoaded, sub, NULL, MLS_LOADED, 0); } else if (!strcmp(token, "shadowModel")) { int numverts, v; int numindexes, i; data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "{")) return false; data = COM_ParseOut(data, token, sizeof(token)); //name data = COM_ParseOut(data, token, sizeof(token)); numverts = atoi(token); data = COM_ParseOut(data, token, sizeof(token)); //nocaps data = COM_ParseOut(data, token, sizeof(token)); //nofrontcaps data = COM_ParseOut(data, token, sizeof(token)); numindexes = atoi(token); data = COM_ParseOut(data, token, sizeof(token)); //planebits for (v = 0; v < numverts; v++) { data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "(")) return false; data = COM_ParseOut(data, token, sizeof(token)); //x data = COM_ParseOut(data, token, sizeof(token)); //y data = COM_ParseOut(data, token, sizeof(token)); //z data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, ")")) return false; } for (i = 0; i < numindexes; i++) { data = COM_ParseOut(data, token, sizeof(token)); } data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "}")) return false; } else if (!strcmp(token, "nodes")) { int numnodes, n; data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "{")) return false; data = COM_ParseOut(data, token, sizeof(token)); numnodes = atoi(token); model->nodes = ZG_Malloc(&model->memgroup, sizeof(*model->nodes)*numnodes); model->planes = ZG_Malloc(&model->memgroup, sizeof(*model->planes)*numnodes); for (n = 0; n < numnodes; n++) { data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "(")) return false; model->nodes[n].plane = &model->planes[n]; data = COM_ParseOut(data, token, sizeof(token)); model->planes[n].normal[0] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); model->planes[n].normal[1] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); model->planes[n].normal[2] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); model->planes[n].dist = atof(token); data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, ")")) return false; data = COM_ParseOut(data, token, sizeof(token)); model->nodes[n].childnum[0] = atoi(token); data = COM_ParseOut(data, token, sizeof(token)); model->nodes[n].childnum[1] = atoi(token); } data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "}")) return false; Mod_SetParent(model->nodes, NULL); } else if (!strcmp(token, "interAreaPortals")) { //int numareas; int pno, v; portal_t *p; data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "{")) return false; data = COM_ParseOut(data, token, sizeof(token)); model->numclusters = atoi(token); data = COM_ParseOut(data, token, sizeof(token)); model->numportals = atoi(token); model->portal = p = ZG_Malloc(&model->memgroup, sizeof(*p) * model->numportals); for (pno = 0; pno < model->numportals; pno++, p++) { data = COM_ParseOut(data, token, sizeof(token)); p->numpoints = atoi(token); data = COM_ParseOut(data, token, sizeof(token)); p->area[0] = atoi(token); data = COM_ParseOut(data, token, sizeof(token)); p->area[1] = atoi(token); p->points = ZG_Malloc(&model->memgroup, sizeof(*p->points) * p->numpoints); ClearBounds(p->min, p->max); for (v = 0; v < p->numpoints; v++) { data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "(")) return false; data = COM_ParseOut(data, token, sizeof(token)); p->points[v][0] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); p->points[v][1] = atof(token); data = COM_ParseOut(data, token, sizeof(token)); p->points[v][2] = atof(token); p->points[v][3] = 1; AddPointToBounds(p->points[v], p->min, p->max); data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, ")")) return false; } } data = COM_ParseOut(data, token, sizeof(token)); if (strcmp(token, "}")) return false; } else { Con_Printf("unexpected token %s\n", token); return false; } } return true; } qboolean R_CullBox (vec3_t mins, vec3_t maxs); static int walkno; /*fixme: convert each portal to a 2d box, because its much much simpler than true poly clipping*/ /*fixme: use occlusion tests, with temporal coherance (draw the portal as black or something if we think its invisible)*/ static void D3_WalkPortal(model_t *mod, int start, vec_t bounds[4], unsigned char *vis) { int i; portal_t *p; int side; vec_t newbounds[4]; vis[start>>3] |= 1<<(start&7); for (i = 0; i < mod->numportals; i++) { p = mod->portal+i; if (p->walkno == walkno) continue; if (p->area[0] == start) side = 0; else if (p->area[1] == start) side = 1; else continue; if (R_CullBox(p->min, p->max)) { continue; } p->walkno = walkno; D3_WalkPortal(mod, p->area[!side], newbounds, vis); } } unsigned char *D3_CalcVis(model_t *mod, vec3_t org) { int start; static qbyte visbuf[256]; qbyte *usevis; vec_t newbounds[4]; int area; entity_t ent; start = D3_ClusterForPoint(mod, org); /*figure out which area we're in*/ if (start < 0) { /*outside the world, just make it all visible, and take the fps hit*/ memset(visbuf, 255, 4); usevis = visbuf; } else if (r_novis.value) usevis = visbuf; else { memset(visbuf, 0, 4); /*make a bounds the size of the view*/ newbounds[0] = -1; newbounds[1] = 1; newbounds[2] = -1; newbounds[3] = 1; walkno++; D3_WalkPortal(mod, start, newbounds, visbuf); // Con_Printf("%x %x %x %x\n", vis[0], vis[1], vis[2], vis[3]); usevis = visbuf; } //now generate the various entities for that region. memset(&ent, 0, sizeof(ent)); for (area = 0; area < 256*8; area++) { if (usevis[area>>3] & (1u<<(area&7))) { ent.model = Mod_FindName(va("*_area%i", area)); ent.scale = 1; AngleVectors(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]); VectorInverse(ent.axis[1]); ent.shaderRGBAf[0] = 1; ent.shaderRGBAf[1] = 1; ent.shaderRGBAf[2] = 1; ent.shaderRGBAf[3] = 1; V_AddEntity(&ent); } } return usevis; } #endif //edict system as opposed to q2 game dll system. static void D3_FindTouchedLeafs (struct model_s *model, struct pvscache_s *ent, vec3_t cullmins, vec3_t cullmaxs) { } static qbyte *D3_ClusterPVS (struct model_s *model, int num, pvsbuffer_t *buffer, pvsmerge_t merge) { memset(buffer->buffer, 0xff, buffer->buffersize); return buffer->buffer; } static int D3_ClusterForPoint (struct model_s *model, vec3_t point) { float p; int c; mnode_t *node; node = model->nodes; while(1) { p = DotProduct(point, node->plane->normal) + node->plane->dist; c = node->childnum[p<0]; if (c <= 0) return -1-c; node = model->nodes + c; } return 0; } static unsigned int D3_FatPVS (struct model_s *model, vec3_t org, pvsbuffer_t *pvsbuffer, qboolean merge) { return 0; } static void D3_StainNode (struct mnode_s *node, float *parms) { } static void D3_LightPointValues (struct model_s *model, vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir) { /*basically require rtlighting for any light*/ VectorClear(res_diffuse); VectorClear(res_ambient); VectorClear(res_dir); res_dir[2] = 1; } static qboolean D3_EdictInFatPVS (struct model_s *model, struct pvscache_s *edict, qbyte *pvsbuffer) { int i; for (i = 0; i < edict->num_leafs; i++) if (pvsbuffer[edict->leafnums[i]>>3] & (1u<<(edict->leafnums[i]&7))) return true; return false; } typedef struct cm_surface_s { vec3_t mins, maxs; vec4_t plane; int numedges; vec4_t *edge; // shader_t *shader; struct cm_surface_s *next; } cm_surface_t; typedef struct cm_brush_s { vec3_t mins, maxs; int numplanes; vec4_t *plane; unsigned int contents; struct cm_brush_s *next; } cm_brush_t; typedef struct cm_node_s { int axis; /*0=x,1=y,2=z*/ float dist; vec3_t mins, maxs; struct cm_node_s *parent; struct cm_node_s *child[2]; cm_brush_t *brushlist; cm_surface_t *surfacelist; } cm_node_t; static struct { float truefraction; qboolean ispoint; vec3_t start; vec3_t end; vec3_t absmins, absmaxs; vec3_t szmins, szmaxs; vec3_t extents; cm_surface_t *surf; } traceinfo; #define DIST_EPSILON (0.03125) static void D3_TraceToLeaf (cm_node_t *leaf) { float diststart; float distend; float frac; vec3_t impactpoint; qboolean back; int i, j; float pdist, expand; vec3_t ofs; cm_surface_t *surf; for (surf = leaf->surfacelist; surf; surf = surf->next) { /*lots of maths in this function, we should check the surf's bbox*/ if (surf->mins[0] > traceinfo.absmaxs[0] || traceinfo.absmins[0] > surf->maxs[0] || surf->mins[1] > traceinfo.absmaxs[1] || traceinfo.absmins[1] > surf->maxs[1] || surf->mins[2] > traceinfo.absmaxs[2] || traceinfo.absmins[2] > surf->maxs[2]) continue; if (!traceinfo.ispoint) { // general box case // push the plane out apropriately for mins/maxs // FIXME: use signbits into 8 way lookup for each mins/maxs for (i=0 ; i<3 ; i++) { if (surf->plane[i] < 0) ofs[i] = traceinfo.szmaxs[i]; else ofs[i] = traceinfo.szmins[i]; } expand = DotProduct (ofs, surf->plane); // pdist = surf->plane[3] - expand; } else { // special point case // pdist = surf->plane[3]; expand = 0; } diststart = DotProduct(traceinfo.start, surf->plane); /*started behind?*/ back = diststart < surf->plane[3]; if (diststart <= surf->plane[3]-expand) { /*the trace started behind our expanded front plane*/ /*don't stop just because the point is closer than the extended plane*/ /*epsilon here please*/ if (diststart <= surf->plane[3]) continue; distend = DotProduct(traceinfo.end, surf->plane); if (distend < diststart) frac = 0; /*don't let us go further into the wall*/ else continue; } else { distend = DotProduct(traceinfo.end, surf->plane); /*ended on the other side*/ if (back) { if (distend+expand > -surf->plane[3]) continue; } else { if (distend+expand > surf->plane[3]) continue; } if (diststart == distend) frac = 0; else frac = (diststart - (surf->plane[3]-expand)) / (diststart-distend); } /*give up if we already found a closer plane*/ if (frac >= traceinfo.truefraction) continue; /*okay, this is where it hits this plane*/ impactpoint[0] = traceinfo.start[0] + frac*(traceinfo.end[0] - traceinfo.start[0]); impactpoint[1] = traceinfo.start[1] + frac*(traceinfo.end[1] - traceinfo.start[1]); impactpoint[2] = traceinfo.start[2] + frac*(traceinfo.end[2] - traceinfo.start[2]); /*if the impact was not on the surface*/ for (i = 0; i < surf->numedges; i++) { if (!traceinfo.ispoint) { // general box case // push the plane out apropriately for mins/maxs // FIXME: use signbits into 8 way lookup for each mins/maxs for (j=0 ; j<3 ; j++) { if (surf->edge[i][j] < 0) ofs[j] = traceinfo.szmaxs[j]; else ofs[j] = traceinfo.szmins[j]; } pdist = DotProduct (ofs, surf->edge[i]); pdist = surf->edge[i][3] - pdist; } else { // special point case pdist = surf->edge[i][3]; } if (DotProduct(impactpoint, surf->edge[i]) > pdist) { break; } } /*if we were inside all edges, we hit the surface*/ if (i == surf->numedges) { traceinfo.truefraction = frac; traceinfo.surf = surf; /*we can't early out. there are multiple surfs in each leaf, and they could overlap. earlying out will result in errors if we hit a further one before the nearer*/ } } } /*returns the most distant node which contains the entire box*/ static cm_node_t *D3_ChildNodeForBox(cm_node_t *node, vec3_t mins, vec3_t maxs) { float t1, t2; for(;;) { t1 = mins[node->axis] - node->dist; t2 = maxs[node->axis] - node->dist; //if its completely to one side, walk down that side if (t1 > maxs[node->axis] && t2 > maxs[node->axis]) { //if this is a leaf, we can't insert in a child anyway. if (!node->child[0]) break; node = node->child[0]; continue; } if (t1 < mins[node->axis] && t2 < mins[node->axis]) { //if this is a leaf, we can't insert in a child anyway. if (!node->child[1]) break; node = node->child[1]; continue; } //the box crosses this node break; } return node; } static void D3_InsertClipSurface(cm_node_t *node, cm_surface_t *surf) { node = D3_ChildNodeForBox(node, surf->mins, surf->maxs); surf->next = node->surfacelist; node->surfacelist = surf; } static void D3_InsertClipBrush(cm_node_t *node, cm_brush_t *brush) { node = D3_ChildNodeForBox(node, brush->mins, brush->maxs); brush->next = node->brushlist; node->brushlist = brush; } static void D3_RecursiveSurfCheck (cm_node_t *node, float p1f, float p2f, vec3_t p1, vec3_t p2) { float t1, t2, offset; float frac, frac2; float idist; int i; vec3_t mid; int side; float midf; if (traceinfo.truefraction <= p1f) return; // already hit something nearer /*err, no child here*/ if (!node) return; D3_TraceToLeaf (node); // // find the point distances to the seperating plane // and the offset for the size of the box // t1 = p1[node->axis] - node->dist; t2 = p2[node->axis] - node->dist; offset = traceinfo.extents[node->axis]; #if 0 D3_RecursiveHullCheck (node->childnum[0], p1f, p2f, p1, p2); D3_RecursiveHullCheck (node->childnum[1], p1f, p2f, p1, p2); return; #endif // see which sides we need to consider if (t1 >= offset && t2 >= offset) { D3_RecursiveSurfCheck (node->child[0], p1f, p2f, p1, p2); return; } if (t1 < -offset && t2 < -offset) { D3_RecursiveSurfCheck ( node->child[1], p1f, p2f, p1, p2); return; } // put the crosspoint DIST_EPSILON pixels on the near side if (t1 < t2) { idist = 1.0/(t1-t2); side = 1; frac2 = (t1 + offset + DIST_EPSILON)*idist; frac = (t1 - offset + DIST_EPSILON)*idist; } else if (t1 > t2) { idist = 1.0/(t1-t2); side = 0; frac2 = (t1 - offset - DIST_EPSILON)*idist; frac = (t1 + offset + DIST_EPSILON)*idist; } else { side = 0; frac = 1; frac2 = 0; } // move up to the node if (frac < 0) frac = 0; if (frac > 1) frac = 1; midf = p1f + (p2f - p1f)*frac; for (i=0 ; i<3 ; i++) mid[i] = p1[i] + frac*(p2[i] - p1[i]); D3_RecursiveSurfCheck (node->child[side], p1f, midf, p1, mid); // go past the node if (frac2 < 0) frac2 = 0; if (frac2 > 1) frac2 = 1; midf = p1f + (p2f - p1f)*frac2; for (i=0 ; i<3 ; i++) mid[i] = p1[i] + frac2*(p2[i] - p1[i]); D3_RecursiveSurfCheck (node->child[side^1], midf, p2f, mid, p2); } static qboolean D3_Trace (struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t p1, vec3_t p2, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int hitcontentsmask, struct trace_s *trace) { int i; float e1,e2; traceinfo.truefraction = 1; VectorCopy(p1, traceinfo.start); VectorCopy(p2, traceinfo.end); for (i = 0; i < 3; i++) { e1 = fabs(mins[i]); e2 = fabs(maxs[i]); traceinfo.extents[i] = ((e1>e2)?e1:e2); traceinfo.szmins[i] = mins[i]; traceinfo.szmaxs[i] = maxs[i]; traceinfo.absmins[i] = ((p1[i]p2[i])?p1[i]:p2[i]) + maxs[i]; } traceinfo.ispoint = !traceinfo.extents[0] && !traceinfo.extents[1] && !traceinfo.extents[2]; traceinfo.surf = NULL; D3_RecursiveSurfCheck(model->cnodes, 0, 1, p1, p2); memset(trace, 0, sizeof(*trace)); if (!traceinfo.surf) { trace->fraction = 1; VectorCopy(p2, trace->endpos); } else { float frac; /*we now know which surface it hit. recalc the impact point, but with an epsilon this time, so we can never get too close to the surface*/ VectorCopy(traceinfo.surf->plane, trace->plane.normal); if (!traceinfo.ispoint) { // general box case vec3_t ofs; // push the plane out apropriately for mins/maxs // FIXME: use signbits into 8 way lookup for each mins/maxs for (i=0 ; i<3 ; i++) { if (traceinfo.surf->plane[i] < 0) ofs[i] = traceinfo.szmaxs[i]; else ofs[i] = traceinfo.szmins[i]; } e1 = DotProduct (ofs, traceinfo.surf->plane); trace->plane.dist = traceinfo.surf->plane[3] - e1; } else { // special point case trace->plane.dist = traceinfo.surf->plane[3]; } frac = traceinfo.truefraction; /* diststart = DotProduct(traceinfo.start, trace->plane.normal); distend = DotProduct(traceinfo.end, trace->plane.normal); if (diststart == distend) frac = 0; else { frac = (diststart - trace->plane.dist) / (diststart-distend); if (frac < 0) frac = 0; else if (frac > 1) frac = 1; }*/ /*okay, this is where it hits this plane*/ trace->endpos[0] = traceinfo.start[0] + frac*(traceinfo.end[0] - traceinfo.start[0]); trace->endpos[1] = traceinfo.start[1] + frac*(traceinfo.end[1] - traceinfo.start[1]); trace->endpos[2] = traceinfo.start[2] + frac*(traceinfo.end[2] - traceinfo.start[2]); trace->fraction = frac; } trace->ent = NULL; return false; } static unsigned int D3_PointContents (struct model_s *model, vec3_t axis[3], vec3_t p) { cm_node_t *node = model->cnodes; cm_brush_t *brush; float t1; unsigned int contents = 0; int i; if (axis) { vec3_t tmp; VectorCopy(p, tmp); p[0] = DotProduct(tmp, axis[0]); p[1] = DotProduct(tmp, axis[1]); p[2] = DotProduct(tmp, axis[2]); } while(node) { for (brush = node->brushlist; brush; brush = brush->next) { if (brush->mins[0] > p[0] || p[0] > brush->maxs[0] || brush->mins[1] > p[1] || p[1] > brush->maxs[1] || brush->mins[2] > p[2] || p[2] > brush->maxs[2]) continue; for (i = 0; i < brush->numplanes; i++) { if (DotProduct(p, brush->plane[i]) > brush->plane[i][3]) break; } if (i == brush->numplanes) contents |= brush->contents; } t1 = p[node->axis] - node->dist; // see which side we need to go down if (t1 >= 0) { node = node->child[0]; } else { node = node->child[1]; } } return contents; } #define ensurenewtoken(t) buf = COM_ParseOut(buf, token, sizeof(token)); if (strcmp(token, t)) break; static int D3_ParseContents(char *str) { char *e, *n; unsigned int contents = 0; while(str) { e = strchr(str, ','); if (e) { *e = 0; n = e+1; } else n = NULL; if (!strcmp(str, "solid") || !strcmp(str, "opaque")) contents |= FTECONTENTS_SOLID; else if (!strcmp(str, "playerclip")) contents |= FTECONTENTS_PLAYERCLIP; else if (!strcmp(str, "monsterclip")) contents |= FTECONTENTS_PLAYERCLIP; else Con_Printf("Unknown contents type \"%s\"\n", str); str = n; } return contents; } typedef struct { int v[2]; int fl[2]; } d3edge_t; qboolean QDECL D3_LoadMap_CollisionMap(model_t *mod, void *buf, size_t bufsize) { int pedges[64]; cm_surface_t *surf; cm_brush_t *brush; vec3_t *verts; d3edge_t *edges; int i, j; int filever = 0; int numverts, numedges, numpedges; model_t *cmod; char token[256]; buf = COM_ParseOut(buf, token, sizeof(token)); if (strcmp(token, "CM")) return false; buf = COM_ParseOut(buf, token, sizeof(token)); filever = atof(token); if (filever != 1 && filever != 3) return false; buf = COM_ParseOut(buf, token, sizeof(token)); /*some number, discard*/ while(buf) { buf = COM_ParseOut(buf, token, sizeof(token)); if (!strcmp(token, "collisionModel")) { buf = COM_ParseOut(buf, token, sizeof(token)); if (!strcmp(token, "worldMap")) cmod = mod; else cmod = Mod_FindName(token); if (filever == 3) { buf = COM_ParseOut(buf, token, sizeof(token)); /*don't know*/ } ClearBounds(cmod->mins, cmod->maxs); ensurenewtoken("{"); ensurenewtoken("vertices"); ensurenewtoken("{"); buf = COM_ParseOut(buf, token, sizeof(token)); numverts = atoi(token); verts = malloc(numverts * sizeof(*verts)); for (i = 0; i < numverts; i++) { ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); verts[i][0] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); verts[i][1] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); verts[i][2] = atof(token); ensurenewtoken(")"); } ensurenewtoken("}"); ensurenewtoken("edges"); ensurenewtoken("{"); buf = COM_ParseOut(buf, token, sizeof(token)); numedges = atoi(token); edges = malloc(numedges * sizeof(*edges)); for (i = 0; i < numedges; i++) { ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); edges[i].v[0] = atoi(token); buf = COM_ParseOut(buf, token, sizeof(token)); edges[i].v[1] = atoi(token); ensurenewtoken(")"); buf = COM_ParseOut(buf, token, sizeof(token)); edges[i].fl[0] = atoi(token); buf = COM_ParseOut(buf, token, sizeof(token)); edges[i].fl[1] = atoi(token); } ensurenewtoken("}"); ensurenewtoken("nodes"); ensurenewtoken("{"); cmod->cnodes = ZG_Malloc(&mod->memgroup, sizeof(cm_node_t)); for (;;) { buf = COM_ParseOut(buf, token, sizeof(token)); if (strcmp(token, "(")) break; buf = COM_ParseOut(buf, token, sizeof(token)); buf = COM_ParseOut(buf, token, sizeof(token)); //axis, dist ensurenewtoken(")"); } if (strcmp(token, "}")) break; ensurenewtoken("polygons"); if (filever == 1) { buf = COM_ParseOut(buf, token, sizeof(token)); /*'polygonMemory', which is unusable for us*/ } else { buf = COM_ParseOut(buf, token, sizeof(token)); /*numPolygons*/ buf = COM_ParseOut(buf, token, sizeof(token)); /*numPolygonEdges*/ } ensurenewtoken("{"); for (;;) { buf = COM_ParseOut(buf, token, sizeof(token)); if (!strcmp(token, "}")) break; numpedges = atoi(token); surf = ZG_Malloc(&mod->memgroup, sizeof(*surf) + sizeof(vec4_t)*numpedges); surf->numedges = numpedges; surf->edge = (vec4_t*)(surf+1); ensurenewtoken("("); for (j = 0; j < numpedges; j++) { buf = COM_ParseOut(buf, token, sizeof(token)); pedges[j] = atoi(token); } ensurenewtoken(")"); ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); surf->plane[0] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); surf->plane[1] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); surf->plane[2] = atof(token); ensurenewtoken(")"); buf = COM_ParseOut(buf, token, sizeof(token)); surf->plane[3] = atof(token); ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); surf->mins[0] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); surf->mins[1] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); surf->mins[2] = atof(token); ensurenewtoken(")"); ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); surf->maxs[0] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); surf->maxs[1] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); surf->maxs[2] = atof(token); ensurenewtoken(")"); buf = COM_ParseOut(buf, token, sizeof(token)); #ifndef SERVERONLY // surf->shader = R_RegisterShader_Vertex(token); // R_BuildDefaultTexnums_Doom3(NULL, surf->shader); #endif if (filever == 3) { ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); buf = COM_ParseOut(buf, token, sizeof(token)); ensurenewtoken(")"); ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); buf = COM_ParseOut(buf, token, sizeof(token)); ensurenewtoken(")"); ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); buf = COM_ParseOut(buf, token, sizeof(token)); ensurenewtoken(")"); buf = COM_ParseOut(buf, token, sizeof(token)); } for (j = 0; j < numpedges; j++) { float *v1, *v2; vec3_t dir; if (pedges[j] < 0) { v2 = verts[edges[-pedges[j]].v[0]]; v1 = verts[edges[-pedges[j]].v[1]]; } else { v1 = verts[edges[pedges[j]].v[0]]; v2 = verts[edges[pedges[j]].v[1]]; } VectorSubtract(v1, v2, dir); VectorNormalize(dir); CrossProduct(surf->plane, dir, surf->edge[j]); surf->edge[j][3] = DotProduct(v1, surf->edge[j]); surf->edge[j][3] += DIST_EPSILON; } D3_InsertClipSurface(cmod->cnodes, surf); AddPointToBounds(surf->mins, cmod->mins, cmod->maxs); AddPointToBounds(surf->maxs, cmod->mins, cmod->maxs); } free(verts); free(edges); ensurenewtoken("brushes"); if (filever == 1) { buf = COM_ParseOut(buf, token, sizeof(token)); /*'brushMemory', which is unusable for us*/ } else { buf = COM_ParseOut(buf, token, sizeof(token)); /*numBrushes */ buf = COM_ParseOut(buf, token, sizeof(token)); /*numBrushPlanes*/ } ensurenewtoken("{"); for (;;) { buf = COM_ParseOut(buf, token, sizeof(token)); if (!strcmp(token, "}")) break; j = atoi(token); brush = ZG_Malloc(&mod->memgroup, j*sizeof(vec4_t) + sizeof(*brush)); brush->numplanes = j; brush->plane = (vec4_t*)(brush+1); ensurenewtoken("{"); for (i = 0; i < brush->numplanes; i++) { ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); brush->plane[i][0] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); brush->plane[i][1] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); brush->plane[i][2] = atof(token); ensurenewtoken(")"); buf = COM_ParseOut(buf, token, sizeof(token)); brush->plane[i][3] = atof(token); } ensurenewtoken("}"); ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); brush->mins[0] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); brush->mins[1] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); brush->mins[2] = atof(token); ensurenewtoken(")"); ensurenewtoken("("); buf = COM_ParseOut(buf, token, sizeof(token)); brush->maxs[0] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); brush->maxs[1] = atof(token); buf = COM_ParseOut(buf, token, sizeof(token)); brush->maxs[2] = atof(token); ensurenewtoken(")"); buf = COM_ParseOut(buf, token, sizeof(token)); brush->contents = D3_ParseContents(token); if (filever == 3) buf = COM_ParseOut(buf, token, sizeof(token)); D3_InsertClipBrush(cmod->cnodes, brush); AddPointToBounds(brush->mins, cmod->mins, cmod->maxs); AddPointToBounds(brush->maxs, cmod->mins, cmod->maxs); } } else break; } /*load up the .map so we can get some entities (anyone going to bother making a qc mod compatible with this?)*/ COM_StripExtension(mod->name, token, sizeof(token)); Mod_SetEntitiesString(mod, FS_LoadMallocFile(va("%s.map", token), NULL), true); mod->funcs.FindTouchedLeafs = D3_FindTouchedLeafs; mod->funcs.NativeTrace = D3_Trace; mod->funcs.PointContents = D3_PointContents; mod->funcs.FatPVS = D3_FatPVS; mod->funcs.ClusterForPoint = D3_ClusterForPoint; mod->funcs.StainNode = D3_StainNode; mod->funcs.LightPointValues = D3_LightPointValues; mod->funcs.EdictInFatPVS = D3_EdictInFatPVS; mod->funcs.ClusterPVS = D3_ClusterPVS; mod->fromgame = fg_doom3; /*that's the physics sorted*/ #ifndef SERVERONLY if (!isDedicated) { COM_StripExtension(mod->name, token, sizeof(token)); buf = FS_LoadMallocFile(va("%s.proc", token), NULL); Mod_LoadMap_Proc(mod, buf); BZ_Free(buf); } #endif return true; } #endif