- moved the 2D drawing code to its own directory under 'rendering'.

This commit is contained in:
Christoph Oelckers 2019-02-23 18:08:57 +01:00
parent ef3e5ef01e
commit c3890342e6
8 changed files with 8 additions and 5 deletions

394
src/rendering/2D/f_wipe.cpp Normal file
View file

@ -0,0 +1,394 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
// Mission begin melt/wipe screen special effect.
//
//-----------------------------------------------------------------------------
#include "v_video.h"
#include "m_random.h"
#include "f_wipe.h"
#include "templates.h"
#include "textures/bitmap.h"
#include "hwrenderer/textures/hw_material.h"
class FBurnTexture : public FTexture
{
TArray<uint32_t> WorkBuffer;
public:
FBurnTexture(int w, int h)
: WorkBuffer(w*h, true)
{
Width = w;
Height = h;
}
FBitmap GetBgraBitmap(PalEntry*, int *trans) override
{
FBitmap bmp;
bmp.Create(Width, Height);
bmp.CopyPixelDataRGB(0, 0, (uint8_t*)WorkBuffer.Data(), Width, Height, 4, Width*4, 0, CF_RGBA, nullptr);
if (trans) *trans = 0;
return bmp;
}
uint32_t *GetBuffer()
{
return WorkBuffer.Data();
}
};
int wipe_CalcBurn (uint8_t *burnarray, int width, int height, int density)
{
// This is a modified version of the fire that was once used
// on the player setup menu.
static int voop;
int a, b;
uint8_t *from;
// generator
from = &burnarray[width * height];
b = voop;
voop += density / 3;
for (a = 0; a < density/8; a++)
{
unsigned int offs = (a+b) & (width - 1);
unsigned int v = M_Random();
v = MIN(from[offs] + 4 + (v & 15) + (v >> 3) + (M_Random() & 31), 255u);
from[offs] = from[width*2 + ((offs + width*3/2) & (width - 1))] = v;
}
density = MIN(density + 10, width * 7);
from = burnarray;
for (b = 0; b <= height; b += 2)
{
uint8_t *pixel = from;
// special case: first pixel on line
uint8_t *p = pixel + (width << 1);
unsigned int top = *p + *(p + width - 1) + *(p + 1);
unsigned int bottom = *(pixel + (width << 2));
unsigned int c1 = (top + bottom) >> 2;
if (c1 > 1) c1--;
*pixel = c1;
*(pixel + width) = (c1 + bottom) >> 1;
pixel++;
// main line loop
for (a = 1; a < width-1; a++)
{
// sum top pixels
p = pixel + (width << 1);
top = *p + *(p - 1) + *(p + 1);
// bottom pixel
bottom = *(pixel + (width << 2));
// combine pixels
c1 = (top + bottom) >> 2;
if (c1 > 1) c1--;
// store pixels
*pixel = c1;
*(pixel + width) = (c1 + bottom) >> 1; // interpolate
// next pixel
pixel++;
}
// special case: last pixel on line
p = pixel + (width << 1);
top = *p + *(p - 1) + *(p - width + 1);
bottom = *(pixel + (width << 2));
c1 = (top + bottom) >> 2;
if (c1 > 1) c1--;
*pixel = c1;
*(pixel + width) = (c1 + bottom) >> 1;
// next line
from += width << 1;
}
// Check for done-ness. (Every pixel with level 126 or higher counts as done.)
for (a = width * height, from = burnarray; a != 0; --a, ++from)
{
if (*from < 126)
{
return density;
}
}
return -1;
}
// TYPES -------------------------------------------------------------------
class Wiper_Crossfade : public Wiper
{
public:
bool Run(int ticks) override;
private:
int Clock = 0;
};
class Wiper_Melt : public Wiper
{
public:
Wiper_Melt();
bool Run(int ticks) override;
private:
static const int WIDTH = 320, HEIGHT = 200;
int y[WIDTH];
};
class Wiper_Burn : public Wiper
{
public:
~Wiper_Burn();
bool Run(int ticks) override;
void SetTextures(FTexture *startscreen, FTexture *endscreen) override;
private:
static const int WIDTH = 64, HEIGHT = 64;
uint8_t BurnArray[WIDTH * (HEIGHT + 5)] = {0};
FBurnTexture *BurnTexture = nullptr;
int Density = 4;
int BurnTime = 8;
};
//===========================================================================
//
// Screen wipes
//
//===========================================================================
Wiper *Wiper::Create(int type)
{
switch(type)
{
case wipe_Burn:
return new Wiper_Burn;
case wipe_Fade:
return new Wiper_Crossfade;
case wipe_Melt:
return new Wiper_Melt;
default:
return nullptr;
}
}
//==========================================================================
//
// OpenGLFrameBuffer :: WipeCleanup
//
// Release any resources that were specifically created for the wipe.
//
//==========================================================================
Wiper::~Wiper()
{
if (startScreen != nullptr) delete startScreen;
if (endScreen != nullptr) delete endScreen;
}
//==========================================================================
//
// WIPE: CROSSFADE ---------------------------------------------------------
//
//==========================================================================
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Crossfade :: Run
//
// Fades the old screen into the new one over 32 ticks.
//
//==========================================================================
bool Wiper_Crossfade::Run(int ticks)
{
Clock += ticks;
screen->DrawTexture(startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
screen->DrawTexture(endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, DTA_Alpha, clamp(Clock / 32.f, 0.f, 1.f), TAG_DONE);
return Clock >= 32;
}
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Melt Constructor
//
//==========================================================================
Wiper_Melt::Wiper_Melt()
{
int i, r;
// setup initial column positions
// (y<0 => not ready to scroll yet)
y[0] = -(M_Random() & 15);
for (i = 1; i < WIDTH; ++i)
{
r = (M_Random()%3) - 1;
y[i] = clamp(y[i-1] + r, -15, 0);
}
}
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Melt :: Run
//
// Melts the old screen into the new one over 32 ticks.
//
//==========================================================================
bool Wiper_Melt::Run(int ticks)
{
bool done;
screen->DrawTexture(endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
// Copy the old screen in vertical strips on top of the new one.
while (ticks--)
{
done = true;
for (int i = 0; i < WIDTH; i++)
{
if (y[i] < 0)
{
y[i]++;
done = false;
}
else if (y[i] < HEIGHT)
{
int dy = (y[i] < 16) ? y[i] + 1 : 8;
y[i] = MIN(y[i] + dy, HEIGHT);
done = false;
}
if (ticks == 0)
{
struct {
int32_t x;
int32_t y;
} dpt;
struct {
int32_t left;
int32_t top;
int32_t right;
int32_t bottom;
} rect;
// Only draw for the final tick.
// No need for optimization. Wipes won't ever be drawn with anything else.
int w = startScreen->GetDisplayWidth();
int h = startScreen->GetDisplayHeight();
dpt.x = i * w / WIDTH;
dpt.y = MAX(0, y[i] * h / HEIGHT);
rect.left = dpt.x;
rect.top = 0;
rect.right = (i + 1) * w / WIDTH;
rect.bottom = h - dpt.y;
if (rect.bottom > rect.top)
{
screen->DrawTexture(startScreen, 0, dpt.y, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_ClipLeft, rect.left, DTA_ClipRight, rect.right, DTA_Masked, false, TAG_DONE);
}
}
}
}
return done;
}
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Burn Constructor
//
//==========================================================================
void Wiper_Burn::SetTextures(FTexture *startscreen, FTexture *endscreen)
{
startScreen = startscreen;
endScreen = endscreen;
BurnTexture = new FBurnTexture(WIDTH, HEIGHT);
auto mat = FMaterial::ValidateTexture(startscreen, false);
mat->AddTextureLayer(BurnTexture);
}
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Burn Destructor
//
//==========================================================================
Wiper_Burn::~Wiper_Burn()
{
if (BurnTexture != nullptr) delete BurnTexture;
}
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Burn :: Run
//
//==========================================================================
bool Wiper_Burn::Run(int ticks)
{
bool done;
BurnTime += ticks;
ticks *= 2;
// Make the fire burn
done = false;
while (!done && ticks--)
{
Density = wipe_CalcBurn(BurnArray, WIDTH, HEIGHT, Density);
done = (Density < 0);
}
BurnTexture->SystemTextures.Clean(true, true);
const uint8_t *src = BurnArray;
uint32_t *dest = (uint32_t *)BurnTexture->GetBuffer();
for (int y = HEIGHT; y != 0; --y)
{
for (int x = WIDTH; x != 0; --x)
{
uint8_t s = clamp<int>((*src++)*2, 0, 255);
*dest++ = MAKEARGB(s,255,255,255);
}
}
screen->DrawTexture(startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
screen->DrawTexture(endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Burn, true, DTA_Masked, false, TAG_DONE);
// The fire may not always stabilize, so the wipe is forced to end
// after an arbitrary maximum time.
return done || (BurnTime > 40);
}

60
src/rendering/2D/f_wipe.h Normal file
View file

@ -0,0 +1,60 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
// Mission start screen wipe/melt, special effects.
//
//-----------------------------------------------------------------------------
#ifndef __F_WIPE_H__
#define __F_WIPE_H__
#include "stdint.h"
class FTexture;
int wipe_CalcBurn(uint8_t *buffer, int width, int height, int density);
enum
{
wipe_None, // don't bother
wipe_Melt, // weird screen melt
wipe_Burn, // fade in shape of fire
wipe_Fade, // crossfade from old to new
wipe_NUMWIPES
};
class Wiper
{
protected:
FTexture *startScreen = nullptr, *endScreen = nullptr;
public:
virtual ~Wiper();
virtual bool Run(int ticks) = 0;
virtual void SetTextures(FTexture *startscreen, FTexture *endscreen)
{
startScreen = startscreen;
endScreen = endscreen;
}
static Wiper *Create(int type);
};
#endif

View file

@ -0,0 +1,677 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2016-2018 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/
//
//--------------------------------------------------------------------------
//
/*
** v_2ddrawer.h
** Device independent 2D draw list
**
**/
#include <stdarg.h>
#include "doomtype.h"
#include "templates.h"
#include "r_utility.h"
#include "v_video.h"
#include "g_levellocals.h"
#include "vm.h"
EXTERN_CVAR(Float, transsouls)
IMPLEMENT_CLASS(DShape2D, false, false)
DEFINE_ACTION_FUNCTION(DShape2D, Clear)
{
PARAM_SELF_PROLOGUE(DShape2D);
PARAM_INT(which);
if ( which&C_Verts ) self->mVertices.Clear();
if ( which&C_Coords ) self->mCoords.Clear();
if ( which&C_Indices ) self->mIndices.Clear();
return 0;
}
DEFINE_ACTION_FUNCTION(DShape2D, PushVertex)
{
PARAM_SELF_PROLOGUE(DShape2D);
PARAM_FLOAT(x);
PARAM_FLOAT(y);
self->mVertices.Push(DVector2(x,y));
return 0;
}
DEFINE_ACTION_FUNCTION(DShape2D, PushCoord)
{
PARAM_SELF_PROLOGUE(DShape2D);
PARAM_FLOAT(u);
PARAM_FLOAT(v);
self->mCoords.Push(DVector2(u,v));
return 0;
}
DEFINE_ACTION_FUNCTION(DShape2D, PushTriangle)
{
PARAM_SELF_PROLOGUE(DShape2D);
PARAM_INT(a);
PARAM_INT(b);
PARAM_INT(c);
self->mIndices.Push(a);
self->mIndices.Push(b);
self->mIndices.Push(c);
return 0;
}
//==========================================================================
//
//
//
//==========================================================================
int F2DDrawer::AddCommand(const RenderCommand *data)
{
if (mData.Size() > 0 && data->isCompatible(mData.Last()))
{
// Merge with the last command.
mData.Last().mIndexCount += data->mIndexCount;
mData.Last().mVertCount += data->mVertCount;
return mData.Size();
}
else
{
return mData.Push(*data);
}
}
//==========================================================================
//
//
//
//==========================================================================
void F2DDrawer::AddIndices(int firstvert, int count, ...)
{
va_list ap;
va_start(ap, count);
int addr = mIndices.Reserve(count);
for (int i = 0; i < count; i++)
{
mIndices[addr + i] = firstvert + va_arg(ap, int);
}
}
//==========================================================================
//
// SetStyle
//
// Patterned after R_SetPatchStyle.
//
//==========================================================================
bool F2DDrawer::SetStyle(FTexture *tex, DrawParms &parms, PalEntry &vertexcolor, RenderCommand &quad)
{
FRenderStyle style = parms.style;
float alpha;
bool stencilling;
if (style.Flags & STYLEF_TransSoulsAlpha)
{
alpha = transsouls;
}
else if (style.Flags & STYLEF_Alpha1)
{
alpha = 1;
}
else
{
alpha = clamp(parms.Alpha, 0.f, 1.f);
}
style.CheckFuzz();
if (style.BlendOp == STYLEOP_Shadow || style.BlendOp == STYLEOP_Fuzz)
{
style = LegacyRenderStyles[STYLE_TranslucentStencil];
alpha = 0.3f;
parms.fillcolor = 0;
}
else if (style.BlendOp == STYLEOP_FuzzOrAdd)
{
style.BlendOp = STYLEOP_Add;
}
else if (style.BlendOp == STYLEOP_FuzzOrSub)
{
style.BlendOp = STYLEOP_Sub;
}
else if (style.BlendOp == STYLEOP_FuzzOrRevSub)
{
style.BlendOp = STYLEOP_RevSub;
}
stencilling = false;
if (style.Flags & STYLEF_InvertOverlay)
{
// Only the overlay color is inverted, not the overlay alpha.
parms.colorOverlay.r = 255 - parms.colorOverlay.r;
parms.colorOverlay.g = 255 - parms.colorOverlay.g;
parms.colorOverlay.b = 255 - parms.colorOverlay.b;
}
SetColorOverlay(parms.colorOverlay, alpha, vertexcolor, quad.mColor1);
if (style.Flags & STYLEF_ColorIsFixed)
{
if (style.Flags & STYLEF_InvertSource)
{ // Since the source color is a constant, we can invert it now
// without spending time doing it in the shader.
parms.fillcolor.r = 255 - parms.fillcolor.r;
parms.fillcolor.g = 255 - parms.fillcolor.g;
parms.fillcolor.b = 255 - parms.fillcolor.b;
style.Flags &= ~STYLEF_InvertSource;
}
if (parms.desaturate > 0)
{
// Desaturation can also be computed here without having to do it in the shader.
auto gray = parms.fillcolor.Luminance();
auto notgray = 255 - gray;
parms.fillcolor.r = uint8_t((parms.fillcolor.r * notgray + gray * 255) / 255);
parms.fillcolor.g = uint8_t((parms.fillcolor.g * notgray + gray * 255) / 255);
parms.fillcolor.b = uint8_t((parms.fillcolor.b * notgray + gray * 255) / 255);
parms.desaturate = 0;
}
// Set up the color mod to replace the color from the image data.
vertexcolor.r = parms.fillcolor.r;
vertexcolor.g = parms.fillcolor.g;
vertexcolor.b = parms.fillcolor.b;
if (style.Flags & STYLEF_RedIsAlpha)
{
quad.mDrawMode = TM_ALPHATEXTURE;
}
else
{
quad.mDrawMode = TM_STENCIL;
}
}
else
{
if (style.Flags & STYLEF_RedIsAlpha)
{
quad.mDrawMode = TM_ALPHATEXTURE;
}
else if (style.Flags & STYLEF_InvertSource)
{
quad.mDrawMode = TM_INVERSE;
}
if (parms.specialcolormap != nullptr)
{ // draw with an invulnerability or similar colormap.
auto scm = parms.specialcolormap;
quad.mSpecialColormap[0] = PalEntry(255, int(scm->ColorizeStart[0] * 127.5f), int(scm->ColorizeStart[1] * 127.5f), int(scm->ColorizeStart[2] * 127.5f));
quad.mSpecialColormap[1] = PalEntry(255, int(scm->ColorizeEnd[0] * 127.5f), int(scm->ColorizeEnd[1] * 127.5f), int(scm->ColorizeEnd[2] * 127.5f));
quad.mColor1 = 0; // this disables the color overlay.
}
quad.mDesaturate = parms.desaturate;
}
// apply the element's own color. This is being blended with anything that came before.
vertexcolor = PalEntry((vertexcolor.a * parms.color.a) / 255, (vertexcolor.r * parms.color.r) / 255, (vertexcolor.g * parms.color.g) / 255, (vertexcolor.b * parms.color.b) / 255);
if (!parms.masked)
{
// For TM_ALPHATEXTURE and TM_STENCIL the mask cannot be turned off because it would not yield a usable result.
if (quad.mDrawMode == TM_NORMAL) quad.mDrawMode = TM_OPAQUE;
else if (quad.mDrawMode == TM_INVERSE) quad.mDrawMode = TM_INVERTOPAQUE;
}
quad.mRenderStyle = parms.style; // this contains the blend mode and blend equation settings.
if (parms.burn) quad.mFlags |= DTF_Burn;
return true;
}
//==========================================================================
//
// Draws a texture
//
//==========================================================================
void F2DDrawer::SetColorOverlay(PalEntry color, float alpha, PalEntry &vertexcolor, PalEntry &overlaycolor)
{
if (color.a != 0 && (color & 0xffffff) != 0)
{
// overlay color uses premultiplied alpha.
int a = color.a * 256 / 255;
overlaycolor.r = (color.r * a) >> 8;
overlaycolor.g = (color.g * a) >> 8;
overlaycolor.b = (color.b * a) >> 8;
overlaycolor.a = 0; // The overlay gets added on top of the texture data so to preserve the pixel's alpha this must be 0.
}
else
{
overlaycolor = 0;
}
// Vertex intensity is the inverse of the overlay so that the shader can do a simple addition to combine them.
uint8_t light = 255 - color.a;
vertexcolor = PalEntry(int(alpha * 255), light, light, light);
// The real color gets multiplied into vertexcolor later.
}
//==========================================================================
//
// Draws a texture
//
//==========================================================================
void F2DDrawer::AddTexture(FTexture *img, DrawParms &parms)
{
if (parms.style.BlendOp == STYLEOP_None) return; // not supposed to be drawn.
double xscale = parms.destwidth / parms.texwidth;
double yscale = parms.destheight / parms.texheight;
double x = parms.x - parms.left * xscale;
double y = parms.y - parms.top * yscale;
double w = parms.destwidth;
double h = parms.destheight;
double u1, v1, u2, v2;
PalEntry vertexcolor;
RenderCommand dg;
dg.mType = DrawTypeTriangles;
dg.mVertCount = 4;
dg.mTexture = img;
if (img->isWarped()) dg.mFlags |= DTF_Wrap;
dg.mTranslation = 0;
SetStyle(img, parms, vertexcolor, dg);
if (!img->isHardwareCanvas() && parms.remap != nullptr && !parms.remap->Inactive)
{
dg.mTranslation = parms.remap;
}
u1 = parms.srcx;
v1 = parms.srcy;
u2 = parms.srcx + parms.srcwidth;
v2 = parms.srcy + parms.srcheight;
if (parms.flipX)
std::swap(u1, u2);
if (parms.flipY)
std::swap(v1, v2);
// This is crap. Only kept for backwards compatibility with scripts that may have used it.
// Note that this only works for unflipped full textures.
if (parms.windowleft > 0 || parms.windowright < parms.texwidth)
{
double wi = MIN(parms.windowright, parms.texwidth);
x += parms.windowleft * xscale;
w -= (parms.texwidth - wi + parms.windowleft) * xscale;
u1 = float(u1 + parms.windowleft / parms.texwidth);
u2 = float(u2 - (parms.texwidth - wi) / parms.texwidth);
}
if (x < (double)parms.lclip || y < (double)parms.uclip || x + w >(double)parms.rclip || y + h >(double)parms.dclip)
{
dg.mScissor[0] = parms.lclip;
dg.mScissor[1] = parms.uclip;
dg.mScissor[2] = parms.rclip;
dg.mScissor[3] = parms.dclip;
dg.mFlags |= DTF_Scissor;
}
else
{
memset(dg.mScissor, 0, sizeof(dg.mScissor));
}
dg.mVertCount = 4;
dg.mVertIndex = (int)mVertices.Reserve(4);
TwoDVertex *ptr = &mVertices[dg.mVertIndex];
ptr->Set(x, y, 0, u1, v1, vertexcolor); ptr++;
ptr->Set(x, y + h, 0, u1, v2, vertexcolor); ptr++;
ptr->Set(x + w, y, 0, u2, v1, vertexcolor); ptr++;
ptr->Set(x + w, y + h, 0, u2, v2, vertexcolor); ptr++;
dg.mIndexIndex = mIndices.Size();
dg.mIndexCount += 6;
AddIndices(dg.mVertIndex, 6, 0, 1, 2, 1, 3, 2);
AddCommand(&dg);
}
//==========================================================================
//
//
//
//==========================================================================
void F2DDrawer::AddShape( FTexture *img, DShape2D *shape, DrawParms &parms )
{
if (parms.style.BlendOp == STYLEOP_None) return; // not supposed to be drawn.
PalEntry vertexcolor;
RenderCommand dg;
dg.mType = DrawTypeTriangles;
dg.mVertCount = shape->mVertices.Size();
dg.mFlags |= DTF_Wrap;
dg.mTexture = img;
dg.mTranslation = 0;
SetStyle(img, parms, vertexcolor, dg);
if (!img->isHardwareCanvas() && parms.remap != nullptr && !parms.remap->Inactive)
dg.mTranslation = parms.remap;
double minx = 16383, miny = 16383, maxx = -16384, maxy = -16384;
for ( int i=0; i<dg.mVertCount; i++ )
{
if ( shape->mVertices[i].X < minx ) minx = shape->mVertices[i].X;
if ( shape->mVertices[i].Y < miny ) miny = shape->mVertices[i].Y;
if ( shape->mVertices[i].X > maxx ) maxx = shape->mVertices[i].X;
if ( shape->mVertices[i].Y > maxy ) maxy = shape->mVertices[i].Y;
}
if (minx < (double)parms.lclip || miny < (double)parms.uclip || maxx >(double)parms.rclip || maxy >(double)parms.dclip)
{
dg.mScissor[0] = parms.lclip;
dg.mScissor[1] = parms.uclip;
dg.mScissor[2] = parms.rclip;
dg.mScissor[3] = parms.dclip;
dg.mFlags |= DTF_Scissor;
}
else
memset(dg.mScissor, 0, sizeof(dg.mScissor));
dg.mVertIndex = (int)mVertices.Reserve(dg.mVertCount);
TwoDVertex *ptr = &mVertices[dg.mVertIndex];
for ( int i=0; i<dg.mVertCount; i++ )
ptr[i].Set(shape->mVertices[i].X, shape->mVertices[i].Y, 0, shape->mCoords[i].X, shape->mCoords[i].Y, vertexcolor);
dg.mIndexIndex = mIndices.Size();
dg.mIndexCount += shape->mIndices.Size();
for ( int i=0; i<int(shape->mIndices.Size()); i+=3 )
AddIndices(dg.mVertIndex, 3, shape->mIndices[i], shape->mIndices[i+1], shape->mIndices[i+2]);
AddCommand(&dg);
}
//==========================================================================
//
//
//
//==========================================================================
void F2DDrawer::AddPoly(FTexture *texture, FVector2 *points, int npoints,
double originx, double originy, double scalex, double scaley,
DAngle rotation, const FColormap &colormap, PalEntry flatcolor, int lightlevel,
uint32_t *indices, size_t indexcount)
{
// Use an equation similar to player sprites to determine shade
// Convert a light level into an unbounded colormap index (shade).
// Why the +12? I wish I knew, but experimentation indicates it
// is necessary in order to best reproduce Doom's original lighting.
double fadelevel;
if (vid_rendermode != 4 || primaryLevel->lightMode == ELightMode::Doom || primaryLevel->lightMode == ELightMode::ZDoomSoftware || primaryLevel->lightMode == ELightMode::DoomSoftware)
{
double map = (NUMCOLORMAPS * 2.) - ((lightlevel + 12) * (NUMCOLORMAPS / 128.));
fadelevel = clamp((map - 12) / NUMCOLORMAPS, 0.0, 1.0);
}
else
{
// The hardware renderer's light modes 0, 1 and 4 use a linear light scale which must be used here as well. Otherwise the automap gets too dark.
fadelevel = 1. - clamp(lightlevel, 0, 255) / 255.f;
}
RenderCommand poly;
poly.mType = DrawTypeTriangles;
poly.mTexture = texture;
poly.mRenderStyle = DefaultRenderStyle();
poly.mFlags |= DTF_Wrap;
poly.mDesaturate = colormap.Desaturation;
PalEntry color0;
double invfade = 1. - fadelevel;
color0.r = uint8_t(colormap.LightColor.r * invfade);
color0.g = uint8_t(colormap.LightColor.g * invfade);
color0.b = uint8_t(colormap.LightColor.b * invfade);
color0.a = 255;
poly.mColor1.a = 0;
poly.mColor1.r = uint8_t(colormap.FadeColor.r * fadelevel);
poly.mColor1.g = uint8_t(colormap.FadeColor.g * fadelevel);
poly.mColor1.b = uint8_t(colormap.FadeColor.b * fadelevel);
bool dorotate = rotation != 0;
float cosrot = (float)cos(rotation.Radians());
float sinrot = (float)sin(rotation.Radians());
float uscale = float(1.f / (texture->GetDisplayWidth() * scalex));
float vscale = float(1.f / (texture->GetDisplayHeight() * scaley));
float ox = float(originx);
float oy = float(originy);
poly.mVertCount = npoints;
poly.mVertIndex = (int)mVertices.Reserve(npoints);
for (int i = 0; i < npoints; ++i)
{
float u = points[i].X - 0.5f - ox;
float v = points[i].Y - 0.5f - oy;
if (dorotate)
{
float t = u;
u = t * cosrot - v * sinrot;
v = v * cosrot + t * sinrot;
}
mVertices[poly.mVertIndex+i].Set(points[i].X, points[i].Y, 0, u*uscale, v*vscale, color0);
}
poly.mIndexIndex = mIndices.Size();
poly.mIndexCount += (npoints - 2) * 3;
if (indices == nullptr || indexcount == 0)
{
for (int i = 2; i < npoints; ++i)
{
AddIndices(poly.mVertIndex, 3, 0, i - 1, i);
}
}
else
{
int addr = mIndices.Reserve(indexcount);
for (size_t i = 0; i < indexcount; i++)
{
mIndices[addr + i] = addr + indices[i];
}
}
AddCommand(&poly);
}
//==========================================================================
//
//
//
//==========================================================================
void F2DDrawer::AddFlatFill(int left, int top, int right, int bottom, FTexture *src, bool local_origin)
{
float fU1, fU2, fV1, fV2;
RenderCommand dg;
dg.mType = DrawTypeTriangles;
dg.mRenderStyle = DefaultRenderStyle();
dg.mTexture = src;
dg.mVertCount = 4;
dg.mTexture = src;
dg.mFlags = DTF_Wrap;
// scaling is not used here.
if (!local_origin)
{
fU1 = float(left) / src->GetDisplayWidth();
fV1 = float(top) / src->GetDisplayHeight();
fU2 = float(right) / src->GetDisplayWidth();
fV2 = float(bottom) / src->GetDisplayHeight();
}
else
{
fU1 = 0;
fV1 = 0;
fU2 = float(right - left) / src->GetDisplayWidth();
fV2 = float(bottom - top) / src->GetDisplayHeight();
}
dg.mVertIndex = (int)mVertices.Reserve(4);
auto ptr = &mVertices[dg.mVertIndex];
ptr->Set(left, top, 0, fU1, fV1, 0xffffffff); ptr++;
ptr->Set(left, bottom, 0, fU1, fV2, 0xffffffff); ptr++;
ptr->Set(right, top, 0, fU2, fV1, 0xffffffff); ptr++;
ptr->Set(right, bottom, 0, fU2, fV2, 0xffffffff); ptr++;
dg.mIndexIndex = mIndices.Size();
dg.mIndexCount += 6;
AddIndices(dg.mVertIndex, 6, 0, 1, 2, 1, 3, 2);
AddCommand(&dg);
}
//===========================================================================
//
//
//
//===========================================================================
void F2DDrawer::AddColorOnlyQuad(int x1, int y1, int w, int h, PalEntry color, FRenderStyle *style)
{
RenderCommand dg;
dg.mType = DrawTypeTriangles;
dg.mVertCount = 4;
dg.mVertIndex = (int)mVertices.Reserve(4);
dg.mRenderStyle = style? *style : LegacyRenderStyles[STYLE_Translucent];
auto ptr = &mVertices[dg.mVertIndex];
ptr->Set(x1, y1, 0, 0, 0, color); ptr++;
ptr->Set(x1, y1 + h, 0, 0, 0, color); ptr++;
ptr->Set(x1 + w, y1, 0, 0, 0, color); ptr++;
ptr->Set(x1 + w, y1 + h, 0, 0, 0, color); ptr++;
dg.mIndexIndex = mIndices.Size();
dg.mIndexCount += 6;
AddIndices(dg.mVertIndex, 6, 0, 1, 2, 1, 3, 2);
AddCommand(&dg);
}
//==========================================================================
//
//
//
//==========================================================================
void F2DDrawer::AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t color, uint8_t alpha)
{
PalEntry p = color ? (PalEntry)color : GPalette.BaseColors[palcolor];
p.a = alpha;
RenderCommand dg;
dg.mType = DrawTypeLines;
dg.mRenderStyle = LegacyRenderStyles[STYLE_Translucent];
dg.mVertCount = 2;
dg.mVertIndex = (int)mVertices.Reserve(2);
mVertices[dg.mVertIndex].Set(x1, y1, 0, 0, 0, p);
mVertices[dg.mVertIndex+1].Set(x2, y2, 0, 0, 0, p);
AddCommand(&dg);
}
//==========================================================================
//
//
//
//==========================================================================
void F2DDrawer::AddThickLine(int x1, int y1, int x2, int y2, double thickness, uint32_t color, uint8_t alpha)
{
PalEntry p = (PalEntry)color;
p.a = alpha;
DVector2 point0(x1, y1);
DVector2 point1(x2, y2);
DVector2 delta = point1 - point0;
DVector2 perp(-delta.Y, delta.X);
perp.MakeUnit();
perp *= thickness / 2;
DVector2 corner0 = point0 + perp;
DVector2 corner1 = point0 - perp;
DVector2 corner2 = point1 + perp;
DVector2 corner3 = point1 - perp;
RenderCommand dg;
dg.mType = DrawTypeTriangles;
dg.mVertCount = 4;
dg.mVertIndex = (int)mVertices.Reserve(4);
dg.mRenderStyle = LegacyRenderStyles[STYLE_Translucent];
auto ptr = &mVertices[dg.mVertIndex];
ptr->Set(corner0.X, corner0.Y, 0, 0, 0, p); ptr++;
ptr->Set(corner1.X, corner1.Y, 0, 0, 0, p); ptr++;
ptr->Set(corner2.X, corner2.Y, 0, 0, 0, p); ptr++;
ptr->Set(corner3.X, corner3.Y, 0, 0, 0, p); ptr++;
dg.mIndexIndex = mIndices.Size();
dg.mIndexCount += 6;
AddIndices(dg.mVertIndex, 6, 0, 1, 2, 1, 3, 2);
AddCommand(&dg);
}
//==========================================================================
//
//
//
//==========================================================================
void F2DDrawer::AddPixel(int x1, int y1, int palcolor, uint32_t color)
{
PalEntry p = color ? (PalEntry)color : GPalette.BaseColors[palcolor];
p.a = 255;
RenderCommand dg;
dg.mType = DrawTypePoints;
dg.mRenderStyle = LegacyRenderStyles[STYLE_Translucent];
dg.mVertCount = 1;
dg.mVertIndex = (int)mVertices.Reserve(1);
mVertices[dg.mVertIndex].Set(x1, y1, 0, 0, 0, p);
AddCommand(&dg);
}
//==========================================================================
//
//
//
//==========================================================================
void F2DDrawer::Clear()
{
mVertices.Clear();
mIndices.Clear();
mData.Clear();
}

View file

@ -0,0 +1,150 @@
#ifndef __2DDRAWER_H
#define __2DDRAWER_H
#include "tarray.h"
#include "textures.h"
#include "v_palette.h"
#include "r_data/renderstyle.h"
#include "r_data/colormaps.h"
struct DrawParms;
// intermediate struct for shape drawing
enum EClearWhich
{
C_Verts = 1,
C_Coords = 2,
C_Indices = 4,
};
class DShape2D : public DObject
{
DECLARE_CLASS(DShape2D,DObject)
public:
TArray<int> mIndices;
TArray<DVector2> mVertices;
TArray<DVector2> mCoords;
};
class F2DDrawer
{
public:
enum EDrawType : uint8_t
{
DrawTypeTriangles,
DrawTypeLines,
DrawTypePoints,
};
enum ETextureFlags : uint8_t
{
DTF_Wrap = 1,
DTF_Scissor = 2,
DTF_Burn = 4,
};
// This vertex type is hardware independent and needs conversion when put into a buffer.
struct TwoDVertex
{
float x, y, z;
float u, v;
PalEntry color0;
void Set(float xx, float yy, float zz)
{
x = xx;
z = zz;
y = yy;
u = 0;
v = 0;
color0 = 0;
}
void Set(double xx, double yy, double zz, double uu, double vv, PalEntry col)
{
x = (float)xx;
z = (float)zz;
y = (float)yy;
u = (float)uu;
v = (float)vv;
color0 = col;
}
};
struct RenderCommand
{
EDrawType mType;
int mVertIndex;
int mVertCount;
int mIndexIndex;
int mIndexCount;
FTexture *mTexture;
FRemapTable *mTranslation;
PalEntry mSpecialColormap[2];
int mScissor[4];
int mDesaturate;
FRenderStyle mRenderStyle;
PalEntry mColor1; // Overlay color
ETexMode mDrawMode;
uint8_t mFlags;
RenderCommand()
{
memset(this, 0, sizeof(*this));
}
// If these fields match, two draw commands can be batched.
bool isCompatible(const RenderCommand &other) const
{
return mTexture == other.mTexture &&
mType == other.mType &&
mTranslation == other.mTranslation &&
mSpecialColormap == other.mSpecialColormap &&
!memcmp(mScissor, other.mScissor, sizeof(mScissor)) &&
mDesaturate == other.mDesaturate &&
mRenderStyle == other.mRenderStyle &&
mDrawMode == other.mDrawMode &&
mFlags == other.mFlags &&
mColor1 == other.mColor1;
}
};
TArray<int> mIndices;
TArray<TwoDVertex> mVertices;
TArray<RenderCommand> mData;
int AddCommand(const RenderCommand *data);
void AddIndices(int firstvert, int count, ...);
bool SetStyle(FTexture *tex, DrawParms &parms, PalEntry &color0, RenderCommand &quad);
void SetColorOverlay(PalEntry color, float alpha, PalEntry &vertexcolor, PalEntry &overlaycolor);
public:
void AddTexture(FTexture *img, DrawParms &parms);
void AddShape(FTexture *img, DShape2D *shape, DrawParms &parms);
void AddPoly(FTexture *texture, FVector2 *points, int npoints,
double originx, double originy, double scalex, double scaley,
DAngle rotation, const FColormap &colormap, PalEntry flatcolor, int lightlevel, uint32_t *indices, size_t indexcount);
void AddFlatFill(int left, int top, int right, int bottom, FTexture *src, bool local_origin);
void AddColorOnlyQuad(int left, int top, int width, int height, PalEntry color, FRenderStyle *style);
void AddDim(PalEntry color, float damount, int x1, int y1, int w, int h);
void AddClear(int left, int top, int right, int bottom, int palcolor, uint32_t color);
void AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32_t color, uint8_t alpha = 255);
void AddThickLine(int x1, int y1, int x2, int y2, double thickness, uint32_t color, uint8_t alpha = 255);
void AddPixel(int x1, int y1, int palcolor, uint32_t color);
void Clear();
};
#endif

View file

@ -0,0 +1,209 @@
/*
** v_blend.cpp
** Screen blending stuff
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 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 <assert.h>
#include "templates.h"
#include "sbar.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "v_video.h"
#include "s_sound.h"
#include "gi.h"
#include "d_player.h"
#include "g_levellocals.h"
#include "vm.h"
CVAR( Float, blood_fade_scalar, 1.0f, CVAR_ARCHIVE ) // [SP] Pulled from Skulltag - changed default from 0.5 to 1.0
CVAR( Float, pickup_fade_scalar, 1.0f, CVAR_ARCHIVE ) // [SP] Uses same logic as blood_fade_scalar except for pickups
// [RH] Amount of red flash for up to 114 damage points. Calculated by hand
// using a logarithmic scale and my trusty HP48G.
static uint8_t DamageToAlpha[114] =
{
0, 8, 16, 23, 30, 36, 42, 47, 53, 58, 62, 67, 71, 75, 79,
83, 87, 90, 94, 97, 100, 103, 107, 109, 112, 115, 118, 120, 123, 125,
128, 130, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157,
159, 160, 162, 164, 165, 167, 169, 170, 172, 173, 175, 176, 178, 179, 181,
182, 183, 185, 186, 187, 189, 190, 191, 192, 194, 195, 196, 197, 198, 200,
201, 202, 203, 204, 205, 206, 207, 209, 210, 211, 212, 213, 214, 215, 216,
217, 218, 219, 220, 221, 221, 222, 223, 224, 225, 226, 227, 228, 229, 229,
230, 231, 232, 233, 234, 235, 235, 236, 237
};
/*
=============
SV_AddBlend
[RH] This is from Q2.
=============
*/
void V_AddBlend (float r, float g, float b, float a, float v_blend[4])
{
float a2, a3;
if (a <= 0)
return;
a2 = v_blend[3] + (1-v_blend[3])*a; // new total alpha
a3 = v_blend[3]/a2; // fraction of color from old
v_blend[0] = v_blend[0]*a3 + r*(1-a3);
v_blend[1] = v_blend[1]*a3 + g*(1-a3);
v_blend[2] = v_blend[2]*a3 + b*(1-a3);
v_blend[3] = a2;
}
//---------------------------------------------------------------------------
//
// BlendView
//
//---------------------------------------------------------------------------
void V_AddPlayerBlend (player_t *CPlayer, float blend[4], float maxinvalpha, int maxpainblend)
{
int cnt;
auto Level = CPlayer->mo->Level;
// [RH] All powerups can affect the screen blending now
for (AActor *item = CPlayer->mo->Inventory; item != NULL; item = item->Inventory)
{
PalEntry color = 0;
IFVIRTUALPTRNAME(item, NAME_Inventory, GetBlend)
{
VMValue params[1] = { item };
VMReturn ret((int*)&color.d);
VMCall(func, params, 1, &ret, 1);
}
if (color.a != 0)
{
V_AddBlend (color.r/255.f, color.g/255.f, color.b/255.f, color.a/255.f, blend);
if (color.a/255.f > maxinvalpha) maxinvalpha = color.a/255.f;
}
}
if (CPlayer->bonuscount)
{
cnt = CPlayer->bonuscount << 3;
// [SP] Allow player to tone down intensity of pickup flash.
cnt = (int)( cnt * pickup_fade_scalar );
V_AddBlend (RPART(gameinfo.pickupcolor)/255.f, GPART(gameinfo.pickupcolor)/255.f,
BPART(gameinfo.pickupcolor)/255.f, cnt > 128 ? 0.5f : cnt / 255.f, blend);
}
PalEntry painFlash = 0;
IFVIRTUALPTRNAME(CPlayer->mo, NAME_PlayerPawn, GetPainFlash)
{
VMValue param = CPlayer->mo;
VMReturn ret((int*)&painFlash.d);
VMCall(func, &param, 1, &ret, 1);
}
if (painFlash.a != 0)
{
cnt = DamageToAlpha[MIN (113, CPlayer->damagecount * painFlash.a / 255)];
// [BC] Allow users to tone down the intensity of the blood on the screen.
cnt = (int)( cnt * blood_fade_scalar );
if (cnt)
{
if (cnt > maxpainblend)
cnt = maxpainblend;
V_AddBlend (painFlash.r / 255.f, painFlash.g / 255.f, painFlash.b / 255.f, cnt / 255.f, blend);
}
}
// Unlike Doom, I did not have any utility source to look at to find the
// exact numbers to use here, so I've had to guess by looking at how they
// affect the white color in Hexen's palette and picking an alpha value
// that seems reasonable.
// [Gez] The exact values could be obtained by looking how they affect
// each color channel in Hexen's palette.
if (CPlayer->poisoncount)
{
cnt = MIN (CPlayer->poisoncount, 64);
if (paletteflash & PF_POISON)
{
V_AddBlend(44/255.f, 92/255.f, 36/255.f, ((cnt + 7) >> 3) * 0.1f, blend);
}
else
{
V_AddBlend (0.04f, 0.2571f, 0.f, cnt/93.2571428571f, blend);
}
}
if (CPlayer->hazardcount)
{
if (paletteflash & PF_HAZARD)
{
if (CPlayer->hazardcount > 16*TICRATE || (CPlayer->hazardcount & 8))
{
float r = ((Level->hazardflash & 0xff0000) >> 16) / 255.f;
float g = ((Level->hazardflash & 0xff00) >> 8) / 255.f;
float b = ((Level->hazardflash & 0xff)) / 255.f;
V_AddBlend (r, g, b, 0.125f, blend);
}
}
else
{
cnt= MIN(CPlayer->hazardcount/8, 64);
float r = ((Level->hazardcolor & 0xff0000) >> 16) / 255.f;
float g = ((Level->hazardcolor & 0xff00) >> 8) / 255.f;
float b = ((Level->hazardcolor & 0xff)) / 255.f;
V_AddBlend (r, g, b, cnt/93.2571428571f, blend);
}
}
if (CPlayer->mo->DamageType == NAME_Ice)
{
if (paletteflash & PF_ICE)
{
V_AddBlend(0.f, 0.f, 224/255.f, 0.5f, blend);
}
else
{
V_AddBlend (0.25f, 0.25f, 0.853f, 0.4f, blend);
}
}
// cap opacity if desired
if (blend[3] > maxinvalpha) blend[3] = maxinvalpha;
}

1495
src/rendering/2D/v_draw.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,376 @@
/*
** v_text.cpp
** Draws text to a canvas. Also has a text line-breaker thingy.
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 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 <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <wctype.h>
#include "v_text.h"
#include "v_video.h"
#include "w_wad.h"
#include "image.h"
#include "textures/formats/multipatchtexture.h"
#include "gstrings.h"
#include "vm.h"
#include "serializer.h"
int ListGetInt(VMVa_List &tags);
//==========================================================================
//
// Create a texture from a text in a given font.
//
//==========================================================================
FTexture * BuildTextTexture(FFont *font, const char *string, int textcolor)
{
int w;
const uint8_t *ch;
int cx;
int cy;
FRemapTable *range;
int kerning;
FTexture *pic;
kerning = font->GetDefaultKerning();
ch = (const uint8_t *)string;
cx = 0;
cy = 0;
IntRect box;
while (auto c = GetCharFromString(ch))
{
if (c == TEXTCOLOR_ESCAPE)
{
// Here we only want to measure the texture so just parse over the color.
V_ParseFontColor(ch, 0, 0);
continue;
}
if (c == '\n')
{
cx = 0;
cy += font->GetHeight();
continue;
}
if (nullptr != (pic = font->GetChar(c, CR_UNTRANSLATED, &w, nullptr)))
{
auto img = pic->GetImage();
auto offsets = img->GetOffsets();
int x = cx - offsets.first;
int y = cy - offsets.second;
int ww = img->GetWidth();
int h = img->GetHeight();
box.AddToRect(x, y);
box.AddToRect(x + ww, y + h);
}
cx += (w + kerning);
}
cx = -box.left;
cy = -box.top;
TArray<TexPart> part(strlen(string));
while (auto c = GetCharFromString(ch))
{
if (c == TEXTCOLOR_ESCAPE)
{
EColorRange newcolor = V_ParseFontColor(ch, textcolor, textcolor);
if (newcolor != CR_UNDEFINED)
{
range = font->GetColorTranslation(newcolor);
textcolor = newcolor;
}
continue;
}
if (c == '\n')
{
cx = 0;
cy += font->GetHeight();
continue;
}
if (nullptr != (pic = font->GetChar(c, textcolor, &w, nullptr)))
{
auto img = pic->GetImage();
auto offsets = img->GetOffsets();
int x = cx - offsets.first;
int y = cy - offsets.second;
auto &tp = part[part.Reserve(1)];
tp.OriginX = x;
tp.OriginY = y;
tp.Image = img;
tp.Translation = range;
}
cx += (w + kerning);
}
FMultiPatchTexture *image = new FMultiPatchTexture(box.width, box.height, part, false, false);
image->SetOffsets(-box.left, -box.top);
FImageTexture *tex = new FImageTexture(image, "");
tex->SetUseType(ETextureType::MiscPatch);
TexMan.AddTexture(tex);
return tex;
}
//==========================================================================
//
// DrawChar
//
// Write a single character using the given font
//
//==========================================================================
void DFrameBuffer::DrawChar (FFont *font, int normalcolor, double x, double y, int character, int tag_first, ...)
{
if (font == NULL)
return;
if (normalcolor >= NumTextColors)
normalcolor = CR_UNTRANSLATED;
FTexture *pic;
int dummy;
bool redirected;
if (NULL != (pic = font->GetChar (character, normalcolor, &dummy, &redirected)))
{
DrawParms parms;
Va_List tags;
va_start(tags.list, tag_first);
bool res = ParseDrawTextureTags(pic, x, y, tag_first, tags, &parms, false);
va_end(tags.list);
if (!res)
{
return;
}
PalEntry color = 0xffffffff;
parms.remap = redirected? nullptr : font->GetColorTranslation((EColorRange)normalcolor, &color);
parms.color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255);
DrawTextureParms(pic, parms);
}
}
void DFrameBuffer::DrawChar(FFont *font, int normalcolor, double x, double y, int character, VMVa_List &args)
{
if (font == NULL)
return;
if (normalcolor >= NumTextColors)
normalcolor = CR_UNTRANSLATED;
FTexture *pic;
int dummy;
bool redirected;
if (NULL != (pic = font->GetChar(character, normalcolor, &dummy, &redirected)))
{
DrawParms parms;
uint32_t tag = ListGetInt(args);
bool res = ParseDrawTextureTags(pic, x, y, tag, args, &parms, false);
if (!res) return;
PalEntry color = 0xffffffff;
parms.remap = redirected ? nullptr : font->GetColorTranslation((EColorRange)normalcolor, &color);
parms.color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255);
DrawTextureParms(pic, parms);
}
}
DEFINE_ACTION_FUNCTION(_Screen, DrawChar)
{
PARAM_PROLOGUE;
PARAM_POINTER(font, FFont);
PARAM_INT(cr);
PARAM_FLOAT(x);
PARAM_FLOAT(y);
PARAM_INT(chr);
PARAM_VA_POINTER(va_reginfo) // Get the hidden type information array
if (!screen->HasBegun2D()) ThrowAbortException(X_OTHER, "Attempt to draw to screen outside a draw function");
VMVa_List args = { param + 5, 0, numparam - 6, va_reginfo + 5 };
screen->DrawChar(font, cr, x, y, chr, args);
return 0;
}
//==========================================================================
//
// DrawText
//
// Write a string using the given font
//
//==========================================================================
void DFrameBuffer::DrawTextCommon(FFont *font, int normalcolor, double x, double y, const char *string, DrawParms &parms)
{
int w;
const uint8_t *ch;
int c;
double cx;
double cy;
int boldcolor;
FRemapTable *range;
int kerning;
FTexture *pic;
if (parms.celly == 0) parms.celly = font->GetHeight() + 1;
parms.celly *= parms.scaley;
if (normalcolor >= NumTextColors)
normalcolor = CR_UNTRANSLATED;
boldcolor = normalcolor ? normalcolor - 1 : NumTextColors - 1;
PalEntry colorparm = parms.color;
PalEntry color = 0xffffffff;
range = font->GetColorTranslation((EColorRange)normalcolor, &color);
parms.color = PalEntry(colorparm.a, (color.r * colorparm.r) / 255, (color.g * colorparm.g) / 255, (color.b * colorparm.b) / 255);
kerning = font->GetDefaultKerning();
ch = (const uint8_t *)string;
cx = x;
cy = y;
auto currentcolor = normalcolor;
while ((const char *)ch - string < parms.maxstrlen)
{
c = GetCharFromString(ch);
if (!c)
break;
if (c == TEXTCOLOR_ESCAPE)
{
EColorRange newcolor = V_ParseFontColor(ch, normalcolor, boldcolor);
if (newcolor != CR_UNDEFINED)
{
range = font->GetColorTranslation(newcolor, &color);
parms.color = PalEntry(colorparm.a, (color.r * colorparm.r) / 255, (color.g * colorparm.g) / 255, (color.b * colorparm.b) / 255);
currentcolor = newcolor;
}
continue;
}
if (c == '\n')
{
cx = x;
cy += parms.celly;
continue;
}
bool redirected = false;
if (NULL != (pic = font->GetChar(c, currentcolor, &w, &redirected)))
{
parms.remap = redirected? nullptr : range;
SetTextureParms(&parms, pic, cx, cy);
if (parms.cellx)
{
w = parms.cellx;
parms.destwidth = parms.cellx;
parms.destheight = parms.celly;
}
DrawTextureParms(pic, parms);
}
cx += (w + kerning) * parms.scalex;
}
}
void DFrameBuffer::DrawText(FFont *font, int normalcolor, double x, double y, const char *string, int tag_first, ...)
{
Va_List tags;
DrawParms parms;
if (font == NULL || string == NULL)
return;
va_start(tags.list, tag_first);
bool res = ParseDrawTextureTags(nullptr, 0, 0, tag_first, tags, &parms, true);
va_end(tags.list);
if (!res)
{
return;
}
DrawTextCommon(font, normalcolor, x, y, string, parms);
}
void DFrameBuffer::DrawText(FFont *font, int normalcolor, double x, double y, const char *string, VMVa_List &args)
{
DrawParms parms;
if (font == NULL || string == NULL)
return;
uint32_t tag = ListGetInt(args);
bool res = ParseDrawTextureTags(nullptr, 0, 0, tag, args, &parms, true);
if (!res)
{
return;
}
DrawTextCommon(font, normalcolor, x, y, string, parms);
}
DEFINE_ACTION_FUNCTION(_Screen, DrawText)
{
PARAM_PROLOGUE;
PARAM_POINTER(font, FFont);
PARAM_INT(cr);
PARAM_FLOAT(x);
PARAM_FLOAT(y);
PARAM_STRING(chr);
PARAM_VA_POINTER(va_reginfo) // Get the hidden type information array
if (!screen->HasBegun2D()) ThrowAbortException(X_OTHER, "Attempt to draw to screen outside a draw function");
VMVa_List args = { param + 5, 0, numparam - 6, va_reginfo + 5 };
const char *txt = chr[0] == '$' ? GStrings(&chr[1]) : chr.GetChars();
screen->DrawText(font, cr, x, y, txt, args);
return 0;
}