X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=r_shadow.c;h=2aed02fce0ed513eac26c33da2883abd1422d8bc;hb=f9620140162ea276fcacb1524b31fec5248aadb5;hp=7c16c17ec3afa41b25c45ad696b093da2a4a7cd0;hpb=e13cc9a617e40e93d360926f5586367fbebae5d2;p=xonotic%2Fdarkplaces.git diff --git a/r_shadow.c b/r_shadow.c index 7c16c17e..2aed02fc 100644 --- a/r_shadow.c +++ b/r_shadow.c @@ -180,6 +180,7 @@ r_shadow_rendermode_t r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_N qboolean r_shadow_usingshadowmaprect; qboolean r_shadow_usingshadowmap2d; qboolean r_shadow_usingshadowmapcube; +qboolean r_shadow_usingshadowmaportho; int r_shadow_shadowmapside; float r_shadow_shadowmap_texturescale[2]; float r_shadow_shadowmap_parameters[4]; @@ -200,6 +201,7 @@ qboolean r_shadow_shadowmapvsdct; qboolean r_shadow_shadowmapsampler; int r_shadow_shadowmappcf; int r_shadow_shadowmapborder; +matrix4x4_t r_shadow_shadowmapmatrix; int r_shadow_lightscissor[4]; qboolean r_shadow_usingdeferredprepass; @@ -347,6 +349,7 @@ static memexpandablearray_t r_shadow_worldlightsarray; dlight_t *r_shadow_selectedlight; dlight_t r_shadow_bufferlight; vec3_t r_editlights_cursorlocation; +qboolean r_editlights_lockcursor; extern int con_vislines; @@ -440,6 +443,12 @@ void R_Shadow_SetShadowMode(void) r_shadow_shadowmode = R_SHADOW_SHADOWMODE_SHADOWMAP2D; break; } + // Cg has very little choice in depth texture sampling + if (vid.cgcontext) + { + r_shadow_shadowmapsampler = false; + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_SHADOWMAP2D; + } } break; case RENDERPATH_GL13: @@ -449,6 +458,19 @@ void R_Shadow_SetShadowMode(void) } } +qboolean R_Shadow_ShadowMappingEnabled(void) +{ + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + case R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE: + case R_SHADOW_SHADOWMODE_SHADOWMAPCUBESIDE: + return true; + default: + return false; + } +} + void R_Shadow_FreeShadowMaps(void) { int i; @@ -644,7 +666,7 @@ void r_shadow_newmap(void) if (r_editlights_sprcubemaplight) R_SkinFrame_MarkUsed(r_editlights_sprcubemaplight); if (r_editlights_sprcubemapnoshadowlight) R_SkinFrame_MarkUsed(r_editlights_sprcubemapnoshadowlight); if (r_editlights_sprselection) R_SkinFrame_MarkUsed(r_editlights_sprselection); - if (cl.worldmodel && strncmp(cl.worldmodel->name, r_shadow_mapname, sizeof(r_shadow_mapname))) + if (strncmp(cl.worldname, r_shadow_mapname, sizeof(r_shadow_mapname))) R_Shadow_EditLights_Reload_f(); } @@ -739,7 +761,7 @@ void R_Shadow_Init(void) r_shadow_buffer_surfacesides = NULL; r_shadow_buffer_shadowtrispvs = NULL; r_shadow_buffer_lighttrispvs = NULL; - R_RegisterModule("R_Shadow", r_shadow_start, r_shadow_shutdown, r_shadow_newmap); + R_RegisterModule("R_Shadow", r_shadow_start, r_shadow_shutdown, r_shadow_newmap, NULL, NULL); } matrix4x4_t matrix_attenuationxyz = @@ -1492,19 +1514,48 @@ int R_Shadow_CullFrustumSides(rtlight_t *rtlight, float size, float border) } // this next test usually clips off more sides than the former, but occasionally clips fewer/different ones, so do both and combine results // check if frustum corners/origin cross plane sides +#if 1 + // infinite version, assumes frustum corners merely give direction and extend to infinite distance + Matrix4x4_Transform(&rtlight->matrix_worldtolight, r_refdef.view.origin, p); + dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn); + masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn); + masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn); + masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + for (i = 0;i < 4;i++) + { + Matrix4x4_Transform(&rtlight->matrix_worldtolight, r_refdef.view.frustumcorner[i], n); + VectorSubtract(n, p, n); + dp = n[0] + n[1], dn = n[0] - n[1], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[0] |= dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2); + if(an > 0) masks[1] |= dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2); + dp = n[1] + n[2], dn = n[1] - n[2], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[2] |= dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4); + if(an > 0) masks[3] |= dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4); + dp = n[2] + n[0], dn = n[2] - n[0], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[4] |= dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0); + if(an > 0) masks[5] |= dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0); + } +#else + // finite version, assumes corners are a finite distance from origin dependent on far plane for (i = 0;i < 5;i++) { Matrix4x4_Transform(&rtlight->matrix_worldtolight, !i ? r_refdef.view.origin : r_refdef.view.frustumcorner[i-1], p); - dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn), + dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn); masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); - dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn), + dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn); masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); - dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn), + dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn); masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); } +#endif return sides & masks[0] & masks[1] & masks[2] & masks[3] & masks[4] & masks[5]; } @@ -1703,12 +1754,12 @@ static void R_Shadow_MakeTextures(void) // 1D gradient texture for (x = 0;x < ATTEN1DSIZE;x++) data[x] = R_Shadow_MakeTextures_SamplePoint((x + 0.5f) * (1.0f / ATTEN1DSIZE) * (1.0f / 0.9375), 0, 0); - r_shadow_attenuationgradienttexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation1d", ATTEN1DSIZE, 1, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, NULL); + r_shadow_attenuationgradienttexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation1d", ATTEN1DSIZE, 1, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); // 2D circle texture for (y = 0;y < ATTEN2DSIZE;y++) for (x = 0;x < ATTEN2DSIZE;x++) data[y*ATTEN2DSIZE+x] = R_Shadow_MakeTextures_SamplePoint(((x + 0.5f) * (2.0f / ATTEN2DSIZE) - 1.0f) * (1.0f / 0.9375), ((y + 0.5f) * (2.0f / ATTEN2DSIZE) - 1.0f) * (1.0f / 0.9375), 0); - r_shadow_attenuation2dtexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation2d", ATTEN2DSIZE, ATTEN2DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, NULL); + r_shadow_attenuation2dtexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation2d", ATTEN2DSIZE, ATTEN2DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); // 3D sphere texture if (r_shadow_texture3d.integer && vid.support.ext_texture_3d) { @@ -1716,7 +1767,7 @@ static void R_Shadow_MakeTextures(void) for (y = 0;y < ATTEN3DSIZE;y++) for (x = 0;x < ATTEN3DSIZE;x++) data[(z*ATTEN3DSIZE+y)*ATTEN3DSIZE+x] = R_Shadow_MakeTextures_SamplePoint(((x + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375), ((y + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375), ((z + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375)); - r_shadow_attenuation3dtexture = R_LoadTexture3D(r_shadow_texturepool, "attenuation3d", ATTEN3DSIZE, ATTEN3DSIZE, ATTEN3DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, NULL); + r_shadow_attenuation3dtexture = R_LoadTexture3D(r_shadow_texturepool, "attenuation3d", ATTEN3DSIZE, ATTEN3DSIZE, ATTEN3DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); } else r_shadow_attenuation3dtexture = NULL; @@ -1961,6 +2012,7 @@ void R_Shadow_RenderMode_Reset(void) r_shadow_usingshadowmaprect = false; r_shadow_usingshadowmapcube = false; r_shadow_usingshadowmap2d = false; + r_shadow_usingshadowmaportho = false; CHECKGLERROR } @@ -2038,7 +2090,7 @@ static void R_Shadow_MakeVSDCT(void) 0, 0, 0x33, 0xFF, // +Z: <0, 0>, <0.5, 2.5> 0, 0, 0x99, 0xFF, // -Z: <0, 0>, <1.5, 2.5> }; - r_shadow_shadowmapvsdcttexture = R_LoadTextureCubeMap(r_shadow_texturepool, "shadowmapvsdct", 1, data, TEXTYPE_RGBA, TEXF_FORCENEAREST | TEXF_CLAMP | TEXF_ALPHA, NULL); + r_shadow_shadowmapvsdcttexture = R_LoadTextureCubeMap(r_shadow_texturepool, "shadowmapvsdct", 1, data, TEXTYPE_RGBA, TEXF_FORCENEAREST | TEXF_CLAMP | TEXF_ALPHA, -1, NULL); } static void R_Shadow_MakeShadowMap(int side, int size) @@ -2086,20 +2138,21 @@ void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size) { float nearclip, farclip, bias; r_viewport_t viewport; + int flipped; GLuint fbo = 0; CHECKGLERROR nearclip = r_shadow_shadowmapping_nearclip.value / rsurface.rtlight->radius; farclip = 1.0f; bias = r_shadow_shadowmapping_bias.value * nearclip * (1024.0f / size);// * rsurface.rtlight->radius; - r_shadow_shadowmap_parameters[2] = 0.5f + 0.5f * (farclip + nearclip) / (farclip - nearclip); - r_shadow_shadowmap_parameters[3] = -nearclip * farclip / (farclip - nearclip) - 0.5f * bias; + r_shadow_shadowmap_parameters[1] = -nearclip * farclip / (farclip - nearclip) - 0.5f * bias; + r_shadow_shadowmap_parameters[3] = 0.5f + 0.5f * (farclip + nearclip) / (farclip - nearclip); r_shadow_shadowmapside = side; r_shadow_shadowmapsize = size; switch (r_shadow_shadowmode) { case R_SHADOW_SHADOWMODE_SHADOWMAP2D: r_shadow_shadowmap_parameters[0] = 0.5f * (size - r_shadow_shadowmapborder); - r_shadow_shadowmap_parameters[1] = r_shadow_shadowmapvsdct ? 2.5f*size : size; + r_shadow_shadowmap_parameters[2] = r_shadow_shadowmapvsdct ? 2.5f*size : size; R_Viewport_InitRectSideView(&viewport, &rsurface.rtlight->matrix_lighttoworld, side, size, r_shadow_shadowmapborder, nearclip, farclip, NULL); if (r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAP2D) goto init_done; @@ -2116,7 +2169,7 @@ void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size) break; case R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE: r_shadow_shadowmap_parameters[0] = 0.5f * (size - r_shadow_shadowmapborder); - r_shadow_shadowmap_parameters[1] = r_shadow_shadowmapvsdct ? 2.5f*size : size; + r_shadow_shadowmap_parameters[2] = r_shadow_shadowmapvsdct ? 2.5f*size : size; R_Viewport_InitRectSideView(&viewport, &rsurface.rtlight->matrix_lighttoworld, side, size, r_shadow_shadowmapborder, nearclip, farclip, NULL); if (r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAPRECTANGLE) goto init_done; @@ -2133,7 +2186,7 @@ void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size) break; case R_SHADOW_SHADOWMODE_SHADOWMAPCUBESIDE: r_shadow_shadowmap_parameters[0] = 1.0f; - r_shadow_shadowmap_parameters[1] = 1.0f; + r_shadow_shadowmap_parameters[2] = 1.0f; R_Viewport_InitCubeSideView(&viewport, &rsurface.rtlight->matrix_lighttoworld, side, size, nearclip, farclip, NULL); if (r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAPCUBESIDE) goto init_done; @@ -2170,9 +2223,11 @@ void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size) init_done: R_SetViewport(&viewport); - if(r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAP2D || r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAPRECTANGLE) + switch (r_shadow_rendermode) { - int flipped = (side & 1) ^ (side >> 2); + case R_SHADOW_RENDERMODE_SHADOWMAP2D: + case R_SHADOW_RENDERMODE_SHADOWMAPRECTANGLE: + flipped = (side & 1) ^ (side >> 2); r_refdef.view.cullface_front = flipped ? r_shadow_cullface_back : r_shadow_cullface_front; r_refdef.view.cullface_back = flipped ? r_shadow_cullface_front : r_shadow_cullface_back; GL_CullFace(r_refdef.view.cullface_back); @@ -2187,13 +2242,15 @@ init_done: GL_Clear(GL_DEPTH_BUFFER_BIT); } GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); - } - else if(r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAPCUBESIDE) - { + break; + case R_SHADOW_RENDERMODE_SHADOWMAPCUBESIDE: qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + side, R_GetTexture(r_shadow_shadowmapcubetexture[r_shadow_shadowmaplod]), 0);CHECKGLERROR GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); if (clear) GL_Clear(GL_DEPTH_BUFFER_BIT); + break; + default: + break; } CHECKGLERROR } @@ -2230,12 +2287,20 @@ void R_Shadow_RenderMode_Lighting(qboolean stenciltest, qboolean transparent, qb } if (shadowmapping) { - if (r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAP2D) + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: r_shadow_usingshadowmap2d = true; - else if (r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE) + break; + case R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE: r_shadow_usingshadowmaprect = true; - else if (r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAPCUBESIDE) + break; + case R_SHADOW_SHADOWMODE_SHADOWMAPCUBESIDE: r_shadow_usingshadowmapcube = true; + break; + default: + break; + } } R_Mesh_ColorPointer(rsurface.array_color4f, 0, 0); CHECKGLERROR @@ -2285,12 +2350,20 @@ void R_Shadow_RenderMode_DrawDeferredLight(qboolean stenciltest, qboolean shadow qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, r_shadow_prepasslightingfbo);CHECKGLERROR if (shadowmapping) { - if (r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAP2D) + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: r_shadow_usingshadowmap2d = true; - else if (r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE) + break; + case R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE: r_shadow_usingshadowmaprect = true; - else if (r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAPCUBESIDE) + break; + case R_SHADOW_SHADOWMODE_SHADOWMAPCUBESIDE: r_shadow_usingshadowmapcube = true; + break; + default: + break; + } } // render the lighting @@ -3345,12 +3418,17 @@ void R_Shadow_DrawEntityShadow(entity_render_t *ent) relativeshadowmaxs[0] = relativeshadoworigin[0] + relativeshadowradius; relativeshadowmaxs[1] = relativeshadoworigin[1] + relativeshadowradius; relativeshadowmaxs[2] = relativeshadoworigin[2] + relativeshadowradius; - if (r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAPRECTANGLE || r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAPCUBESIDE || r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAP2D) + switch (r_shadow_rendermode) { + case R_SHADOW_RENDERMODE_SHADOWMAP2D: + case R_SHADOW_RENDERMODE_SHADOWMAPRECTANGLE: + case R_SHADOW_RENDERMODE_SHADOWMAPCUBESIDE: ent->model->DrawShadowMap(r_shadow_shadowmapside, ent, relativeshadoworigin, NULL, relativeshadowradius, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, NULL, relativeshadowmins, relativeshadowmaxs); - } - else + break; + default: ent->model->DrawShadowVolume(ent, relativeshadoworigin, NULL, relativeshadowradius, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, relativeshadowmins, relativeshadowmaxs); + break; + } rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } @@ -3414,19 +3492,19 @@ void R_Shadow_PrepareLight(rtlight_t *rtlight) static entity_render_t *lightentities_noselfshadow[MAX_EDICTS]; static entity_render_t *shadowentities[MAX_EDICTS]; static entity_render_t *shadowentities_noselfshadow[MAX_EDICTS]; + qboolean nolight; rtlight->draw = false; // skip lights that don't light because of ambientscale+diffusescale+specularscale being 0 (corona only lights) // skip lights that are basically invisible (color 0 0 0) - if (VectorLength2(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale) < (1.0f / 1048576.0f)) - return; + nolight = VectorLength2(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale) < (1.0f / 1048576.0f); // loading is done before visibility checks because loading should happen // all at once at the start of a level, not when it stalls gameplay. // (especially important to benchmarks) // compile light - if (rtlight->isstatic && (!rtlight->compiled || (rtlight->shadow && rtlight->shadowmode != (int)r_shadow_shadowmode)) && r_shadow_realtime_world_compile.integer) + if (rtlight->isstatic && !nolight && (!rtlight->compiled || (rtlight->shadow && rtlight->shadowmode != (int)r_shadow_shadowmode)) && r_shadow_realtime_world_compile.integer) { if (rtlight->compiled) R_RTLight_Uncompile(rtlight); @@ -3451,6 +3529,10 @@ void R_Shadow_PrepareLight(rtlight_t *rtlight) if (VectorLength2(rtlight->currentcolor) < (1.0f / 1048576.0f)) return; + // skip processing on corona-only lights + if (nolight) + return; + // if the light box is offscreen, skip it if (R_CullBox(rtlight->cullmins, rtlight->cullmaxs)) return; @@ -4001,6 +4083,8 @@ void R_Shadow_PrepareLights(void) r_shadow_shadowmapborder != bound(0, r_shadow_shadowmapping_bordersize.integer, 16)) R_Shadow_FreeShadowMaps(); + r_shadow_usingshadowmaportho = false; + switch (vid.renderpath) { case RENDERPATH_GL20: @@ -4022,9 +4106,9 @@ void R_Shadow_PrepareLights(void) r_shadow_prepass_width = vid.width; r_shadow_prepass_height = vid.height; r_shadow_prepassgeometrydepthtexture = R_LoadTextureShadowMap2D(r_shadow_texturepool, "prepassgeometrydepthmap", vid.width, vid.height, 24, false); - r_shadow_prepassgeometrynormalmaptexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrynormalmap", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, NULL); - r_shadow_prepasslightingdiffusetexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingdiffuse", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, NULL); - r_shadow_prepasslightingspeculartexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingspecular", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, NULL); + r_shadow_prepassgeometrynormalmaptexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrynormalmap", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + r_shadow_prepasslightingdiffusetexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingdiffuse", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + r_shadow_prepasslightingspeculartexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingspecular", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); // set up the geometry pass fbo (depth + normalmap) qglGenFramebuffersEXT(1, &r_shadow_prepassgeometryfbo);CHECKGLERROR @@ -4158,6 +4242,243 @@ extern cvar_t r_shadows_drawafterrtlighting; extern cvar_t r_shadows_castfrombmodels; extern cvar_t r_shadows_throwdistance; extern cvar_t r_shadows_throwdirection; +extern cvar_t r_shadows_focus; +extern cvar_t r_shadows_shadowmapscale; + +void R_Shadow_PrepareModelShadows(void) +{ + int i; + float scale, size, radius, dot1, dot2; + vec3_t shadowdir, shadowforward, shadowright, shadoworigin, shadowfocus, shadowmins, shadowmaxs; + entity_render_t *ent; + + if (!r_refdef.scene.numentities) + return; + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + case R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE: + if (r_shadows.integer >= 2) + break; + // fall through + case R_SHADOW_SHADOWMODE_STENCIL: + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + if (!ent->animcache_vertex3f && ent->model && ent->model->DrawShadowVolume != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + R_AnimCache_GetEntity(ent, false, false); + } + return; + default: + return; + } + + size = 2*r_shadow_shadowmapmaxsize; + scale = r_shadow_shadowmapping_precision.value * r_shadows_shadowmapscale.value; + radius = 0.5f * size / scale; + + Math_atov(r_shadows_throwdirection.string, shadowdir); + VectorNormalize(shadowdir); + dot1 = DotProduct(r_refdef.view.forward, shadowdir); + dot2 = DotProduct(r_refdef.view.up, shadowdir); + if (fabs(dot1) <= fabs(dot2)) + VectorMA(r_refdef.view.forward, -dot1, shadowdir, shadowforward); + else + VectorMA(r_refdef.view.up, -dot2, shadowdir, shadowforward); + VectorNormalize(shadowforward); + CrossProduct(shadowdir, shadowforward, shadowright); + Math_atov(r_shadows_focus.string, shadowfocus); + VectorM(shadowfocus[0], r_refdef.view.right, shadoworigin); + VectorMA(shadoworigin, shadowfocus[1], r_refdef.view.up, shadoworigin); + VectorMA(shadoworigin, -shadowfocus[2], r_refdef.view.forward, shadoworigin); + VectorAdd(shadoworigin, r_refdef.view.origin, shadoworigin); + if (shadowfocus[0] || shadowfocus[1] || shadowfocus[2]) + dot1 = 1; + VectorMA(shadoworigin, (1.0f - fabs(dot1)) * radius, shadowforward, shadoworigin); + + shadowmins[0] = shadoworigin[0] - r_shadows_throwdistance.value * fabs(shadowdir[0]) - radius * (fabs(shadowforward[0]) + fabs(shadowright[0])); + shadowmins[1] = shadoworigin[1] - r_shadows_throwdistance.value * fabs(shadowdir[1]) - radius * (fabs(shadowforward[1]) + fabs(shadowright[1])); + shadowmins[2] = shadoworigin[2] - r_shadows_throwdistance.value * fabs(shadowdir[2]) - radius * (fabs(shadowforward[2]) + fabs(shadowright[2])); + shadowmaxs[0] = shadoworigin[0] + r_shadows_throwdistance.value * fabs(shadowdir[0]) + radius * (fabs(shadowforward[0]) + fabs(shadowright[0])); + shadowmaxs[1] = shadoworigin[1] + r_shadows_throwdistance.value * fabs(shadowdir[1]) + radius * (fabs(shadowforward[1]) + fabs(shadowright[1])); + shadowmaxs[2] = shadoworigin[2] + r_shadows_throwdistance.value * fabs(shadowdir[2]) + radius * (fabs(shadowforward[2]) + fabs(shadowright[2])); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + if (!BoxesOverlap(ent->mins, ent->maxs, shadowmins, shadowmaxs)) + continue; + // cast shadows from anything of the map (submodels are optional) + if (!ent->animcache_vertex3f && ent->model && ent->model->DrawShadowMap != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + R_AnimCache_GetEntity(ent, false, false); + } +} + +void R_DrawModelShadowMaps(void) +{ + int i; + float relativethrowdistance, scale, size, radius, nearclip, farclip, bias, dot1, dot2; + entity_render_t *ent; + vec3_t relativelightorigin; + vec3_t relativelightdirection, relativeforward, relativeright; + vec3_t relativeshadowmins, relativeshadowmaxs; + vec3_t shadowdir, shadowforward, shadowright, shadoworigin, shadowfocus; + float m[12]; + matrix4x4_t shadowmatrix, cameramatrix, mvpmatrix, invmvpmatrix, scalematrix, texmatrix; + r_viewport_t viewport; + GLuint fbo = 0; + + if (!r_refdef.scene.numentities) + return; + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + case R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE: + break; + default: + return; + } + + CHECKGLERROR + R_ResetViewRendering3D(); + R_Shadow_RenderMode_Begin(); + R_Shadow_RenderMode_ActiveLight(NULL); + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (!r_shadow_shadowmap2dtexture) + R_Shadow_MakeShadowMap(0, r_shadow_shadowmapmaxsize); + fbo = r_shadow_fbo2d; + r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2dtexture); + r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2dtexture); + r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D; + break; + case R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE: + if (!r_shadow_shadowmaprectangletexture) + R_Shadow_MakeShadowMap(0, r_shadow_shadowmapmaxsize); + fbo = r_shadow_fborectangle; + r_shadow_shadowmap_texturescale[0] = 1.0f; + r_shadow_shadowmap_texturescale[1] = 1.0f; + r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAPRECTANGLE; + break; + default: + break; + } + + size = 2*r_shadow_shadowmapmaxsize; + scale = (r_shadow_shadowmapping_precision.value * r_shadows_shadowmapscale.value) / size; + radius = 0.5f / scale; + nearclip = -r_shadows_throwdistance.value; + farclip = r_shadows_throwdistance.value; + bias = r_shadow_shadowmapping_bias.value * r_shadow_shadowmapping_nearclip.value / (2 * r_shadows_throwdistance.value) * (1024.0f / size); + + r_shadow_shadowmap_parameters[0] = size; + r_shadow_shadowmap_parameters[1] = size; + r_shadow_shadowmap_parameters[2] = 1.0; + r_shadow_shadowmap_parameters[3] = bound(0.0f, 1.0f - r_shadows_darken.value, 1.0f); + + Math_atov(r_shadows_throwdirection.string, shadowdir); + VectorNormalize(shadowdir); + Math_atov(r_shadows_focus.string, shadowfocus); + VectorM(shadowfocus[0], r_refdef.view.right, shadoworigin); + VectorMA(shadoworigin, shadowfocus[1], r_refdef.view.up, shadoworigin); + VectorMA(shadoworigin, -shadowfocus[2], r_refdef.view.forward, shadoworigin); + VectorAdd(shadoworigin, r_refdef.view.origin, shadoworigin); + dot1 = DotProduct(r_refdef.view.forward, shadowdir); + dot2 = DotProduct(r_refdef.view.up, shadowdir); + if (fabs(dot1) <= fabs(dot2)) + VectorMA(r_refdef.view.forward, -dot1, shadowdir, shadowforward); + else + VectorMA(r_refdef.view.up, -dot2, shadowdir, shadowforward); + VectorNormalize(shadowforward); + VectorM(scale, shadowforward, &m[0]); + if (shadowfocus[0] || shadowfocus[1] || shadowfocus[2]) + dot1 = 1; + m[3] = fabs(dot1) * 0.5f - DotProduct(shadoworigin, &m[0]); + CrossProduct(shadowdir, shadowforward, shadowright); + VectorM(scale, shadowright, &m[4]); + m[7] = 0.5f - DotProduct(shadoworigin, &m[4]); + VectorM(1.0f / (farclip - nearclip), shadowdir, &m[8]); + m[11] = 0.5f - DotProduct(shadoworigin, &m[8]); + Matrix4x4_FromArray12FloatD3D(&shadowmatrix, m); + Matrix4x4_Invert_Full(&cameramatrix, &shadowmatrix); + R_Viewport_InitOrtho(&viewport, &cameramatrix, 0, 0, size, size, 0, 0, 1, 1, 0, -1, NULL); + + VectorMA(shadoworigin, (1.0f - fabs(dot1)) * radius, shadowforward, shadoworigin); + +#if 0 + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);CHECKGLERROR + R_SetupShader_ShowDepth(); +#else + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);CHECKGLERROR + R_SetupShader_DepthOrShadow(); +#endif + CHECKGLERROR + GL_PolygonOffset(r_shadow_shadowmapping_polygonfactor.value, r_shadow_shadowmapping_polygonoffset.value); + GL_DepthMask(true); + GL_DepthTest(true); + R_SetViewport(&viewport); + GL_Scissor(viewport.x, viewport.y, min(viewport.width + r_shadow_shadowmapborder, 2*r_shadow_shadowmapmaxsize), viewport.height + r_shadow_shadowmapborder); + qglClearDepth(1); +#if 0 + qglClearColor(1,1,1,1); + GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +#else + GL_Clear(GL_DEPTH_BUFFER_BIT); +#endif + GL_Scissor(viewport.x + r_shadow_shadowmapborder, viewport.y + r_shadow_shadowmapborder, viewport.width - 2*r_shadow_shadowmapborder, viewport.height - 2*r_shadow_shadowmapborder); + CHECKGLERROR + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + + // cast shadows from anything of the map (submodels are optional) + if (ent->model && ent->model->DrawShadowMap != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + { + relativethrowdistance = r_shadows_throwdistance.value * Matrix4x4_ScaleFromMatrix(&ent->inversematrix); + Matrix4x4_Transform(&ent->inversematrix, shadoworigin, relativelightorigin); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowdir, relativelightdirection); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowforward, relativeforward); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowright, relativeright); + relativeshadowmins[0] = relativelightorigin[0] - r_shadows_throwdistance.value * fabs(relativelightdirection[0]) - radius * (fabs(relativeforward[0]) + fabs(relativeright[0])); + relativeshadowmins[1] = relativelightorigin[1] - r_shadows_throwdistance.value * fabs(relativelightdirection[1]) - radius * (fabs(relativeforward[1]) + fabs(relativeright[1])); + relativeshadowmins[2] = relativelightorigin[2] - r_shadows_throwdistance.value * fabs(relativelightdirection[2]) - radius * (fabs(relativeforward[2]) + fabs(relativeright[2])); + relativeshadowmaxs[0] = relativelightorigin[0] + r_shadows_throwdistance.value * fabs(relativelightdirection[0]) + radius * (fabs(relativeforward[0]) + fabs(relativeright[0])); + relativeshadowmaxs[1] = relativelightorigin[1] + r_shadows_throwdistance.value * fabs(relativelightdirection[1]) + radius * (fabs(relativeforward[1]) + fabs(relativeright[1])); + relativeshadowmaxs[2] = relativelightorigin[2] + r_shadows_throwdistance.value * fabs(relativelightdirection[2]) + radius * (fabs(relativeforward[2]) + fabs(relativeright[2])); + RSurf_ActiveModelEntity(ent, false, false, false); + ent->model->DrawShadowMap(0, ent, relativelightorigin, relativelightdirection, relativethrowdistance, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, NULL, relativeshadowmins, relativeshadowmaxs); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + } + } + + R_Shadow_RenderMode_End(); + + Matrix4x4_Concat(&mvpmatrix, &r_refdef.view.viewport.projectmatrix, &r_refdef.view.viewport.viewmatrix); + Matrix4x4_Invert_Full(&invmvpmatrix, &mvpmatrix); + Matrix4x4_CreateScale3(&scalematrix, size, -size, 1); + Matrix4x4_AdjustOrigin(&scalematrix, 0, size, -0.5f * bias); + Matrix4x4_Concat(&texmatrix, &scalematrix, &shadowmatrix); + Matrix4x4_Concat(&r_shadow_shadowmapmatrix, &texmatrix, &invmvpmatrix); + + r_shadow_usingshadowmaportho = true; + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + r_shadow_usingshadowmap2d = true; + break; + case R_SHADOW_SHADOWMODE_SHADOWMAPRECTANGLE: + r_shadow_usingshadowmaprect = true; + break; + default: + break; + } +} + void R_DrawModelShadows(void) { int i; @@ -4168,7 +4489,7 @@ void R_DrawModelShadows(void) vec3_t relativeshadowmins, relativeshadowmaxs; vec3_t tmp, shadowdir; - if (!r_refdef.scene.numentities || !vid.stencil) + if (!r_refdef.scene.numentities || !vid.stencil || (r_shadow_shadowmode != R_SHADOW_SHADOWMODE_STENCIL && r_shadows.integer != 1)) return; CHECKGLERROR @@ -4208,26 +4529,34 @@ void R_DrawModelShadows(void) { if(ent->entitynumber != 0) { - // networked entity - might be attached in some way (then we should use the parent's light direction, to not tear apart attached entities) - int entnum, entnum2, recursion; - entnum = entnum2 = ent->entitynumber; - for(recursion = 32; recursion > 0; --recursion) + if(ent->entitynumber >= MAX_EDICTS) // csqc entity { - entnum2 = cl.entities[entnum].state_current.tagentity; - if(entnum2 >= 1 && entnum2 < cl.num_entities && cl.entities_active[entnum2]) - entnum = entnum2; - else - break; + // FIXME handle this + VectorNegate(ent->modellight_lightdir, relativelightdirection); } - if(recursion && recursion != 32) // if we followed a valid non-empty attachment chain + else { - VectorNegate(cl.entities[entnum].render.modellight_lightdir, relativelightdirection); - // transform into modelspace of OUR entity - Matrix4x4_Transform3x3(&cl.entities[entnum].render.matrix, relativelightdirection, tmp); - Matrix4x4_Transform3x3(&ent->inversematrix, tmp, relativelightdirection); + // networked entity - might be attached in some way (then we should use the parent's light direction, to not tear apart attached entities) + int entnum, entnum2, recursion; + entnum = entnum2 = ent->entitynumber; + for(recursion = 32; recursion > 0; --recursion) + { + entnum2 = cl.entities[entnum].state_current.tagentity; + if(entnum2 >= 1 && entnum2 < cl.num_entities && cl.entities_active[entnum2]) + entnum = entnum2; + else + break; + } + if(recursion && recursion != 32) // if we followed a valid non-empty attachment chain + { + VectorNegate(cl.entities[entnum].render.modellight_lightdir, relativelightdirection); + // transform into modelspace of OUR entity + Matrix4x4_Transform3x3(&cl.entities[entnum].render.matrix, relativelightdirection, tmp); + Matrix4x4_Transform3x3(&ent->inversematrix, tmp, relativelightdirection); + } + else + VectorNegate(ent->modellight_lightdir, relativelightdirection); } - else - VectorNegate(ent->modellight_lightdir, relativelightdirection); } else VectorNegate(ent->modellight_lightdir, relativelightdirection); @@ -4602,7 +4931,8 @@ void R_Shadow_DrawLightSprites(void) if (light) R_MeshQueue_AddTransparent(light->origin, R_Shadow_DrawLightSprite_TransparentCallback, (entity_render_t *)light, 5, &light->rtlight); } - R_MeshQueue_AddTransparent(r_editlights_cursorlocation, R_Shadow_DrawCursor_TransparentCallback, NULL, 0, NULL); + if (!r_editlights_lockcursor) + R_MeshQueue_AddTransparent(r_editlights_cursorlocation, R_Shadow_DrawCursor_TransparentCallback, NULL, 0, NULL); } int R_Shadow_GetRTLightInfo(unsigned int lightindex, float *origin, float *radius, float *color) @@ -4634,6 +4964,9 @@ void R_Shadow_SelectLightInView(void) size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked best = NULL; bestrating = 0; + + if (r_editlights_lockcursor) + return; for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); @@ -4664,8 +4997,7 @@ void R_Shadow_LoadWorldLights(void) Con_Print("No map loaded.\n"); return; } - FS_StripExtension (cl.worldmodel->name, name, sizeof (name)); - strlcat (name, ".rtlights", sizeof (name)); + dpsnprintf(name, sizeof(name), "%s.rtlights", cl.worldnamenoextension); lightsstring = (char *)FS_LoadFile(name, tempmempool, false, NULL); if (lightsstring) { @@ -4775,8 +5107,7 @@ void R_Shadow_SaveWorldLights(void) Con_Print("No map loaded.\n"); return; } - FS_StripExtension (cl.worldmodel->name, name, sizeof (name)); - strlcat (name, ".rtlights", sizeof (name)); + dpsnprintf(name, sizeof(name), "%s.rtlights", cl.worldnamenoextension); bufchars = bufmaxchars = 0; buf = NULL; for (lightindex = 0;lightindex < range;lightindex++) @@ -4824,8 +5155,7 @@ void R_Shadow_LoadLightsFile(void) Con_Print("No map loaded.\n"); return; } - FS_StripExtension (cl.worldmodel->name, name, sizeof (name)); - strlcat (name, ".lights", sizeof (name)); + dpsnprintf(name, sizeof(name), "%s.lights", cl.worldnamenoextension); lightsstring = (char *)FS_LoadFile(name, tempmempool, false, NULL); if (lightsstring) { @@ -4887,8 +5217,7 @@ void R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(void) return; } // try to load a .ent file first - FS_StripExtension (cl.worldmodel->name, key, sizeof (key)); - strlcat (key, ".ent", sizeof (key)); + dpsnprintf(key, sizeof(key), "%s.ent", cl.worldnamenoextension); data = entfiledata = (char *)FS_LoadFile(key, tempmempool, true, NULL); // and if that is not found, fall back to the bsp file entity string if (!data) @@ -5137,7 +5466,7 @@ void R_Shadow_EditLights_Reload_f(void) { if (!cl.worldmodel) return; - strlcpy(r_shadow_mapname, cl.worldmodel->name, sizeof(r_shadow_mapname)); + strlcpy(r_shadow_mapname, cl.worldname, sizeof(r_shadow_mapname)); R_Shadow_ClearWorldLights(); R_Shadow_LoadWorldLights(); if (!Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)) @@ -5502,7 +5831,7 @@ void R_Shadow_EditLights_Edit_f(void) void R_Shadow_EditLights_EditAll_f(void) { size_t lightindex; - dlight_t *light; + dlight_t *light, *oldselected; size_t range; if (!r_editlights.integer) @@ -5511,6 +5840,7 @@ void R_Shadow_EditLights_EditAll_f(void) return; } + oldselected = r_shadow_selectedlight; // EditLights doesn't seem to have a "remove" command or something so: range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) @@ -5521,6 +5851,8 @@ void R_Shadow_EditLights_EditAll_f(void) R_Shadow_SelectLight(light); R_Shadow_EditLights_Edit_f(); } + // return to old selected (to not mess editing once selection is locked) + R_Shadow_SelectLight(oldselected); } void R_Shadow_EditLights_DrawSelectedLightProperties(void) @@ -5630,6 +5962,7 @@ void R_Shadow_EditLights_Help_f(void) "r_editlights_help : this help\n" "r_editlights_clear : remove all lights\n" "r_editlights_reload : reload .rtlights, .lights file, or entities\n" +"r_editlights_lock : lock selection to current light, if already locked - unlock\n" "r_editlights_save : save to .rtlights file\n" "r_editlights_spawn : create a light with default settings\n" "r_editlights_edit command : edit selected light - more documentation below\n" @@ -5714,6 +6047,26 @@ void R_Shadow_EditLights_PasteInfo_f(void) R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_bufferlight.angles, r_shadow_bufferlight.color, r_shadow_bufferlight.radius, r_shadow_bufferlight.corona, r_shadow_bufferlight.style, r_shadow_bufferlight.shadow, r_shadow_bufferlight.cubemapname, r_shadow_bufferlight.coronasizescale, r_shadow_bufferlight.ambientscale, r_shadow_bufferlight.diffusescale, r_shadow_bufferlight.specularscale, r_shadow_bufferlight.flags); } +void R_Shadow_EditLights_Lock_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot lock on light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (r_editlights_lockcursor) + { + r_editlights_lockcursor = false; + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light to lock on.\n"); + return; + } + r_editlights_lockcursor = true; +} + void R_Shadow_EditLights_Init(void) { Cvar_RegisterVariable(&r_editlights); @@ -5736,6 +6089,7 @@ void R_Shadow_EditLights_Init(void) Cmd_AddCommand("r_editlights_importlightsfile", R_Shadow_EditLights_ImportLightsFile_f, "load lights from .lights file (ignoring .rtlights or .ent files and map entities)"); Cmd_AddCommand("r_editlights_copyinfo", R_Shadow_EditLights_CopyInfo_f, "store a copy of all properties (except origin) of the selected light"); Cmd_AddCommand("r_editlights_pasteinfo", R_Shadow_EditLights_PasteInfo_f, "apply the stored properties onto the selected light (making it exactly identical except for origin)"); + Cmd_AddCommand("r_editlights_lock", R_Shadow_EditLights_Lock_f, "lock selection to current light, if already locked - unlock"); }