- separated the software-renderer-specific parts of colormap processing from the common parts.

- moved testcolor and test fades into SWRenderer files.

These CCMDs work by hacking the default colormap and were never implemented for hardware rendering because they require many checks throughout the code.
This commit is contained in:
Christoph Oelckers 2017-03-15 22:04:23 +01:00
parent cd015f9340
commit 921bc763fb
27 changed files with 695 additions and 572 deletions

View File

@ -806,6 +806,7 @@ file( GLOB HEADER_FILES
set ( SWRENDER_SOURCES
swrenderer/r_swcanvas.cpp
swrenderer/r_swcolormaps.cpp
swrenderer/r_swrenderer.cpp
swrenderer/r_memory.cpp
swrenderer/r_renderthread.cpp

View File

@ -1390,10 +1390,6 @@ void G_InitLevelLocals ()
level_info_t *info;
BaseBlendA = 0.0f; // Remove underwater blend effect, if any
NormalLight.Maps = realcolormaps.Maps;
// [BB] Instead of just setting the color, we also have to reset Desaturate and build the lights.
NormalLight.ChangeColor (PalEntry (255, 255, 255), 0);
level.gravity = sv_gravity * 35/TICRATE;
level.aircontrol = sv_aircontrol;
@ -1415,18 +1411,10 @@ void G_InitLevelLocals ()
level.FromSnapshot = false;
if (level.fadeto == 0)
{
R_SetDefaultColormap (info->FadeTable);
if (strnicmp (info->FadeTable, "COLORMAP", 8) != 0)
{
level.fadeto = 0xff939393; //[SP] Hexen True-color compatibility, just use gray.
level.flags |= LEVEL_HASFADETABLE;
}
/*
}
else
{
NormalLight.ChangeFade (level.fadeto);
*/
}
level.airsupply = info->airsupply*TICRATE;
level.outsidefog = info->outsidefog;
@ -1475,8 +1463,6 @@ void G_InitLevelLocals ()
compatflags.Callback();
compatflags2.Callback();
NormalLight.ChangeFade (level.fadeto);
level.DefaultEnvironment = info->DefaultEnvironment;
}

View File

@ -591,14 +591,6 @@ void GLSceneDrawer::DrawBlend(sector_t * viewsector)
if (blendv.a == 0)
{
blendv = R_BlendForColormap(blendv);
if (blendv.a == 255)
{
// The calculated average is too dark so brighten it according to the palettes's overall brightness
int maxcol = MAX<int>(MAX<int>(GLRenderer->framebuffer->palette_brightness, blendv.r), MAX<int>(blendv.g, blendv.b));
blendv.r = blendv.r * 255 / maxcol;
blendv.g = blendv.g * 255 / maxcol;
blendv.b = blendv.b * 255 / maxcol;
}
}
if (blendv.a == 255)
@ -983,7 +975,6 @@ void GLSceneDrawer::WriteSavePic (player_t *player, FileWriter *file, int width,
struct FGLInterface : public FRenderer
{
bool UsesColormap() const override;
void Precache(uint8_t *texhitlist, TMap<PClassActor*, bool> &actorhitlist) override;
void RenderView(player_t *player) override;
void WriteSavePic (player_t *player, FileWriter *file, int width, int height) override;
@ -999,18 +990,6 @@ struct FGLInterface : public FRenderer
void Init() override;
};
//===========================================================================
//
// The GL renderer has no use for colormaps so let's
// not create them and save us some time.
//
//===========================================================================
bool FGLInterface::UsesColormap() const
{
return false;
}
//==========================================================================
//
// DFrameBuffer :: Precache

View File

@ -308,19 +308,6 @@ bool OpenGLFrameBuffer::SetContrast(float contrast)
void OpenGLFrameBuffer::UpdatePalette()
{
int rr=0,gg=0,bb=0;
for(int x=0;x<256;x++)
{
rr+=GPalette.BaseColors[x].r;
gg+=GPalette.BaseColors[x].g;
bb+=GPalette.BaseColors[x].b;
}
rr>>=8;
gg>>=8;
bb>>=8;
palette_brightness = (rr*77 + gg*143 + bb*35)/255;
if (GLRenderer)
GLRenderer->ClearTonemapPalette();
}

View File

@ -84,7 +84,6 @@ public:
void SetVSync(bool vsync);
int palette_brightness; // brightness of the active palette - this is used for screen blends
bool HWGammaActive = false; // Are we using hardware or software gamma?
std::shared_ptr<FGLDebug> mDebug; // Debug API
private:

View File

@ -385,7 +385,6 @@ static bool P_LoadBloodMap (uint8_t *data, size_t len, FMapThing **mapthings, in
static void LoadSectors (sectortype *bsec, int count)
{
FDynamicColormap *map = GetSpecialLights (PalEntry (255,255,255), level.fadeto, 0);
sector_t *sec;
char tnam[9];

View File

@ -33,6 +33,7 @@
#include "r_data/r_translate.h"
#include "v_palette.h"
#include "r_data/colormaps.h"
#include "swrenderer/r_swcolormaps.h"
#include "poly_draw_args.h"
#include "swrenderer/viewport/r_viewport.h"

View File

@ -37,6 +37,7 @@
#include "swrenderer/scene/r_light.h"
#include "swrenderer/drawers/r_draw_rgba.h"
#include "swrenderer/viewport/r_viewport.h"
#include "swrenderer/r_swcolormaps.h"
EXTERN_CVAR(Bool, r_shadercolormaps)
EXTERN_CVAR(Int, screenblocks)
@ -122,6 +123,11 @@ void PolyRenderer::RenderActorView(AActor *actor, bool dontmaplines)
P_FindParticleSubsectors();
PO_LinkToSubsectors();
R_SetupFrame(Thread.Viewport->viewpoint, Thread.Viewport->viewwindow, actor);
if (APART(R_OldBlend)) NormalLight.Maps = realcolormaps.Maps;
else NormalLight.Maps = realcolormaps.Maps + NUMCOLORMAPS * 256 * R_OldBlend;
swrenderer::CameraLight::Instance()->SetCamera(Thread.Viewport.get(), actor);
Thread.Viewport->SetupFreelook();

View File

@ -308,7 +308,7 @@ void RenderPolyPlayerSprites::RenderSprite(DPSprite *sprite, AActor *owner, floa
if (visstyle.Invert)
{
BaseColormap = &SpecialColormaps[INVERSECOLORMAP];
BaseColormap = &SpecialSWColormaps[INVERSECOLORMAP];
ColormapNum = 0;
if (BaseColormap->Maps < mybasecolormap->Maps || BaseColormap->Maps >= mybasecolormap->Maps + NUMCOLORMAPS * 256)
{
@ -319,8 +319,8 @@ void RenderPolyPlayerSprites::RenderSprite(DPSprite *sprite, AActor *owner, floa
// If we're drawing with a special colormap, but shaders for them are disabled, do
// not accelerate.
if (!r_shadercolormaps && (BaseColormap >= &SpecialColormaps[0] &&
BaseColormap <= &SpecialColormaps.Last()))
if (!r_shadercolormaps && (BaseColormap >= &SpecialSWColormaps[0] &&
BaseColormap <= &SpecialSWColormaps.Last()))
{
noaccel = true;
}
@ -405,10 +405,10 @@ void PolyScreenSprite::Render()
FColormapStyle colormapstyle;
PalEntry overlay = 0;
bool usecolormapstyle = false;
if (BaseColormap >= &SpecialColormaps[0] &&
BaseColormap < &SpecialColormaps[SpecialColormaps.Size()])
if (BaseColormap >= &SpecialSWColormaps[0] &&
BaseColormap < &SpecialSWColormaps[SpecialColormaps.Size()])
{
special = static_cast<FSpecialColormap*>(BaseColormap);
special = &SpecialColormaps[BaseColormap - &SpecialSWColormaps[0]];
}
else if (Colormap->Color == PalEntry(255, 255, 255) &&
Colormap->Desaturate == 0)

View File

@ -1,5 +1,6 @@
/*
** r_data.cpp
** colormaps.cpp
** common Colormap handling
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
@ -54,27 +55,7 @@
#include "r_utility.h"
#include "r_renderer.h"
static bool R_CheckForFixedLights(const uint8_t *colormaps);
FDynamicColormap NormalLight;
FDynamicColormap FullNormalLight; //[SP] Emulate GZDoom brightness
bool NormalLightHasFixedLights;
struct FakeCmap
{
char name[8];
PalEntry blend;
int lump;
};
TArray<FakeCmap> fakecmaps;
FSWColormap realcolormaps;
FSWColormap realfbcolormaps; //[SP] For fullbright use
size_t numfakecmaps;
TArray<FSpecialColormap> SpecialColormaps;
uint8_t DesaturateColormap[31][256];
@ -176,273 +157,6 @@ int AddSpecialColormap(float r1, float g1, float b1, float r2, float g2, float b
return SpecialColormaps.Size() - 1;
}
//==========================================================================
//
// Colored Lighting Stuffs
//
//==========================================================================
FDynamicColormap *GetSpecialLights (PalEntry color, PalEntry fade, int desaturate)
{
FDynamicColormap *colormap;
// If this colormap has already been created, just return it
for (colormap = &NormalLight; colormap != NULL; colormap = colormap->Next)
{
if (color == colormap->Color &&
fade == colormap->Fade &&
desaturate == colormap->Desaturate)
{
return colormap;
}
}
// Not found. Create it.
colormap = new FDynamicColormap;
colormap->Next = NormalLight.Next;
colormap->Color = color;
colormap->Fade = fade;
colormap->Desaturate = desaturate;
NormalLight.Next = colormap;
if (Renderer->UsesColormap())
{
colormap->Maps = new uint8_t[NUMCOLORMAPS*256];
colormap->BuildLights ();
}
else colormap->Maps = NULL;
return colormap;
}
//==========================================================================
//
// Free all lights created with GetSpecialLights
//
//==========================================================================
static void FreeSpecialLights()
{
FDynamicColormap *colormap, *next;
for (colormap = NormalLight.Next; colormap != NULL; colormap = next)
{
next = colormap->Next;
delete[] colormap->Maps;
delete colormap;
}
NormalLight.Next = NULL;
}
//==========================================================================
//
// Builds NUMCOLORMAPS colormaps lit with the specified color
//
//==========================================================================
void FDynamicColormap::BuildLights ()
{
int l, c;
int lr, lg, lb, ld, ild;
PalEntry colors[256], basecolors[256];
uint8_t *shade;
if (Maps == NULL)
return;
// Scale light to the range 0-256, so we can avoid
// dividing by 255 in the bottom loop.
lr = Color.r*256/255;
lg = Color.g*256/255;
lb = Color.b*256/255;
ld = Desaturate*256/255;
if (ld < 0) // No negative desaturations, please.
{
ld = -ld;
}
ild = 256-ld;
if (ld == 0)
{
memcpy (basecolors, GPalette.BaseColors, sizeof(basecolors));
}
else
{
// Desaturate the palette before lighting it.
for (c = 0; c < 256; c++)
{
int r = GPalette.BaseColors[c].r;
int g = GPalette.BaseColors[c].g;
int b = GPalette.BaseColors[c].b;
int intensity = ((r * 77 + g * 143 + b * 37) >> 8) * ld;
basecolors[c].r = (r*ild + intensity) >> 8;
basecolors[c].g = (g*ild + intensity) >> 8;
basecolors[c].b = (b*ild + intensity) >> 8;
basecolors[c].a = 0;
}
}
// build normal (but colored) light mappings
for (l = 0; l < NUMCOLORMAPS; l++)
{
DoBlending (basecolors, colors, 256,
Fade.r, Fade.g, Fade.b, l * (256 / NUMCOLORMAPS));
shade = Maps + 256*l;
if ((uint32_t)Color == MAKERGB(255,255,255))
{ // White light, so we can just pick the colors directly
for (c = 0; c < 256; c++)
{
*shade++ = ColorMatcher.Pick (colors[c].r, colors[c].g, colors[c].b);
}
}
else
{ // Colored light, so do the (slightly) slower thing
for (c = 0; c < 256; c++)
{
*shade++ = ColorMatcher.Pick (
(colors[c].r*lr)>>8,
(colors[c].g*lg)>>8,
(colors[c].b*lb)>>8);
}
}
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDynamicColormap::ChangeColor (PalEntry lightcolor, int desaturate)
{
if (lightcolor != Color || desaturate != Desaturate)
{
Color = lightcolor;
// [BB] desaturate must be in [0,255]
Desaturate = clamp(desaturate, 0, 255);
if (Maps) BuildLights ();
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDynamicColormap::ChangeFade (PalEntry fadecolor)
{
if (fadecolor != Fade)
{
Fade = fadecolor;
if (Maps) BuildLights ();
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDynamicColormap::ChangeColorFade (PalEntry lightcolor, PalEntry fadecolor)
{
if (lightcolor != Color || fadecolor != Fade)
{
Color = lightcolor;
Fade = fadecolor;
if (Maps) BuildLights ();
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDynamicColormap::RebuildAllLights()
{
if (Renderer->UsesColormap())
{
FDynamicColormap *cm;
for (cm = &NormalLight; cm != NULL; cm = cm->Next)
{
if (cm->Maps == NULL)
{
cm->Maps = new uint8_t[NUMCOLORMAPS*256];
cm->BuildLights ();
}
}
}
}
//==========================================================================
//
// R_SetDefaultColormap
//
//==========================================================================
void R_SetDefaultColormap (const char *name)
{
if (strnicmp (fakecmaps[0].name, name, 8) != 0)
{
int lump, i, j;
uint8_t map[256];
uint8_t unremap[256];
uint8_t remap[256];
lump = Wads.CheckNumForFullName (name, true, ns_colormaps);
if (lump == -1)
lump = Wads.CheckNumForName (name, ns_global);
// [RH] If using BUILD's palette, generate the colormap
if (lump == -1 || Wads.CheckNumForFullName("palette.dat") >= 0 || Wads.CheckNumForFullName("blood.pal") >= 0)
{
Printf ("Make colormap\n");
FDynamicColormap foo;
foo.Color = 0xFFFFFF;
foo.Fade = 0;
foo.Maps = realcolormaps.Maps;
foo.Desaturate = 0;
foo.Next = NULL;
foo.BuildLights ();
}
else
{
FWadLump lumpr = Wads.OpenLumpNum (lump);
// [RH] The colormap may not have been designed for the specific
// palette we are using, so remap it to match the current palette.
memcpy (remap, GPalette.Remap, 256);
memset (unremap, 0, 256);
for (i = 0; i < 256; ++i)
{
unremap[remap[i]] = i;
}
// Mapping to color 0 is okay, because the colormap won't be used to
// produce a masked texture.
remap[0] = 0;
for (i = 0; i < NUMCOLORMAPS; ++i)
{
uint8_t *map2 = &realcolormaps.Maps[i*256];
lumpr.Read (map, 256);
for (j = 0; j < 256; ++j)
{
map2[j] = remap[map[unremap[j]]];
}
}
}
uppercopy (fakecmaps[0].name, name);
fakecmaps[0].blend = 0;
}
}
//==========================================================================
//
// R_DeinitColormaps
@ -453,13 +167,6 @@ void R_DeinitColormaps ()
{
SpecialColormaps.Clear();
fakecmaps.Clear();
delete[] realcolormaps.Maps;
if (realfbcolormaps.Maps)
{
delete[] realfbcolormaps.Maps;
realfbcolormaps.Maps = nullptr;
}
FreeSpecialLights();
}
//==========================================================================
@ -501,41 +208,32 @@ void R_InitColormaps ()
}
}
}
realcolormaps.Maps = new uint8_t[256*NUMCOLORMAPS*fakecmaps.Size()];
R_SetDefaultColormap ("COLORMAP");
int rr = 0, gg = 0, bb = 0;
for(int x=0;x<256;x++)
{
rr += GPalette.BaseColors[x].r;
gg += GPalette.BaseColors[x].g;
bb += GPalette.BaseColors[x].b;
}
rr >>= 8;
gg >>= 8;
bb >>= 8;
int palette_brightness = (rr*77 + gg*143 + bb*35) / 255;
// To calculate the blend it will just average the colors of the first map
if (fakecmaps.Size() > 1)
{
uint8_t unremap[256], remap[256], mapin[256];
int i;
unsigned j;
uint8_t map[256];
memcpy (remap, GPalette.Remap, 256);
memset (unremap, 0, 256);
for (i = 0; i < 256; ++i)
for (unsigned j = 1; j < fakecmaps.Size(); j++)
{
unremap[remap[i]] = i;
}
remap[0] = 0;
for (j = 1; j < fakecmaps.Size(); j++)
{
if (Wads.LumpLength (fakecmaps[j].lump) >= (NUMCOLORMAPS+1)*256)
if (Wads.LumpLength (fakecmaps[j].lump) >= 256)
{
int k, r, g, b;
FWadLump lump = Wads.OpenLumpNum (fakecmaps[j].lump);
uint8_t *const map = realcolormaps.Maps + NUMCOLORMAPS*256*j;
for (k = 0; k < NUMCOLORMAPS; ++k)
{
uint8_t *map2 = &map[k*256];
lump.Read (mapin, 256);
map2[0] = 0;
for (r = 1; r < 256; ++r)
{
map2[r] = remap[mapin[unremap[r]]];
}
}
lump.Read(map, 256);
r = g = b = 0;
for (k = 0; k < 256; k++)
@ -544,27 +242,17 @@ void R_InitColormaps ()
g += GPalette.BaseColors[map[k]].g;
b += GPalette.BaseColors[map[k]].b;
}
fakecmaps[j].blend = PalEntry (255, r/256, g/256, b/256);
r /= 256;
g /= 256;
b /= 256;
// The calculated average is too dark so brighten it according to the palettes's overall brightness
int maxcol = MAX<int>(MAX<int>(palette_brightness, r), MAX<int>(g, b));
fakecmaps[j].blend = PalEntry (255, r * 255 / maxcol, g * 255 / maxcol, b * 255 / maxcol);
}
}
}
// [SP] Create a copy of the colormap
if (!realfbcolormaps.Maps)
{
realfbcolormaps.Maps = new uint8_t[256*NUMCOLORMAPS*fakecmaps.Size()];
memcpy(realfbcolormaps.Maps, realcolormaps.Maps, 256*NUMCOLORMAPS*fakecmaps.Size());
}
NormalLight.Color = PalEntry (255, 255, 255);
NormalLight.Fade = 0;
NormalLight.Maps = realcolormaps.Maps;
FullNormalLight.Color = PalEntry (255, 255, 255);
FullNormalLight.Fade = 0;
FullNormalLight.Maps = realfbcolormaps.Maps;
NormalLightHasFixedLights = R_CheckForFixedLights(realcolormaps.Maps);
numfakecmaps = fakecmaps.Size();
// build default special maps (e.g. invulnerability)
for (unsigned i = 0; i < countof(SpecialColormapParms); ++i)
@ -591,54 +279,6 @@ void R_InitColormaps ()
}
}
//==========================================================================
//
// R_CheckForFixedLights
//
// Returns true if there are any entries in the colormaps that are the
// same for every colormap and not the fade color.
//
//==========================================================================
static bool R_CheckForFixedLights(const uint8_t *colormaps)
{
const uint8_t *lastcolormap = colormaps + (NUMCOLORMAPS - 1) * 256;
uint8_t freq[256];
int i, j;
// Count the frequencies of different colors in the final colormap.
// If they occur more than X amount of times, we ignore them as a
// potential fixed light.
memset(freq, 0, sizeof(freq));
for (i = 0; i < 256; ++i)
{
freq[lastcolormap[i]]++;
}
// Now check the colormaps for fixed lights that are uncommon in the
// final coloramp.
for (i = 255; i >= 0; --i)
{
uint8_t color = lastcolormap[i];
if (freq[color] > 10) // arbitrary number to decide "common" colors
{
continue;
}
// It's rare in the final colormap. See if it's the same for all colormaps.
for (j = 0; j < NUMCOLORMAPS - 1; ++j)
{
if (colormaps[j * 256 + i] != color)
break;
}
if (j == NUMCOLORMAPS - 1)
{ // It was the same all the way across.
return true;
}
}
return false;
}
//==========================================================================
//
// [RH] Returns an index into realcolormaps. Multiply it by
@ -675,4 +315,5 @@ uint32_t R_BlendForColormap (uint32_t map)
{
return APART(map) ? map :
map < fakecmaps.Size() ? uint32_t(fakecmaps[map].blend) : 0;
}
}

View File

@ -1,9 +1,6 @@
#ifndef __RES_CMAP_H
#define __RES_CMAP_H
#include "m_fixed.h"
struct FSWColormap;
struct lightlist_t;
void R_InitColormaps ();
@ -12,8 +9,15 @@ void R_DeinitColormaps ();
uint32_t R_ColormapNumForName(const char *name); // killough 4/4/98
void R_SetDefaultColormap (const char *name); // [RH] change normal fadetable
uint32_t R_BlendForColormap (uint32_t map); // [RH] return calculated blend for a colormap
extern FSWColormap realcolormaps; // [RH] make the colormaps externally visible
extern size_t numfakecmaps;
struct FakeCmap
{
char name[8];
PalEntry blend;
int lump;
};
extern TArray<FakeCmap> fakecmaps;
// for internal use
struct FColormap
@ -79,25 +83,6 @@ struct FColormap
};
struct FSWColormap
{
uint8_t *Maps = nullptr;
PalEntry Color = 0xffffffff;
PalEntry Fade = 0xff000000;
int Desaturate = 0;
};
struct FDynamicColormap : FSWColormap
{
void ChangeFade (PalEntry fadecolor);
void ChangeColor (PalEntry lightcolor, int desaturate);
void ChangeColorFade (PalEntry lightcolor, PalEntry fadecolor);
void ChangeFogDensity(int newdensity);
void BuildLights ();
static void RebuildAllLights();
FDynamicColormap *Next;
};
// For hardware-accelerated weapon sprites in colored sectors
@ -116,13 +101,8 @@ enum
};
struct FSpecialColormap : FSWColormap
struct FSpecialColormap
{
FSpecialColormap()
{
Maps = Colormap;
}
float ColorizeStart[3];
float ColorizeEnd[3];
uint8_t Colormap[256];
@ -145,23 +125,5 @@ int AddSpecialColormap(float r1, float g1, float b1, float r2, float g2, float b
extern uint8_t DesaturateColormap[31][256];
extern FDynamicColormap NormalLight;
extern FDynamicColormap FullNormalLight;
extern bool NormalLightHasFixedLights;
FDynamicColormap *GetSpecialLights (PalEntry lightcolor, PalEntry fadecolor, int desaturate);
__forceinline FDynamicColormap *GetColorTable(const FColormap &cm)
{
auto p = &NormalLight;
if (cm.LightColor == p->Color &&
cm.FadeColor == p->Fade &&
cm.Desaturation == p->Desaturate)
{
return p;
}
return GetSpecialLights(cm.LightColor, cm.FadeColor, cm.Desaturation);
}
#endif

View File

@ -25,9 +25,6 @@ struct FRenderer
Renderer = NULL;
}
// Can be overridden so that the colormaps for sector color/fade won't be built.
virtual bool UsesColormap() const = 0;
// precache one texture
virtual void Precache(uint8_t *texhitlist, TMap<PClassActor*, bool> &actorhitlist) = 0;

View File

@ -871,7 +871,7 @@ void R_SetupFrame (FRenderViewpoint &viewpoint, FViewWindow &viewwindow, AActor
: s->ceilingplane.PointOnSide(viewpoint.Pos) < 0
? s->topmap
: s->midmap;
if (APART(newblend) == 0 && newblend >= numfakecmaps)
if (APART(newblend) == 0 && newblend >= fakecmaps.Size())
newblend = 0;
}
}
@ -888,11 +888,9 @@ void R_SetupFrame (FRenderViewpoint &viewpoint, FViewWindow &viewwindow, AActor
BaseBlendG = GPART(newblend);
BaseBlendB = BPART(newblend);
BaseBlendA = APART(newblend) / 255.f;
NormalLight.Maps = realcolormaps.Maps;
}
else
{
NormalLight.Maps = realcolormaps.Maps + NUMCOLORMAPS*256*newblend;
BaseBlendR = BaseBlendG = BaseBlendB = 0;
BaseBlendA = 0.f;
}

View File

@ -248,7 +248,6 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, player_t *&
template<> FSerializer &Serialize(FSerializer &arc, const char *key, line_t *&value, line_t **defval);
template<> FSerializer &Serialize(FSerializer &arc, const char *key, side_t *&value, side_t **defval);
template<> FSerializer &Serialize(FSerializer &arc, const char *key, vertex_t *&value, vertex_t **defval);
template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDynamicColormap *&cm, FDynamicColormap **def);
template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClassActor *&clst, PClassActor **def);
template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClass *&clst, PClass **def);
template<> FSerializer &Serialize(FSerializer &arc, const char *key, FStrifeDialogueNode *&node, FStrifeDialogueNode **def);

View File

@ -1,3 +1,4 @@
#pragma once
/*
** st_start.h
** Interface for the startup screen.

View File

@ -691,7 +691,7 @@ namespace swrenderer
}
}
// correct colors now
FDynamicColormap *basecolormap = GetColorTable(frontsector->Colormap);
FDynamicColormap *basecolormap = nullptr;
wallshade = ds->shade;
CameraLight *cameraLight = CameraLight::Instance();
if (cameraLight->FixedLightLevel() < 0)
@ -725,6 +725,8 @@ namespace swrenderer
}
}
}
if (basecolormap == nullptr) basecolormap = GetColorTable(frontsector->Colormap);
if (rw_pic != DONT_DRAW)
{
RenderFakeWall(ds, x1, x2, fover ? fover : rover, wallshade, basecolormap);
@ -868,7 +870,7 @@ namespace swrenderer
}
}
// correct colors now
FDynamicColormap *basecolormap = GetColorTable(frontsector->Colormap);
FDynamicColormap *basecolormap = nullptr;
wallshade = ds->shade;
CameraLight *cameraLight = CameraLight::Instance();
if (cameraLight->FixedLightLevel() < 0)
@ -902,6 +904,7 @@ namespace swrenderer
}
}
}
if (basecolormap == nullptr) basecolormap = GetColorTable(frontsector->Colormap);
if (rw_pic != DONT_DRAW)
{

View File

@ -2,6 +2,7 @@
#include "r_renderthread.cpp"
#include "r_swcanvas.cpp"
#include "r_swrenderer.cpp"
#include "r_swcolormaps.cpp"
#include "drawers/r_draw.cpp"
#include "drawers/r_draw_pal.cpp"
#include "drawers/r_draw_rgba.cpp"

View File

@ -38,6 +38,7 @@
#include "v_palette.h"
#include "v_video.h"
#include "m_png.h"
#include "r_swcolormaps.h"
#include "colormatcher.h"
#include "r_swcanvas.h"
#include "textures/textures.h"

View File

@ -0,0 +1,544 @@
/*
** r_swcolormaps.cpp
** Colormap handling for the software renderer
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
**
*/
#include <stddef.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include "i_system.h"
#include "w_wad.h"
#include "doomdef.h"
#include "r_sky.h"
#include "c_dispatch.h"
#include "sc_man.h"
#include "v_text.h"
#include "st_start.h"
#include "doomstat.h"
#include "v_palette.h"
#include "colormatcher.h"
#include "r_data/colormaps.h"
#include "r_swcolormaps.h"
#include "v_video.h"
#include "templates.h"
#include "r_utility.h"
#include "r_renderer.h"
FDynamicColormap NormalLight;
FDynamicColormap FullNormalLight; //[SP] Emulate GZDoom brightness
bool NormalLightHasFixedLights;
FSWColormap realcolormaps;
FSWColormap realfbcolormaps; //[SP] For fullbright use
TArray<FSWColormap> SpecialSWColormaps;
//==========================================================================
//
// Colored Lighting Stuffs
//
//==========================================================================
FDynamicColormap *GetSpecialLights (PalEntry color, PalEntry fade, int desaturate)
{
FDynamicColormap *colormap;
// If this colormap has already been created, just return it
for (colormap = &NormalLight; colormap != NULL; colormap = colormap->Next)
{
if (color == colormap->Color &&
fade == colormap->Fade &&
desaturate == colormap->Desaturate)
{
return colormap;
}
}
// Not found. Create it.
colormap = new FDynamicColormap;
colormap->Next = NormalLight.Next;
colormap->Color = color;
colormap->Fade = fade;
colormap->Desaturate = desaturate;
NormalLight.Next = colormap;
colormap->Maps = new uint8_t[NUMCOLORMAPS*256];
colormap->BuildLights ();
return colormap;
}
//==========================================================================
//
// Free all lights created with GetSpecialLights
//
//==========================================================================
static void FreeSpecialLights()
{
FDynamicColormap *colormap, *next;
for (colormap = NormalLight.Next; colormap != NULL; colormap = next)
{
next = colormap->Next;
delete[] colormap->Maps;
delete colormap;
}
NormalLight.Next = NULL;
}
//==========================================================================
//
// Builds NUMCOLORMAPS colormaps lit with the specified color
//
//==========================================================================
void FDynamicColormap::BuildLights ()
{
int l, c;
int lr, lg, lb, ld, ild;
PalEntry colors[256], basecolors[256];
uint8_t *shade;
if (Maps == NULL)
return;
// Scale light to the range 0-256, so we can avoid
// dividing by 255 in the bottom loop.
lr = Color.r*256/255;
lg = Color.g*256/255;
lb = Color.b*256/255;
ld = Desaturate*256/255;
if (ld < 0) // No negative desaturations, please.
{
ld = -ld;
}
ild = 256-ld;
if (ld == 0)
{
memcpy (basecolors, GPalette.BaseColors, sizeof(basecolors));
}
else
{
// Desaturate the palette before lighting it.
for (c = 0; c < 256; c++)
{
int r = GPalette.BaseColors[c].r;
int g = GPalette.BaseColors[c].g;
int b = GPalette.BaseColors[c].b;
int intensity = ((r * 77 + g * 143 + b * 37) >> 8) * ld;
basecolors[c].r = (r*ild + intensity) >> 8;
basecolors[c].g = (g*ild + intensity) >> 8;
basecolors[c].b = (b*ild + intensity) >> 8;
basecolors[c].a = 0;
}
}
// build normal (but colored) light mappings
for (l = 0; l < NUMCOLORMAPS; l++)
{
DoBlending (basecolors, colors, 256,
Fade.r, Fade.g, Fade.b, l * (256 / NUMCOLORMAPS));
shade = Maps + 256*l;
if ((uint32_t)Color == MAKERGB(255,255,255))
{ // White light, so we can just pick the colors directly
for (c = 0; c < 256; c++)
{
*shade++ = ColorMatcher.Pick (colors[c].r, colors[c].g, colors[c].b);
}
}
else
{ // Colored light, so do the (slightly) slower thing
for (c = 0; c < 256; c++)
{
*shade++ = ColorMatcher.Pick (
(colors[c].r*lr)>>8,
(colors[c].g*lg)>>8,
(colors[c].b*lb)>>8);
}
}
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDynamicColormap::ChangeColor (PalEntry lightcolor, int desaturate)
{
if (lightcolor != Color || desaturate != Desaturate)
{
Color = lightcolor;
// [BB] desaturate must be in [0,255]
Desaturate = clamp(desaturate, 0, 255);
if (Maps) BuildLights ();
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDynamicColormap::ChangeFade (PalEntry fadecolor)
{
if (fadecolor != Fade)
{
Fade = fadecolor;
if (Maps) BuildLights ();
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDynamicColormap::ChangeColorFade (PalEntry lightcolor, PalEntry fadecolor)
{
if (lightcolor != Color || fadecolor != Fade)
{
Color = lightcolor;
Fade = fadecolor;
if (Maps) BuildLights ();
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDynamicColormap::RebuildAllLights()
{
FDynamicColormap *cm;
for (cm = &NormalLight; cm != NULL; cm = cm->Next)
{
if (cm->Maps == NULL)
{
cm->Maps = new uint8_t[NUMCOLORMAPS*256];
cm->BuildLights ();
}
}
}
//==========================================================================
//
// R_CheckForFixedLights
//
// Returns true if there are any entries in the colormaps that are the
// same for every colormap and not the fade color.
//
//==========================================================================
static bool R_CheckForFixedLights(const uint8_t *colormaps)
{
const uint8_t *lastcolormap = colormaps + (NUMCOLORMAPS - 1) * 256;
uint8_t freq[256];
int i, j;
// Count the frequencies of different colors in the final colormap.
// If they occur more than X amount of times, we ignore them as a
// potential fixed light.
memset(freq, 0, sizeof(freq));
for (i = 0; i < 256; ++i)
{
freq[lastcolormap[i]]++;
}
// Now check the colormaps for fixed lights that are uncommon in the
// final coloramp.
for (i = 255; i >= 0; --i)
{
uint8_t color = lastcolormap[i];
if (freq[color] > 10) // arbitrary number to decide "common" colors
{
continue;
}
// It's rare in the final colormap. See if it's the same for all colormaps.
for (j = 0; j < NUMCOLORMAPS - 1; ++j)
{
if (colormaps[j * 256 + i] != color)
break;
}
if (j == NUMCOLORMAPS - 1)
{ // It was the same all the way across.
return true;
}
}
return false;
}
//==========================================================================
//
// R_SetDefaultColormap
//
//==========================================================================
static void SetDefaultColormap (const char *name)
{
if (strnicmp (fakecmaps[0].name, name, 8) != 0)
{
int lump, i, j;
uint8_t map[256];
uint8_t unremap[256];
uint8_t remap[256];
lump = Wads.CheckNumForFullName (name, true, ns_colormaps);
if (lump == -1)
lump = Wads.CheckNumForName (name, ns_global);
// [RH] If using BUILD's palette, generate the colormap
if (lump == -1 || Wads.CheckNumForFullName("palette.dat") >= 0 || Wads.CheckNumForFullName("blood.pal") >= 0)
{
Printf ("Make colormap\n");
FDynamicColormap foo;
foo.Color = 0xFFFFFF;
foo.Fade = 0;
foo.Maps = realcolormaps.Maps;
foo.Desaturate = 0;
foo.Next = NULL;
foo.BuildLights ();
}
else
{
FWadLump lumpr = Wads.OpenLumpNum (lump);
// [RH] The colormap may not have been designed for the specific
// palette we are using, so remap it to match the current palette.
memcpy (remap, GPalette.Remap, 256);
memset (unremap, 0, 256);
for (i = 0; i < 256; ++i)
{
unremap[remap[i]] = i;
}
// Mapping to color 0 is okay, because the colormap won't be used to
// produce a masked texture.
remap[0] = 0;
for (i = 0; i < NUMCOLORMAPS; ++i)
{
uint8_t *map2 = &realcolormaps.Maps[i*256];
lumpr.Read (map, 256);
for (j = 0; j < 256; ++j)
{
map2[j] = remap[map[unremap[j]]];
}
}
}
}
}
//==========================================================================
//
// R_InitColormaps
//
//==========================================================================
static void InitBoomColormaps ()
{
// [RH] Try and convert BOOM colormaps into blending values.
// This is a really rough hack, but it's better than
// not doing anything with them at all (right?)
uint32_t NumLumps = Wads.GetNumLumps();
realcolormaps.Maps = new uint8_t[256*NUMCOLORMAPS*fakecmaps.Size()];
SetDefaultColormap ("COLORMAP");
if (fakecmaps.Size() > 1)
{
uint8_t unremap[256], remap[256], mapin[256];
int i;
unsigned j;
memcpy (remap, GPalette.Remap, 256);
memset (unremap, 0, 256);
for (i = 0; i < 256; ++i)
{
unremap[remap[i]] = i;
}
remap[0] = 0;
for (j = 1; j < fakecmaps.Size(); j++)
{
if (Wads.LumpLength (fakecmaps[j].lump) >= (NUMCOLORMAPS+1)*256)
{
int k, r;
FWadLump lump = Wads.OpenLumpNum (fakecmaps[j].lump);
uint8_t *const map = realcolormaps.Maps + NUMCOLORMAPS*256*j;
for (k = 0; k < NUMCOLORMAPS; ++k)
{
uint8_t *map2 = &map[k*256];
lump.Read (mapin, 256);
map2[0] = 0;
for (r = 1; r < 256; ++r)
{
map2[r] = remap[mapin[unremap[r]]];
}
}
}
}
}
}
//==========================================================================
//
//
//
//==========================================================================
static void DeinitSWColorMaps()
{
FreeSpecialLights();
if (realcolormaps.Maps != nullptr)
{
delete[] realcolormaps.Maps;
realcolormaps.Maps = nullptr;
}
if (realfbcolormaps.Maps)
{
delete[] realfbcolormaps.Maps;
realfbcolormaps.Maps = nullptr;
}
}
//==========================================================================
//
//
//
//==========================================================================
void InitSWColorMaps()
{
DeinitSWColorMaps();
atterm(DeinitSWColorMaps);
InitBoomColormaps();
NormalLight.Color = PalEntry (255, 255, 255);
NormalLight.Fade = 0;
NormalLight.Maps = realcolormaps.Maps;
NormalLightHasFixedLights = R_CheckForFixedLights(realcolormaps.Maps);
// [SP] Create a copy of the colormap
if (!realfbcolormaps.Maps)
{
realfbcolormaps.Maps = new uint8_t[256*NUMCOLORMAPS*fakecmaps.Size()];
memcpy(realfbcolormaps.Maps, realcolormaps.Maps, 256*NUMCOLORMAPS*fakecmaps.Size());
}
FullNormalLight.Color = PalEntry(255, 255, 255);
FullNormalLight.Fade = 0;
FullNormalLight.Maps = realfbcolormaps.Maps;
SpecialSWColormaps.Resize(SpecialColormaps.Size());
for(unsigned i = 0; i < SpecialColormaps.Size(); i++)
{
SpecialSWColormaps[i].Maps = SpecialColormaps[i].Colormap;
}
}
//==========================================================================
//
//
//
//==========================================================================
CCMD (testfade)
{
FString colorstring;
uint32_t color;
if (argv.argc() < 2)
{
Printf ("testfade <color>\n");
}
else
{
if ( !(colorstring = V_GetColorStringByName (argv[1])).IsEmpty() )
{
color = V_GetColorFromString (NULL, colorstring);
}
else
{
color = V_GetColorFromString (NULL, argv[1]);
}
level.fadeto = color;
NormalLight.ChangeFade (color);
}
}
//==========================================================================
//
//
//
//==========================================================================
CCMD (testcolor)
{
FString colorstring;
uint32_t color;
int desaturate;
if (argv.argc() < 2)
{
Printf ("testcolor <color> [desaturation]\n");
}
else
{
if ( !(colorstring = V_GetColorStringByName (argv[1])).IsEmpty() )
{
color = V_GetColorFromString (NULL, colorstring);
}
else
{
color = V_GetColorFromString (NULL, argv[1]);
}
if (argv.argc() > 2)
{
desaturate = atoi (argv[2]);
}
else
{
desaturate = NormalLight.Desaturate;
}
NormalLight.ChangeColor (color, desaturate);
}
}

View File

@ -0,0 +1,60 @@
#pragma once
struct FSWColormap
{
uint8_t *Maps = nullptr;
PalEntry Color = 0xffffffff;
PalEntry Fade = 0xff000000;
int Desaturate = 0;
};
struct FDynamicColormap : FSWColormap
{
void ChangeFade (PalEntry fadecolor);
void ChangeColor (PalEntry lightcolor, int desaturate);
void ChangeColorFade (PalEntry lightcolor, PalEntry fadecolor);
void ChangeFogDensity(int newdensity);
void BuildLights ();
static void RebuildAllLights();
FDynamicColormap *Next;
};
extern FSWColormap realcolormaps; // [RH] make the colormaps externally visible
extern FDynamicColormap NormalLight;
extern FDynamicColormap FullNormalLight;
extern bool NormalLightHasFixedLights;
extern TArray<FSWColormap> SpecialSWColormaps;
void InitSWColorMaps();
FDynamicColormap *GetSpecialLights (PalEntry lightcolor, PalEntry fadecolor, int desaturate);
void SetDefaultColormap (const char *name);
// Give the compiler a strong hint we want these functions inlined:
#ifndef FORCEINLINE
#if defined(_MSC_VER)
#define FORCEINLINE __forceinline
#elif defined(__GNUC__)
#define FORCEINLINE __attribute__((always_inline)) inline
#else
#define FORCEINLINE inline
#endif
#endif
// MSVC needs the forceinline here.
FORCEINLINE FDynamicColormap *GetColorTable(const FColormap &cm)
{
auto p = &NormalLight;
if (cm.LightColor == p->Color &&
cm.FadeColor == p->Fade &&
cm.Desaturation == p->Desaturate)
{
return p;
}
return GetSpecialLights(cm.LightColor, cm.FadeColor, cm.Desaturation);
}

View File

@ -49,6 +49,7 @@
#include "drawers/r_draw_rgba.h"
#include "polyrenderer/poly_renderer.h"
#include "p_setup.h"
#include "g_levellocals.h"
void gl_InitData();
void gl_PreprocessLevel();
@ -84,11 +85,7 @@ FSoftwareRenderer::~FSoftwareRenderer()
void FSoftwareRenderer::Init()
{
mScene.Init();
}
bool FSoftwareRenderer::UsesColormap() const
{
return true;
InitSWColorMaps();
}
void FSoftwareRenderer::PrecacheTexture(FTexture *tex, int cache)
@ -356,6 +353,19 @@ void FSoftwareRenderer::RenderTextureView (FCanvasTexture *tex, AActor *viewpoin
void FSoftwareRenderer::PreprocessLevel()
{
gl_PreprocessLevel();
// This just sets the default colormap for the spftware renderer.
NormalLight.Maps = realcolormaps.Maps;
NormalLight.ChangeColor(PalEntry(255, 255, 255), 0);
NormalLight.ChangeFade(level.fadeto);
if (level.fadeto == 0)
{
SetDefaultColormap(level.info->FadeTable);
if (level.flags & LEVEL_HASFADETABLE)
{
level.fadeto = 0xff939393; //[SP] Hexen True-color compatibility, just use gray.
}
}
}
void FSoftwareRenderer::CleanLevelData()
@ -372,3 +382,4 @@ void FSoftwareRenderer::SetVisibility(double vis)
{
mScene.MainThread()->Light->SetVisibility(mScene.MainThread()->Viewport.get(), vis);
}

View File

@ -9,9 +9,6 @@ struct FSoftwareRenderer : public FRenderer
FSoftwareRenderer();
~FSoftwareRenderer();
// Can be overridden so that the colormaps for sector color/fade won't be built.
bool UsesColormap() const override;
// precache textures
void Precache(uint8_t *texhitlist, TMap<PClassActor*, bool> &actorhitlist) override;

View File

@ -70,7 +70,7 @@ namespace swrenderer
}
else
{
fixedcolormap = &SpecialColormaps[player->fixedcolormap];
fixedcolormap = &SpecialSWColormaps[player->fixedcolormap];
}
}
else if (player->fixedlightlevel >= 0 && player->fixedlightlevel < NUMCOLORMAPS)
@ -86,7 +86,7 @@ namespace swrenderer
// [RH] Inverse light for shooting the Sigil
if (fixedcolormap == nullptr && viewport->viewpoint.extralight == INT_MIN)
{
fixedcolormap = &SpecialColormaps[INVERSECOLORMAP];
fixedcolormap = &SpecialSWColormaps[INVERSECOLORMAP];
viewport->viewpoint.extralight = 0;
}
}

View File

@ -122,6 +122,10 @@ namespace swrenderer
WallScanCycles.Reset();
R_SetupFrame(MainThread()->Viewport->viewpoint, MainThread()->Viewport->viewwindow, actor);
if (APART(R_OldBlend)) NormalLight.Maps = realcolormaps.Maps;
else NormalLight.Maps = realcolormaps.Maps + NUMCOLORMAPS * 256 * R_OldBlend;
CameraLight::Instance()->SetCamera(MainThread()->Viewport.get(), actor);
MainThread()->Viewport->SetupFreelook();

View File

@ -465,15 +465,15 @@ namespace swrenderer
if (visstyle.Invert)
{
vis.Light.BaseColormap = &SpecialColormaps[INVERSECOLORMAP];
vis.Light.BaseColormap = &SpecialSWColormaps[INVERSECOLORMAP];
vis.Light.ColormapNum = 0;
noaccel = true;
}
}
// If we're drawing with a special colormap, but shaders for them are disabled, do
// not accelerate.
if (!r_shadercolormaps && (vis.Light.BaseColormap >= &SpecialColormaps[0] &&
vis.Light.BaseColormap <= &SpecialColormaps.Last()))
if (!r_shadercolormaps && (vis.Light.BaseColormap >= &SpecialSWColormaps[0] &&
vis.Light.BaseColormap <= &SpecialSWColormaps.Last()))
{
noaccel = true;
}
@ -524,10 +524,10 @@ namespace swrenderer
accelSprite.x1 = x1;
accelSprite.flip = vis.xiscale < 0;
if (vis.Light.BaseColormap >= &SpecialColormaps[0] &&
vis.Light.BaseColormap < &SpecialColormaps[SpecialColormaps.Size()])
if (vis.Light.BaseColormap >= &SpecialSWColormaps[0] &&
vis.Light.BaseColormap < &SpecialSWColormaps[SpecialColormaps.Size()])
{
accelSprite.special = static_cast<FSpecialColormap*>(vis.Light.BaseColormap);
accelSprite.special = &SpecialColormaps[vis.Light.BaseColormap - &SpecialSWColormaps[0]];
}
else if (CameraLight::Instance()->ShaderColormap())
{

View File

@ -19,6 +19,7 @@ namespace swrenderer
void DrawerArgs::SetLight(FSWColormap *base_colormap, float light, int shade)
{
mBaseColormap = base_colormap;
assert(mBaseColormap->Maps != nullptr);
mLight = light;
mShade = shade;
}

View File

@ -538,30 +538,6 @@ CCMD (testblend)
}
}
CCMD (testfade)
{
FString colorstring;
uint32_t color;
if (argv.argc() < 2)
{
Printf ("testfade <color>\n");
}
else
{
if ( !(colorstring = V_GetColorStringByName (argv[1])).IsEmpty() )
{
color = V_GetColorFromString (NULL, colorstring);
}
else
{
color = V_GetColorFromString (NULL, argv[1]);
}
level.fadeto = color;
NormalLight.ChangeFade (color);
}
}
/****** Colorspace Conversion Functions ******/
// Code from http://www.cs.rit.edu/~yxv4997/t_convert.html
@ -637,34 +613,3 @@ void HSVtoRGB (float *r, float *g, float *b, float h, float s, float v)
}
}
CCMD (testcolor)
{
FString colorstring;
uint32_t color;
int desaturate;
if (argv.argc() < 2)
{
Printf ("testcolor <color> [desaturation]\n");
}
else
{
if ( !(colorstring = V_GetColorStringByName (argv[1])).IsEmpty() )
{
color = V_GetColorFromString (NULL, colorstring);
}
else
{
color = V_GetColorFromString (NULL, argv[1]);
}
if (argv.argc() > 2)
{
desaturate = atoi (argv[2]);
}
else
{
desaturate = NormalLight.Desaturate;
}
NormalLight.ChangeColor (color, desaturate);
}
}