mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-16 07:21:28 +00:00
394 lines
9.6 KiB
C++
394 lines
9.6 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// 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);
|
|
}
|