qzdoom-gpl/src/gl/scene/gl_walls_draw.cpp
Christoph Oelckers 8b6e09ca09 - changed the license of the OpenGL renderer to LGPL v3.
This was done to clean up the license and to ensure that any commercial fork of the engine has to obey the far stricter requirements concerning source distribution. The old license was compatible with GPLv2 whereas combining GPLv2 and LGPLv3 force a license upgrade to GPLv3. The license of code that originates from ZDoomGL has not been changed.
2016-09-14 20:01:13 +02:00

459 lines
13 KiB
C++

//
//---------------------------------------------------------------------------
//
// Copyright(C) 2000-2016 Christoph Oelckers
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
#include "gl/system/gl_system.h"
#include "p_local.h"
#include "p_lnspec.h"
#include "a_sharedglobal.h"
#include "gl/gl_functions.h"
#include "gl/system/gl_interface.h"
#include "gl/system/gl_cvars.h"
#include "gl/renderer/gl_lightdata.h"
#include "gl/renderer/gl_renderstate.h"
#include "gl/renderer/gl_renderer.h"
#include "gl/data/gl_data.h"
#include "gl/data/gl_vertexbuffer.h"
#include "gl/dynlights/gl_dynlight.h"
#include "gl/dynlights/gl_glow.h"
#include "gl/dynlights/gl_lightbuffer.h"
#include "gl/scene/gl_drawinfo.h"
#include "gl/scene/gl_portal.h"
#include "gl/shaders/gl_shader.h"
#include "gl/textures/gl_material.h"
#include "gl/utility/gl_clock.h"
#include "gl/utility/gl_templates.h"
#include "gl/renderer/gl_quaddrawer.h"
EXTERN_CVAR(Bool, gl_seamless)
//==========================================================================
//
// Collect lights for shader
//
//==========================================================================
FDynLightData lightdata;
void GLWall::SetupLights()
{
if (RenderStyle == STYLE_Add) return; // no lights on additively blended surfaces.
// check for wall types which cannot have dynamic lights on them (portal types never get here so they don't need to be checked.)
switch (type)
{
case RENDERWALL_FOGBOUNDARY:
case RENDERWALL_MIRRORSURFACE:
case RENDERWALL_COLOR:
return;
}
float vtx[]={glseg.x1,zbottom[0],glseg.y1, glseg.x1,ztop[0],glseg.y1, glseg.x2,ztop[1],glseg.y2, glseg.x2,zbottom[1],glseg.y2};
Plane p;
lightdata.Clear();
p.Init(vtx,4);
if (!p.ValidNormal())
{
return;
}
FLightNode *node;
if (seg->sidedef == NULL)
{
node = NULL;
}
else if (!(seg->sidedef->Flags & WALLF_POLYOBJ))
{
node = seg->sidedef->lighthead;
}
else if (sub)
{
// Polobject segs cannot be checked per sidedef so use the subsector instead.
node = sub->lighthead;
}
else node = NULL;
// Iterate through all dynamic lights which touch this wall and render them
while (node)
{
if (!(node->lightsource->flags2&MF2_DORMANT))
{
iter_dlight++;
Vector fn, pos;
float x = node->lightsource->X();
float y = node->lightsource->Y();
float z = node->lightsource->Z();
float dist = fabsf(p.DistToPoint(x, z, y));
float radius = node->lightsource->GetRadius();
float scale = 1.0f / ((2.f * radius) - dist);
if (radius > 0.f && dist < radius)
{
Vector nearPt, up, right;
pos.Set(x,z,y);
fn=p.Normal();
fn.GetRightUp(right, up);
Vector tmpVec = fn * dist;
nearPt = pos + tmpVec;
Vector t1;
int outcnt[4]={0,0,0,0};
texcoord tcs[4];
// do a quick check whether the light touches this polygon
for(int i=0;i<4;i++)
{
t1.Set(&vtx[i*3]);
Vector nearToVert = t1 - nearPt;
tcs[i].u = (nearToVert.Dot(right) * scale) + 0.5f;
tcs[i].v = (nearToVert.Dot(up) * scale) + 0.5f;
if (tcs[i].u<0) outcnt[0]++;
if (tcs[i].u>1) outcnt[1]++;
if (tcs[i].v<0) outcnt[2]++;
if (tcs[i].v>1) outcnt[3]++;
}
if (outcnt[0]!=4 && outcnt[1]!=4 && outcnt[2]!=4 && outcnt[3]!=4)
{
gl_GetLight(seg->frontsector->PortalGroup, p, node->lightsource, true, lightdata);
}
}
}
node = node->nextLight;
}
dynlightindex = GLRenderer->mLights->UploadLights(lightdata);
}
//==========================================================================
//
// build the vertices for this wall
//
//==========================================================================
void GLWall::MakeVertices(bool nosplit)
{
if (vertcount == 0)
{
bool split = (gl_seamless && !nosplit && seg->sidedef != NULL && !(seg->sidedef->Flags & WALLF_POLYOBJ) && !(flags & GLWF_NOSPLIT));
FFlatVertex *ptr = GLRenderer->mVBO->GetBuffer();
ptr->Set(glseg.x1, zbottom[0], glseg.y1, tcs[LOLFT].u, tcs[LOLFT].v);
ptr++;
if (split && glseg.fracleft == 0) SplitLeftEdge(ptr);
ptr->Set(glseg.x1, ztop[0], glseg.y1, tcs[UPLFT].u, tcs[UPLFT].v);
ptr++;
if (split && !(flags & GLWF_NOSPLITUPPER)) SplitUpperEdge(ptr);
ptr->Set(glseg.x2, ztop[1], glseg.y2, tcs[UPRGT].u, tcs[UPRGT].v);
ptr++;
if (split && glseg.fracright == 1) SplitRightEdge(ptr);
ptr->Set(glseg.x2, zbottom[1], glseg.y2, tcs[LORGT].u, tcs[LORGT].v);
ptr++;
if (split && !(flags & GLWF_NOSPLITLOWER)) SplitLowerEdge(ptr);
vertcount = GLRenderer->mVBO->GetCount(ptr, &vertindex);
}
}
//==========================================================================
//
// General purpose wall rendering function
// everything goes through here
//
//==========================================================================
void GLWall::RenderWall(int textured)
{
gl_RenderState.Apply();
gl_RenderState.ApplyLightIndex(dynlightindex);
if (gl.buffermethod != BM_DEFERRED)
{
MakeVertices(!(textured&RWF_NOSPLIT));
}
else if (vertcount == 0)
{
// This should never happen but in case it actually does, use the quad drawer as fallback (without edge splitting.)
// This way it at least gets drawn.
FQuadDrawer qd;
qd.Set(0, glseg.x1, zbottom[0], glseg.y1, tcs[LOLFT].u, tcs[LOLFT].v);
qd.Set(1, glseg.x1, ztop[0], glseg.y1, tcs[UPLFT].u, tcs[UPLFT].v);
qd.Set(2, glseg.x2, ztop[1], glseg.y2, tcs[UPRGT].u, tcs[UPRGT].v);
qd.Set(3, glseg.x2, zbottom[1], glseg.y2, tcs[LORGT].u, tcs[LORGT].v);
qd.Render(GL_TRIANGLE_FAN);
vertexcount += 4;
return;
}
GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, vertindex, vertcount);
vertexcount += vertcount;
}
//==========================================================================
//
//
//
//==========================================================================
void GLWall::RenderFogBoundary()
{
if (gl_fogmode && gl_fixedcolormap == 0)
{
if (!gl.legacyMode)
{
int rel = rellight + getExtraLight();
gl_SetFog(lightlevel, rel, &Colormap, false);
gl_RenderState.SetEffect(EFF_FOGBOUNDARY);
gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(-1.0f, -128.0f);
RenderWall(RWF_BLANK);
glPolygonOffset(0.0f, 0.0f);
glDisable(GL_POLYGON_OFFSET_FILL);
gl_RenderState.SetEffect(EFF_NONE);
}
else
{
RenderFogBoundaryCompat();
}
}
}
//==========================================================================
//
//
//
//==========================================================================
void GLWall::RenderMirrorSurface()
{
if (GLRenderer->mirrortexture == NULL) return;
// For the sphere map effect we need a normal of the mirror surface,
Vector v(glseg.y2-glseg.y1, 0 ,-glseg.x2+glseg.x1);
v.Normalize();
if (!gl.legacyMode)
{
// we use texture coordinates and texture matrix to pass the normal stuff to the shader so that the default vertex buffer format can be used as is.
tcs[LOLFT].u = tcs[LORGT].u = tcs[UPLFT].u = tcs[UPRGT].u = v.X();
tcs[LOLFT].v = tcs[LORGT].v = tcs[UPLFT].v = tcs[UPRGT].v = v.Z();
gl_RenderState.EnableTextureMatrix(true);
gl_RenderState.mTextureMatrix.computeNormalMatrix(gl_RenderState.mViewMatrix);
}
else
{
glNormal3fv(&v[0]);
}
// Use sphere mapping for this
gl_RenderState.SetEffect(EFF_SPHEREMAP);
gl_SetColor(lightlevel, 0, Colormap ,0.1f);
gl_SetFog(lightlevel, 0, &Colormap, true);
gl_RenderState.BlendFunc(GL_SRC_ALPHA,GL_ONE);
gl_RenderState.AlphaFunc(GL_GREATER,0);
glDepthFunc(GL_LEQUAL);
FMaterial * pat=FMaterial::ValidateTexture(GLRenderer->mirrortexture, false);
gl_RenderState.SetMaterial(pat, CLAMP_NONE, 0, -1, false);
flags &= ~GLWF_GLOW;
RenderWall(RWF_BLANK);
gl_RenderState.EnableTextureMatrix(false);
gl_RenderState.SetEffect(EFF_NONE);
// Restore the defaults for the translucent pass
gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold);
glDepthFunc(GL_LESS);
// This is drawn in the translucent pass which is done after the decal pass
// As a result the decals have to be drawn here.
if (seg->sidedef->AttachedDecals)
{
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(-1.0f, -128.0f);
glDepthMask(false);
DoDrawDecals();
glDepthMask(true);
glPolygonOffset(0.0f, 0.0f);
glDisable(GL_POLYGON_OFFSET_FILL);
gl_RenderState.SetTextureMode(TM_MODULATE);
gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
}
//==========================================================================
//
//
//
//==========================================================================
void GLWall::RenderTextured(int rflags)
{
int tmode = gl_RenderState.GetTextureMode();
int rel = rellight + getExtraLight();
if (flags & GLWF_GLOW)
{
gl_RenderState.EnableGlow(true);
gl_RenderState.SetGlowPlanes(topplane, bottomplane);
gl_RenderState.SetGlowParams(topglowcolor, bottomglowcolor);
}
gl_RenderState.SetMaterial(gltexture, flags & 3, 0, -1, false);
if (type == RENDERWALL_M2SNF)
{
if (flags & GLT_CLAMPY)
{
if (tmode == TM_MODULATE) gl_RenderState.SetTextureMode(TM_CLAMPY);
}
gl_SetFog(255, 0, NULL, false);
}
float absalpha = fabsf(alpha);
if (lightlist == NULL)
{
gl_SetColor(lightlevel, rel, Colormap, absalpha);
if (type != RENDERWALL_M2SNF) gl_SetFog(lightlevel, rel, &Colormap, RenderStyle == STYLE_Add);
RenderWall(rflags);
}
else
{
gl_RenderState.EnableSplit(true);
for (unsigned i = 0; i < lightlist->Size(); i++)
{
secplane_t &lowplane = i == (*lightlist).Size() - 1 ? bottomplane : (*lightlist)[i + 1].plane;
// this must use the exact same calculation method as GLWall::Process etc.
float low1 = lowplane.ZatPoint(vertexes[0]);
float low2 = lowplane.ZatPoint(vertexes[1]);
if (low1 < ztop[0] || low2 < ztop[1])
{
int thisll = (*lightlist)[i].caster != NULL ? gl_ClampLight(*(*lightlist)[i].p_lightlevel) : lightlevel;
FColormap thiscm;
thiscm.FadeColor = Colormap.FadeColor;
thiscm.CopyFrom3DLight(&(*lightlist)[i]);
gl_SetColor(thisll, rel, thiscm, absalpha);
if (type != RENDERWALL_M2SNF) gl_SetFog(thisll, rel, &thiscm, RenderStyle == STYLE_Add);
gl_RenderState.SetSplitPlanes((*lightlist)[i].plane, lowplane);
RenderWall(rflags);
}
if (low1 <= zbottom[0] && low2 <= zbottom[1]) break;
}
gl_RenderState.EnableSplit(false);
}
gl_RenderState.SetTextureMode(tmode);
gl_RenderState.EnableGlow(false);
}
//==========================================================================
//
//
//
//==========================================================================
void GLWall::RenderTranslucentWall()
{
if (gltexture)
{
if (gl_fixedcolormap == CM_DEFAULT && gl_lights && gl.lightmethod == LM_DIRECT)
{
SetupLights();
}
if (!gltexture->GetTransparent()) gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold);
else gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f);
if (RenderStyle == STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA,GL_ONE);
RenderTextured(RWF_TEXTURED | RWF_NOSPLIT);
if (RenderStyle == STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
else
{
gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f);
gl_SetColor(lightlevel, 0, Colormap, fabsf(alpha));
gl_SetFog(lightlevel, 0, &Colormap, RenderStyle == STYLE_Add);
gl_RenderState.EnableTexture(false);
RenderWall(RWF_NOSPLIT);
gl_RenderState.EnableTexture(true);
}
}
//==========================================================================
//
//
//
//==========================================================================
void GLWall::Draw(int pass)
{
switch (pass)
{
case GLPASS_LIGHTSONLY:
SetupLights();
break;
case GLPASS_ALL:
SetupLights();
// fall through
case GLPASS_PLAIN:
RenderTextured(RWF_TEXTURED);
break;
case GLPASS_TRANSLUCENT:
switch (type)
{
case RENDERWALL_MIRRORSURFACE:
RenderMirrorSurface();
break;
case RENDERWALL_FOGBOUNDARY:
RenderFogBoundary();
break;
default:
RenderTranslucentWall();
break;
}
break;
case GLPASS_LIGHTTEX:
case GLPASS_LIGHTTEX_ADDITIVE:
case GLPASS_LIGHTTEX_FOGGY:
RenderLightsCompat(pass);
break;
case GLPASS_TEXONLY:
gl_RenderState.SetMaterial(gltexture, flags & 3, 0, -1, false);
RenderWall(RWF_TEXTURED);
break;
}
}