]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - model_brush.c
got rid of Cshader system as it really wasn't helping anything, now all surfaces...
[xonotic/darkplaces.git] / model_brush.c
index e9bcca803ae2fa36822fb1512c88a100fa549c83..e3919a63ff8f587002a65158c286b88780956f10 100644 (file)
@@ -34,7 +34,14 @@ cvar_t r_novis = {0, "r_novis", "0"};
 cvar_t r_miplightmaps = {CVAR_SAVE, "r_miplightmaps", "0"};
 cvar_t r_lightmaprgba = {0, "r_lightmaprgba", "1"};
 cvar_t r_nosurftextures = {0, "r_nosurftextures", "0"};
-cvar_t mod_q3bsp_curves_subdivide_level = {0, "mod_q3bsp_curves_subdivide_level", "2"};
+cvar_t r_subdivisions_tolerance = {0, "r_subdivisions_tolerance", "4"};
+cvar_t r_subdivisions_minlevel = {0, "r_subdivisions_minlevel", "0"};
+cvar_t r_subdivisions_maxlevel = {0, "r_subdivisions_maxlevel", "10"};
+cvar_t r_subdivisions_maxvertices = {0, "r_subdivisions_maxvertices", "65536"};
+cvar_t r_subdivisions_collision_tolerance = {0, "r_subdivisions_collision_tolerance", "15"};
+cvar_t r_subdivisions_collision_minlevel = {0, "r_subdivisions_collision_minlevel", "0"};
+cvar_t r_subdivisions_collision_maxlevel = {0, "r_subdivisions_collision_maxlevel", "10"};
+cvar_t r_subdivisions_collision_maxvertices = {0, "r_subdivisions_collision_maxvertices", "4225"};
 cvar_t mod_q3bsp_curves_collisions = {0, "mod_q3bsp_curves_collisions", "1"};
 cvar_t mod_q3bsp_optimizedtraceline = {0, "mod_q3bsp_optimizedtraceline", "1"};
 cvar_t mod_q3bsp_debugtracebrush = {0, "mod_q3bsp_debugtracebrush", "0"};
@@ -48,7 +55,14 @@ void Mod_BrushInit(void)
        Cvar_RegisterVariable(&r_miplightmaps);
        Cvar_RegisterVariable(&r_lightmaprgba);
        Cvar_RegisterVariable(&r_nosurftextures);
-       Cvar_RegisterVariable(&mod_q3bsp_curves_subdivide_level);
+       Cvar_RegisterVariable(&r_subdivisions_tolerance);
+       Cvar_RegisterVariable(&r_subdivisions_minlevel);
+       Cvar_RegisterVariable(&r_subdivisions_maxlevel);
+       Cvar_RegisterVariable(&r_subdivisions_maxvertices);
+       Cvar_RegisterVariable(&r_subdivisions_collision_tolerance);
+       Cvar_RegisterVariable(&r_subdivisions_collision_minlevel);
+       Cvar_RegisterVariable(&r_subdivisions_collision_maxlevel);
+       Cvar_RegisterVariable(&r_subdivisions_collision_maxvertices);
        Cvar_RegisterVariable(&mod_q3bsp_curves_collisions);
        Cvar_RegisterVariable(&mod_q3bsp_optimizedtraceline);
        Cvar_RegisterVariable(&mod_q3bsp_debugtracebrush);
@@ -427,7 +441,7 @@ loc0:
                        if (t->trace->allsolid)
                                t->trace->startsolid = true;
 #if COLLISIONPARANOID >= 3
-                       Con_Printf("S");
+                       Con_Print("S");
 #endif
                        return HULLCHECKSTATE_SOLID;
                }
@@ -435,7 +449,7 @@ loc0:
                {
                        t->trace->allsolid = false;
 #if COLLISIONPARANOID >= 3
-                       Con_Printf("E");
+                       Con_Print("E");
 #endif
                        return HULLCHECKSTATE_EMPTY;
                }
@@ -461,7 +475,7 @@ loc0:
                if (t2 < 0)
                {
 #if COLLISIONPARANOID >= 3
-                       Con_Printf("<");
+                       Con_Print("<");
 #endif
                        num = node->children[1];
                        goto loc0;
@@ -473,7 +487,7 @@ loc0:
                if (t2 >= 0)
                {
 #if COLLISIONPARANOID >= 3
-                       Con_Printf(">");
+                       Con_Print(">");
 #endif
                        num = node->children[0];
                        goto loc0;
@@ -484,7 +498,7 @@ loc0:
        // the line intersects, find intersection point
        // LordHavoc: this uses the original trace for maximum accuracy
 #if COLLISIONPARANOID >= 3
-       Con_Printf("M");
+       Con_Print("M");
 #endif
        if (plane->type < 3)
        {
@@ -535,7 +549,7 @@ loc0:
        t->trace->fraction = bound(0, midf, 1);
 
 #if COLLISIONPARANOID >= 3
-       Con_Printf("D");
+       Con_Print("D");
 #endif
        return HULLCHECKSTATE_DONE;
 }
@@ -609,7 +623,7 @@ static void Mod_Q1BSP_TraceBox(struct model_s *model, int frame, trace_t *trace,
 #if COLLISIONPARANOID >= 2
        Con_Printf("t(%f %f %f,%f %f %f,%i %f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[2], rhc.hull - model->brushq1.hulls, rhc.hull->clip_mins[0], rhc.hull->clip_mins[1], rhc.hull->clip_mins[2]);
        Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end);
-       Con_Printf("\n");
+       Con_Print("\n");
 #else
        if (DotProduct(rhc.dist, rhc.dist))
                Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end);
@@ -925,13 +939,10 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                tx->width = 16;
                tx->height = 16;
                tx->skin.base = r_notexture;
-               tx->shader = &Cshader_wall_lightmap;
-               tx->flags = SURF_SOLIDCLIP;
                if (i == loadmodel->brushq1.numtextures - 1)
-               {
-                       tx->flags |= SURF_DRAWTURB | SURF_LIGHTBOTHSIDES;
-                       tx->shader = &Cshader_water;
-               }
+                       tx->flags = SURF_DRAWTURB | SURF_LIGHTBOTHSIDES;
+               else
+                       tx->flags = SURF_LIGHTMAP | SURF_SOLIDCLIP;
                tx->currentframe = tx;
        }
 
@@ -969,7 +980,7 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                }
 
                if ((mtwidth & 15) || (mtheight & 15))
-                       Con_Printf("warning: texture \"%s\" in \"%s\" is not 16 aligned", dmiptex->name, loadmodel->name);
+                       Con_Printf("warning: texture \"%s\" in \"%s\" is not 16 aligned\n", dmiptex->name, loadmodel->name);
 
                // LordHavoc: force all names to lowercase
                for (j = 0;name[j];j++)
@@ -1063,8 +1074,7 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                if (tx->name[0] == '*')
                {
                        // turb does not block movement
-                       tx->flags &= ~SURF_SOLIDCLIP;
-                       tx->flags |= SURF_DRAWTURB | SURF_LIGHTBOTHSIDES;
+                       tx->flags = SURF_DRAWTURB | SURF_LIGHTBOTHSIDES;
                        // LordHavoc: some turbulent textures should be fullbright and solid
                        if (!strncmp(tx->name,"*lava",5)
                         || !strncmp(tx->name,"*teleport",9)
@@ -1072,20 +1082,11 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                                tx->flags |= SURF_DRAWFULLBRIGHT | SURF_DRAWNOALPHA;
                        else
                                tx->flags |= SURF_WATERALPHA;
-                       tx->shader = &Cshader_water;
                }
                else if (tx->name[0] == 's' && tx->name[1] == 'k' && tx->name[2] == 'y')
-               {
-                       tx->flags |= SURF_DRAWSKY;
-                       tx->shader = &Cshader_sky;
-               }
+                       tx->flags = SURF_DRAWSKY | SURF_SOLIDCLIP;
                else
-               {
-                       tx->flags |= SURF_LIGHTMAP;
-                       if (!tx->skin.fog)
-                               tx->flags |= SURF_SHADOWCAST | SURF_SHADOWLIGHT;
-                       tx->shader = &Cshader_wall_lightmap;
-               }
+                       tx->flags = SURF_LIGHTMAP | SURF_SOLIDCLIP;
 
                // start out with no animation
                tx->currentframe = tx;
@@ -1233,9 +1234,9 @@ static void Mod_Q1BSP_LoadLighting(lump_t *l)
                        else
                        {
                                if (fs_filesize == 8)
-                                       Con_Printf("Empty .lit file, ignoring\n");
+                                       Con_Print("Empty .lit file, ignoring\n");
                                else
-                                       Con_Printf("Corrupt .lit file (old version?), ignoring\n");
+                                       Con_Print("Corrupt .lit file (old version?), ignoring\n");
                                Mem_Free(data);
                        }
                }
@@ -1512,7 +1513,7 @@ static void Mod_Q1BSP_LoadTexinfo(lump_t *l)
                {
                        // if texture chosen is NULL or the shader needs a lightmap,
                        // force to notexture water shader
-                       if (out->texture == NULL || out->texture->shader->flags & SHADERFLAGS_NEEDLIGHTMAP)
+                       if (out->texture == NULL || out->texture->flags & SURF_LIGHTMAP)
                                out->texture = loadmodel->brushq1.textures + (loadmodel->brushq1.numtextures - 1);
                }
                else
@@ -1628,7 +1629,7 @@ static void SubdividePolygon(int numverts, float *verts)
        {
                if (subdivpolytriangles >= MAX_SUBDIVPOLYTRIANGLES)
                {
-                       Con_Printf("SubdividePolygon: ran out of triangles in buffer, please increase your r_subdivide_size\n");
+                       Con_Print("SubdividePolygon: ran out of triangles in buffer, please increase your r_subdivide_size\n");
                        return;
                }
 
@@ -1818,7 +1819,7 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                else // LordHavoc: white lighting (bsp version 29)
                        surf->samples = loadmodel->brushq1.lightdata + (i * 3);
 
-               if (surf->texinfo->texture->shader == &Cshader_wall_lightmap)
+               if (surf->texinfo->texture->flags & SURF_LIGHTMAP)
                {
                        if ((surf->extents[0] >> 4) + 1 > (256) || (surf->extents[1] >> 4) + 1 > (256))
                                Host_Error("Bad surface extents");
@@ -1876,7 +1877,7 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                Mod_BuildTriangleNeighbors(mesh->data_neighbor3i, mesh->data_element3i, mesh->num_triangles);
                Mod_BuildTextureVectorsAndNormals(mesh->num_vertices, mesh->num_triangles, mesh->data_vertex3f, mesh->data_texcoordtexture2f, mesh->data_element3i, mesh->data_svector3f, mesh->data_tvector3f, mesh->data_normal3f);
 
-               if (surf->texinfo->texture->shader == &Cshader_wall_lightmap)
+               if (surf->texinfo->texture->flags & SURF_LIGHTMAP)
                {
                        int i, iu, iv, smax, tmax;
                        float u, v, ubase, vbase, uscale, vscale;
@@ -1884,7 +1885,6 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                        smax = surf->extents[0] >> 4;
                        tmax = surf->extents[1] >> 4;
 
-                       surf->flags |= SURF_LIGHTMAP;
                        if (r_miplightmaps.integer)
                        {
                                surf->lightmaptexturestride = smax+1;
@@ -2015,7 +2015,7 @@ static void Mod_Q1BSP_LoadLeafs(lump_t *l)
                if (p >= 0 && out->clusterindex >= 0)
                {
                        if (p >= loadmodel->brushq1.num_compressedpvs)
-                               Con_Printf("Mod_Q1BSP_LoadLeafs: invalid visofs\n");
+                               Con_Print("Mod_Q1BSP_LoadLeafs: invalid visofs\n");
                        else
                                Mod_Q1BSP_DecompressVis(loadmodel->brushq1.data_compressedpvs + p, loadmodel->brushq1.data_compressedpvs + loadmodel->brushq1.num_compressedpvs, loadmodel->brush.data_pvsclusters + out->clusterindex * loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.data_pvsclusters + (out->clusterindex + 1) * loadmodel->brush.num_pvsclusterbytes);
                }
@@ -2508,7 +2508,7 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
                nodeportalwinding = Winding_Clip(nodeportalwinding, clipplane.normal[0], clipplane.normal[1], clipplane.normal[2], clipplane.dist, true);
                if (!nodeportalwinding)
                {
-                       Con_Printf("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal was clipped away\n");
+                       Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal was clipped away\n");
                        break;
                }
        }
@@ -2724,6 +2724,21 @@ static void Mod_Q1BSP_BuildPVSTextureChains(model_t *model)
        }
 }
 
+//Returns PVS data for a given point
+//(note: can return NULL)
+static qbyte *Mod_Q1BSP_GetPVS(model_t *model, const vec3_t p)
+{
+       mnode_t *node;
+       Mod_CheckLoaded(model);
+       node = model->brushq1.nodes;
+       while (node->plane)
+               node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist];
+       if (((mleaf_t *)node)->clusterindex >= 0)
+               return model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes;
+       else
+               return NULL;
+}
+
 static void Mod_Q1BSP_FatPVS_RecursiveBSPNode(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbytes, mnode_t *node)
 {
        while (node->plane)
@@ -2756,7 +2771,7 @@ static int Mod_Q1BSP_FatPVS(model_t *model, const vec3_t org, vec_t radius, qbyt
 {
        int bytes = ((model->brushq1.num_leafs - 1) + 7) >> 3;
        bytes = min(bytes, pvsbufferlength);
-       if (r_novis.integer)
+       if (r_novis.integer || !Mod_Q1BSP_GetPVS(model, org))
        {
                memset(pvsbuffer, 0xFF, bytes);
                return bytes;
@@ -2766,21 +2781,6 @@ static int Mod_Q1BSP_FatPVS(model_t *model, const vec3_t org, vec_t radius, qbyt
        return bytes;
 }
 
-//Returns PVS data for a given point
-//(note: can return NULL)
-static qbyte *Mod_Q1BSP_GetPVS(model_t *model, const vec3_t p)
-{
-       mnode_t *node;
-       Mod_CheckLoaded(model);
-       node = model->brushq1.nodes;
-       while (node->plane)
-               node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist];
-       if (((mleaf_t *)node)->clusterindex >= 0)
-               return model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes;
-       else
-               return NULL;
-}
-
 static void Mod_Q1BSP_RoundUpToHullSize(model_t *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs)
 {
        vec3_t size;
@@ -2814,10 +2814,59 @@ static void Mod_Q1BSP_RoundUpToHullSize(model_t *cmodel, const vec3_t inmins, co
        VectorAdd(inmins, hull->clip_size, outmaxs);
 }
 
+/*
+void Mod_Q1BSP_RecursiveGetVisible(mnode_t *node, model_t *model, const vec3_t point, const vec3_t mins, const vec3_t maxs, int maxleafs, mleaf_t *leaflist, int *numleafs, int maxsurfaces, msurface_t *surfacelist, int *numsurfaces, const qbyte *pvs)
+{
+       mleaf_t *leaf;
+       for (;;)
+       {
+               if (!BoxesOverlap(node->mins, node->maxs, mins, maxs))
+                       return;
+               if (!node->plane)
+                       break;
+               Mod_Q1BSP_RecursiveGetVisible(node->children[0], model, point, mins, maxs, maxleafs, leaflist, numleafs, maxsurfaces, surfacelist, numsurfaces, pvs);
+               node = node->children[1];
+       }
+       leaf = (mleaf_t *)node;
+       if ((pvs == NULL || CHECKPVSBIT(pvs, leaf->clusterindex)))
+       {
+               int marksurfacenum;
+               msurface_t *surf;
+               if (maxleafs && *numleafs < maxleafs)
+                       leaflist[(*numleafs)++] = leaf;
+               if (maxsurfaces)
+               {
+                       for (marksurfacenum = 0;marksurfacenum < leaf->nummarksurfaces;marksurfacenum++)
+                       {
+                               surf = model->brushq1.surfaces + leaf->firstmarksurface[marksurfacenum];
+                               if (surf->shadowmark != shadowmarkcount)
+                               {
+                                       surf->shadowmark = shadowmarkcount;
+                                       if (BoxesOverlap(mins, maxs, surf->poly_mins, surf->poly_maxs) && ((surf->flags & SURF_PLANEBACK) ? PlaneDiff(point, surf->plane) < 0 : PlaneDiff(point, surf->plane) > 0) && *numsurfaces < maxsurfaces)
+                                               surfacelist[(*numsurfaces)++] = surf;
+                               }
+                       }
+               }
+       }
+}
+
+void Mod_Q1BSP_GetVisible(model_t *model, const vec3_t point, const vec3_t mins, const vec3_t maxs, int maxleafs, mleaf_t *leaflist, int *numleafs, int maxsurfaces, msurface_t *surfacelist, int *numsurfaces)
+{
+       // FIXME: support portals
+       if (maxsurfaces)
+               *numsurfaces = 0;
+       if (maxleafs)
+               *numleafs = 0;
+       pvs = ent->model->brush.GetPVS(ent->model, relativelightorigin);
+       Mod_Q1BSP_RecursiveGetVisible(ent->model->brushq1.nodes + ent->model->brushq1.firstclipnode, model, point, mins, maxs, maxleafs, leaflist, numleafs, maxsurfaces, surfacelist, numsurfaces);
+}
+*/
+
 extern void R_Model_Brush_DrawSky(entity_render_t *ent);
 extern void R_Model_Brush_Draw(entity_render_t *ent);
-extern void R_Model_Brush_DrawShadowVolume(entity_render_t *ent, vec3_t relativelightorigin, float lightradius);
-extern void R_Model_Brush_DrawLight(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativeeyeorigin, float lightradius, float *lightcolor, const matrix4x4_t *matrix_modeltolight, const matrix4x4_t *matrix_modeltoattenuationxyz, const matrix4x4_t *matrix_modeltoattenuationz, rtexture_t *lightcubemap);
+extern void R_Model_Brush_GetLightInfo(entity_render_t *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outclusterlist, qbyte *outclusterpvs, int *outnumclusterspointer, int *outsurfacelist, qbyte *outsurfacepvs, int *outnumsurfacespointer);
+extern void R_Model_Brush_DrawShadowVolume(entity_render_t *ent, vec3_t relativelightorigin, float lightradius, int numsurfaces, const int *surfacelist);
+extern void R_Model_Brush_DrawLight(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativeeyeorigin, float lightradius, float *lightcolor, const matrix4x4_t *matrix_modeltolight, const matrix4x4_t *matrix_modeltoattenuationxyz, const matrix4x4_t *matrix_modeltoattenuationz, rtexture_t *lightcubemap, int numsurfaces, const int *surfacelist);
 void Mod_Q1BSP_Load(model_t *mod, void *buffer)
 {
        int i, j, k;
@@ -2934,9 +2983,16 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
                mod->brushq1.firstmodelsurface = bm->firstface;
                mod->brushq1.nummodelsurfaces = bm->numfaces;
 
+               // make the model surface list (used by shadowing/lighting)
+               mod->numsurfaces = mod->brushq1.nummodelsurfaces;
+               mod->surfacelist = Mem_Alloc(originalloadmodel->mempool, mod->numsurfaces * sizeof(*mod->surfacelist));
+               for (j = 0;j < mod->numsurfaces;j++)
+                       mod->surfacelist[j] = mod->brushq1.firstmodelsurface + j;
+
                // this gets altered below if sky is used
                mod->DrawSky = NULL;
                mod->Draw = R_Model_Brush_Draw;
+               mod->GetLightInfo = R_Model_Brush_GetLightInfo;
                mod->DrawShadowVolume = R_Model_Brush_DrawShadowVolume;
                mod->DrawLight = R_Model_Brush_DrawLight;
                if (i != 0)
@@ -2962,7 +3018,7 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
                        for (j = 0, surf = &mod->brushq1.surfaces[mod->brushq1.firstmodelsurface];j < mod->brushq1.nummodelsurfaces;j++, surf++)
                        {
                                // we only need to have a drawsky function if it is used(usually only on world model)
-                               if (surf->texinfo->texture->shader == &Cshader_sky)
+                               if (surf->texinfo->texture->flags & SURF_DRAWSKY)
                                        mod->DrawSky = R_Model_Brush_DrawSky;
                                // LordHavoc: submodels always clip, even if water
                                if (mod->brush.numsubmodels - 1)
@@ -3511,6 +3567,7 @@ static void Mod_Q3BSP_LoadTextures(lump_t *l)
        const char *text;
        int flags;
        char shadername[Q3PATHLENGTH];
+       char sky[Q3PATHLENGTH];
 
        in = (void *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
@@ -3542,8 +3599,9 @@ static void Mod_Q3BSP_LoadTextures(lump_t *l)
                                text = f;
                                while (COM_ParseToken(&text, false))
                                {
-                                       snprintf(shadername, sizeof(shadername), "%s", com_token);
+                                       strncpy(shadername, com_token, sizeof(shadername));
                                        flags = 0;
+                                       sky[0] = 0;
                                        if (COM_ParseToken(&text, false) && !strcasecmp(com_token, "{"))
                                        {
                                                while (COM_ParseToken(&text, false))
@@ -3628,6 +3686,22 @@ static void Mod_Q3BSP_LoadTextures(lump_t *l)
                                                                        goto parseerror;
                                                                }
                                                        }
+                                                       else if (!strcasecmp(com_token, "sky"))
+                                                       {
+                                                               if (COM_ParseToken(&text, true) && strcasecmp(com_token, "\n"))
+                                                                       if (strlen(com_token) < sizeof(sky))
+                                                                               strcpy(sky, com_token);
+                                                       }
+                                                       else if (!strcasecmp(com_token, "skyparms"))
+                                                       {
+                                                               if (COM_ParseToken(&text, true) && strcasecmp(com_token, "\n"))
+                                                               {
+                                                                       if (strlen(com_token) < sizeof(sky) && !atoi(com_token) && strcasecmp(com_token, "-"))
+                                                                               strcpy(sky, com_token);
+                                                                       if (COM_ParseToken(&text, true) && strcasecmp(com_token, "\n"))
+                                                                               COM_ParseToken(&text, true);
+                                                               }
+                                                       }
                                                        else
                                                        {
                                                                // look for linebreak or }
@@ -3640,8 +3714,14 @@ static void Mod_Q3BSP_LoadTextures(lump_t *l)
                                                // add shader to list (shadername and flags)
                                                // actually here we just poke into the texture settings
                                                for (j = 0, out = loadmodel->brushq3.data_textures;j < loadmodel->brushq3.num_textures;j++, out++)
+                                               {
                                                        if (!strcasecmp(out->name, shadername))
+                                                       {
                                                                out->surfaceparms = flags;
+                                                               if ((flags & Q3SURFACEPARM_SKY) && sky[0])
+                                                                       strcpy(loadmodel->brush.skybox, sky);
+                                                       }
+                                               }
                                        }
                                        else
                                        {
@@ -4031,13 +4111,6 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                                out->type = 0; // error
                                continue;
                        }
-                       // convert patch to Q3FACETYPE_MESH
-                       xlevel = mod_q3bsp_curves_subdivide_level.integer;
-                       ylevel = mod_q3bsp_curves_subdivide_level.integer;
-                       finalwidth = ((patchsize[0] - 1) << xlevel) + 1;
-                       finalheight = ((patchsize[1] - 1) << ylevel) + 1;
-                       finalvertices = finalwidth * finalheight;
-                       finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2;
                        originalvertex3f = loadmodel->brushq3.data_vertex3f + out->firstvertex * 3;
                        //originalsvector3f = loadmodel->brushq3.data_svector3f + out->firstvertex * 3;
                        //originaltvector3f = loadmodel->brushq3.data_tvector3f + out->firstvertex * 3;
@@ -4058,6 +4131,27 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        //originalelement3i = out->data_element3i;
                        //originalneighbor3i = out->data_neighbor3i;
                        */
+                       // convert patch to Q3FACETYPE_MESH
+                       xlevel = QuadraticSplinePatchSubdivisionLevelOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value, 10);
+                       ylevel = QuadraticSplinePatchSubdivisionLevelOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value, 10);
+                       // bound to user settings
+                       xlevel = bound(r_subdivisions_minlevel.integer, xlevel, r_subdivisions_maxlevel.integer);
+                       ylevel = bound(r_subdivisions_minlevel.integer, ylevel, r_subdivisions_maxlevel.integer);
+                       // bound to sanity settings
+                       xlevel = bound(0, xlevel, 10);
+                       ylevel = bound(0, ylevel, 10);
+                       // bound to user limit on vertices
+                       while ((xlevel > 0 || ylevel > 0) && (((patchsize[0] - 1) << xlevel) + 1) * (((patchsize[1] - 1) << ylevel) + 1) > min(r_subdivisions_maxvertices.integer, 262144))
+                       {
+                               if (xlevel > ylevel)
+                                       xlevel--;
+                               else
+                                       ylevel--;
+                       }
+                       finalwidth = ((patchsize[0] - 1) << xlevel) + 1;
+                       finalheight = ((patchsize[1] - 1) << ylevel) + 1;
+                       finalvertices = finalwidth * finalheight;
+                       finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2;
                        out->data_vertex3f = Mem_Alloc(loadmodel->mempool, sizeof(float[20]) * finalvertices);
                        out->data_svector3f = out->data_vertex3f + finalvertices * 3;
                        out->data_tvector3f = out->data_svector3f + finalvertices * 3;
@@ -4105,7 +4199,59 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                                        Con_Printf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles\n", patchsize[0], patchsize[1], out->num_vertices, out->num_triangles);
                        }
                        // q3map does not put in collision brushes for curves... ugh
+                       // build the lower quality collision geometry
                        out->collisions = true;
+                       xlevel = QuadraticSplinePatchSubdivisionLevelOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value, 10);
+                       ylevel = QuadraticSplinePatchSubdivisionLevelOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value, 10);
+                       // bound to user settings
+                       xlevel = bound(r_subdivisions_collision_minlevel.integer, xlevel, r_subdivisions_collision_maxlevel.integer);
+                       ylevel = bound(r_subdivisions_collision_minlevel.integer, ylevel, r_subdivisions_collision_maxlevel.integer);
+                       // bound to sanity settings
+                       xlevel = bound(0, xlevel, 10);
+                       ylevel = bound(0, ylevel, 10);
+                       // bound to user limit on vertices
+                       while ((xlevel > 0 || ylevel > 0) && (((patchsize[0] - 1) << xlevel) + 1) * (((patchsize[1] - 1) << ylevel) + 1) > min(r_subdivisions_collision_maxvertices.integer, 262144))
+                       {
+                               if (xlevel > ylevel)
+                                       xlevel--;
+                               else
+                                       ylevel--;
+                       }
+                       finalwidth = ((patchsize[0] - 1) << xlevel) + 1;
+                       finalheight = ((patchsize[1] - 1) << ylevel) + 1;
+                       finalvertices = finalwidth * finalheight;
+                       finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2;
+                       out->data_collisionvertex3f = Mem_Alloc(loadmodel->mempool, sizeof(float[3]) * finalvertices);
+                       out->data_collisionelement3i = Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * finaltriangles);
+                       out->num_collisionvertices = finalvertices;
+                       out->num_collisiontriangles = finaltriangles;
+                       QuadraticSplinePatchSubdivideFloatBuffer(patchsize[0], patchsize[1], xlevel, ylevel, 3, originalvertex3f, out->data_collisionvertex3f);
+                       // generate elements
+                       e = out->data_collisionelement3i;
+                       for (y = 0;y < finalheight - 1;y++)
+                       {
+                               row0 = (y + 0) * finalwidth;
+                               row1 = (y + 1) * finalwidth;
+                               for (x = 0;x < finalwidth - 1;x++)
+                               {
+                                       *e++ = row0;
+                                       *e++ = row1;
+                                       *e++ = row0 + 1;
+                                       *e++ = row1;
+                                       *e++ = row1 + 1;
+                                       *e++ = row0 + 1;
+                                       row0++;
+                                       row1++;
+                               }
+                       }
+                       out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(out->num_collisiontriangles, out->data_collisionelement3i, out->data_collisionelement3i, out->data_collisionvertex3f);
+                       if (developer.integer)
+                       {
+                               if (out->num_collisiontriangles < finaltriangles)
+                                       Con_Printf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided for collisions to %i vertices / %i triangles, %i degenerate triangles removed (leaving %i)\n", patchsize[0], patchsize[1], out->num_collisionvertices, finaltriangles, finaltriangles - out->num_collisiontriangles, out->num_collisiontriangles);
+                               else
+                                       Con_Printf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided for collisions to %i vertices / %i triangles\n", patchsize[0], patchsize[1], out->num_collisionvertices, out->num_collisiontriangles);
+                       }
                        break;
                case Q3FACETYPE_FLARE:
                        Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): Q3FACETYPE_FLARE not supported (yet)\n", i, out->texture->name);
@@ -4127,7 +4273,7 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                                if (out->data_element3i[j] < 0 || out->data_element3i[j] >= out->num_vertices)
                                        out->data_element3i[j] = 0;
                        }
-                       Con_Printf("\n");
+                       Con_Print("\n");
                }
                // for shadow volumes
                Mod_BuildTriangleNeighbors(out->data_neighbor3i, out->data_element3i, out->num_triangles);
@@ -4694,7 +4840,7 @@ static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, q3mnode_t *node
                        if (face->collisions && face->collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, face->mins, face->maxs))
                        {
                                face->collisionmarkframe = markframe;
-                               Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, face->num_triangles, face->data_element3i, face->data_vertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
+                               Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, face->num_collisiontriangles, face->data_collisionelement3i, face->data_collisionvertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
                                if (startfrac > trace->realfraction)
                                        return;
                        }
@@ -5074,7 +5220,7 @@ static void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, q3mnode_t *nod
                        if (face->collisions && face->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, face->mins, face->maxs))
                        {
                                face->markframe = markframe;
-                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, face->num_triangles, face->data_element3i, face->data_vertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
+                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, face->num_collisiontriangles, face->data_collisionelement3i, face->data_collisionvertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
                        }
                }
        }
@@ -5128,7 +5274,7 @@ static void Mod_Q3BSP_TraceBox(model_t *model, int frame, trace_t *trace, const
                                        {
                                                face = model->brushq3.data_thismodel->firstface + i;
                                                if (face->collisions)
-                                                       Collision_TraceLineTriangleMeshFloat(trace, boxstartmins, boxendmins, face->num_triangles, face->data_element3i, face->data_vertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
+                                                       Collision_TraceLineTriangleMeshFloat(trace, boxstartmins, boxendmins, face->num_collisiontriangles, face->data_collisionelement3i, face->data_collisionvertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
                                        }
                                }
                        }
@@ -5152,7 +5298,7 @@ static void Mod_Q3BSP_TraceBox(model_t *model, int frame, trace_t *trace, const
                                {
                                        face = model->brushq3.data_thismodel->firstface + i;
                                        if (face->collisions)
-                                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, face->num_triangles, face->data_element3i, face->data_vertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
+                                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, face->num_collisiontriangles, face->data_collisionelement3i, face->data_collisionvertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
                                }
                        }
                }
@@ -5262,7 +5408,7 @@ static int Mod_Q3BSP_FatPVS(model_t *model, const vec3_t org, vec_t radius, qbyt
 {
        int bytes = model->brush.num_pvsclusterbytes;
        bytes = min(bytes, pvsbufferlength);
-       if (r_novis.integer || !loadmodel->brush.num_pvsclusters)
+       if (r_novis.integer || !loadmodel->brush.num_pvsclusters || !Mod_Q3BSP_GetPVS(model, org))
        {
                memset(pvsbuffer, 0xFF, bytes);
                return bytes;
@@ -5301,10 +5447,74 @@ static int Mod_Q3BSP_NativeContentsFromSuperContents(model_t *model, int superco
        return nativecontents;
 }
 
+/*
+void Mod_Q3BSP_RecursiveGetVisible(q3mnode_t *node, model_t *model, const vec3_t point, const vec3_t mins, const vec3_t maxs, int maxleafs, q3mleaf_t *leaflist, int *numleafs, int maxsurfaces, q3msurface_t *surfacelist, int *numsurfaces, const qbyte *pvs)
+{
+       mleaf_t *leaf;
+       for (;;)
+       {
+               if (!BoxesOverlap(node->mins, node->maxs, mins, maxs))
+                       return;
+               if (!node->plane)
+                       break;
+               Mod_Q3BSP_RecursiveGetVisible(node->children[0], model, point, mins, maxs, maxleafs, leaflist, numleafs, maxsurfaces, surfacelist, numsurfaces, pvs);
+               node = node->children[1];
+       }
+       leaf = (mleaf_t *)node;
+       if ((pvs == NULL || CHECKPVSBIT(pvs, leaf->clusterindex)))
+       {
+               int marksurfacenum;
+               q3mface_t *surf;
+               if (maxleafs && *numleafs < maxleafs)
+                       leaflist[(*numleaf)++] = leaf;
+               if (maxsurfaces)
+               {
+                       for (marksurfacenum = 0;marksurfacenum < leaf->nummarksurfaces;marksurfacenum++)
+                       {
+                               face = leaf->firstleafface[marksurfacenum];
+                               if (face->shadowmark != shadowmarkcount)
+                               {
+                                       face->shadowmark = shadowmarkcount;
+                                       if (BoxesOverlap(mins, maxs, face->mins, face->maxs) && *numsurfaces < maxsurfaces)
+                                               surfacelist[(*numsurfaces)++] = face;
+                               }
+                       }
+               }
+       }
+}
+
+void Mod_Q3BSP_GetVisible(model_t *model, const vec3_t point, const vec3_t mins, const vec3_t maxs, int maxleafs, q3mleaf_t *leaflist, int *numleafs, int maxsurfaces, q3msurface_t *surfacelist, int *numsurfaces)
+{
+       // FIXME: support portals
+       if (maxsurfaces)
+               *numsurfaces = 0;
+       if (maxleafs)
+               *numleafs = 0;
+       if (model->submodel)
+       {
+               if (maxsurfaces)
+               {
+                       for (marksurfacenum = 0;marksurfacenum < leaf->nummarksurfaces;marksurfacenum++)
+                       {
+                               face = ent->model->brushq3.surfaces + leaf->firstmarksurface[marksurfacenum];
+                               if (BoxesOverlap(mins, maxs, face->mins, face->maxs) && *numsurfaces < maxsurfaces)
+                                       surfacelist[(*numsurfaces)++] = face;
+                       }
+               }
+       }
+       else
+       {
+               pvs = ent->model->brush.GetPVS(ent->model, relativelightorigin);
+               Mod_Q3BSP_RecursiveGetVisible(ent->model->brushq3.data_nodes, model, point, mins, maxs, maxleafs, leaflist, numleafs, maxsurfaces, surfacelist, numsurfaces, pvs);
+       }
+}
+*/
+
 extern void R_Q3BSP_DrawSky(struct entity_render_s *ent);
 extern void R_Q3BSP_Draw(struct entity_render_s *ent);
-extern void R_Q3BSP_DrawShadowVolume(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius);
-extern void R_Q3BSP_DrawLight(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativeeyeorigin, float lightradius, float *lightcolor, const matrix4x4_t *matrix_modeltolight, const matrix4x4_t *matrix_modeltoattenuationxyz, const matrix4x4_t *matrix_modeltoattenuationz, rtexture_t *lightcubemap);
+extern void R_Q3BSP_GetLightInfo(entity_render_t *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outclusterlist, qbyte *outclusterpvs, int *outnumclusterspointer, int *outsurfacelist, qbyte *outsurfacepvs, int *outnumsurfacespointer);
+extern void R_Q3BSP_DrawShadowVolume(entity_render_t *ent, vec3_t relativelightorigin, float lightradius, int numsurfaces, const int *surfacelist);
+extern void R_Q3BSP_DrawLight(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativeeyeorigin, float lightradius, float *lightcolor, const matrix4x4_t *matrix_modeltolight, const matrix4x4_t *matrix_modeltoattenuationxyz, const matrix4x4_t *matrix_modeltoattenuationz, rtexture_t *lightcubemap, int numsurfaces, const int *surfacelist);
 void Mod_Q3BSP_Load(model_t *mod, void *buffer)
 {
        int i, j, numshadowmeshtriangles;
@@ -5339,6 +5549,7 @@ void Mod_Q3BSP_Load(model_t *mod, void *buffer)
        mod->brush.FindNonSolidLocation = Mod_Q3BSP_FindNonSolidLocation;
        //mod->DrawSky = R_Q3BSP_DrawSky;
        mod->Draw = R_Q3BSP_Draw;
+       mod->GetLightInfo = R_Q3BSP_GetLightInfo;
        mod->DrawShadowVolume = R_Q3BSP_DrawShadowVolume;
        mod->DrawLight = R_Q3BSP_DrawLight;
 
@@ -5399,10 +5610,21 @@ void Mod_Q3BSP_Load(model_t *mod, void *buffer)
                        // textures and memory belong to the main model
                        mod->texturepool = NULL;
                        mod->mempool = NULL;
+                       mod->brush.GetPVS = NULL;
+                       mod->brush.FatPVS = NULL;
+                       mod->brush.BoxTouchingPVS = NULL;
+                       mod->brush.LightPoint = NULL;
+                       mod->brush.FindNonSolidLocation = Mod_Q3BSP_FindNonSolidLocation;
                }
                mod->brushq3.data_thismodel = loadmodel->brushq3.data_models + i;
                mod->brushq3.submodel = i;
 
+               // make the model surface list (used by shadowing/lighting)
+               mod->numsurfaces = mod->brushq3.data_thismodel->numfaces;
+               mod->surfacelist = Mem_Alloc(loadmodel->mempool, mod->numsurfaces * sizeof(*mod->surfacelist));
+               for (j = 0;j < mod->numsurfaces;j++)
+                       mod->surfacelist[j] = (mod->brushq3.data_thismodel->firstface - mod->brushq3.data_faces) + j;
+
                VectorCopy(mod->brushq3.data_thismodel->mins, mod->normalmins);
                VectorCopy(mod->brushq3.data_thismodel->maxs, mod->normalmaxs);
                corner[0] = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0]));