mirror of
https://github.com/DrBeef/Raze.git
synced 2024-11-22 12:11:21 +00:00
460 lines
12 KiB
C++
Executable file
460 lines
12 KiB
C++
Executable file
/*
|
|
** wipe.cpp
|
|
** Screen wipe implementation
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 1998-2016 Randy Heit
|
|
** Copyright 2005-2022 Christoph Oelckers
|
|
** 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 "v_video.h"
|
|
#include "m_random.h"
|
|
#include "wipe.h"
|
|
|
|
#include "bitmap.h"
|
|
#include "hw_material.h"
|
|
#include "v_draw.h"
|
|
#include "s_soundinternal.h"
|
|
#include "i_time.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(const 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
|
|
{
|
|
protected:
|
|
FGameTexture* startScreen = nullptr, * endScreen = nullptr;
|
|
public:
|
|
virtual ~Wiper();
|
|
virtual bool Run(int ticks) = 0;
|
|
virtual void SetTextures(FGameTexture* startscreen, FGameTexture* endscreen)
|
|
{
|
|
startScreen = startscreen;
|
|
endScreen = endscreen;
|
|
}
|
|
|
|
static Wiper* Create(int type);
|
|
};
|
|
|
|
|
|
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:
|
|
enum { WIDTH = 320, HEIGHT = 200 };
|
|
int y[WIDTH];
|
|
};
|
|
|
|
class Wiper_Burn : public Wiper
|
|
{
|
|
public:
|
|
~Wiper_Burn();
|
|
bool Run(int ticks) override;
|
|
void SetTextures(FGameTexture *startscreen, FGameTexture *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;
|
|
DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
|
|
DrawTexture(twod, 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()
|
|
{
|
|
y[0] = -(M_Random() & 15);
|
|
for (int i = 1; i < WIDTH; ++i)
|
|
{
|
|
y[i] = clamp(y[i-1] + (M_Random() % 3) - 1, -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 = false;
|
|
DrawTexture(twod, 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] < HEIGHT)
|
|
{
|
|
if (y[i] < 0)
|
|
y[i]++;
|
|
else if (y[i] < 16)
|
|
y[i] += y[i] + 1;
|
|
else
|
|
y[i] = min<int>(y[i] + 8, 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.
|
|
|
|
int w = startScreen->GetTexelWidth();
|
|
int h = startScreen->GetTexelHeight();
|
|
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)
|
|
{
|
|
DrawTexture(twod, 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(FGameTexture *startscreen, FGameTexture *endscreen)
|
|
{
|
|
startScreen = startscreen;
|
|
endScreen = endscreen;
|
|
BurnTexture = new FBurnTexture(WIDTH, HEIGHT);
|
|
auto mat = FMaterial::ValidateTexture(endScreen, false);
|
|
mat->ClearLayers();
|
|
mat->AddTextureLayer(BurnTexture, false);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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 = false;
|
|
|
|
|
|
BurnTime += ticks;
|
|
ticks *= 2;
|
|
|
|
// Make the fire burn
|
|
while (!done && ticks--)
|
|
{
|
|
Density = wipe_CalcBurn(BurnArray, WIDTH, HEIGHT, Density);
|
|
done = (Density < 0);
|
|
}
|
|
|
|
BurnTexture->CleanHardwareTextures();
|
|
endScreen->CleanHardwareData(false); // this only cleans the descriptor sets for the Vulkan backend. We do not want to delete the wipe screen's hardware texture here.
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
|
|
DrawTexture(twod, 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);
|
|
}
|
|
|
|
|
|
void PerformWipe(FTexture* startimg, FTexture* endimg, int wipe_type, bool stopsound, std::function<void()> overlaydrawer)
|
|
{
|
|
// wipe update
|
|
uint64_t wipestart, nowtime, diff;
|
|
bool done;
|
|
|
|
GSnd->SetSfxPaused(true, 1);
|
|
I_FreezeTime(true);
|
|
twod->End();
|
|
assert(startimg != nullptr && endimg != nullptr);
|
|
auto starttex = MakeGameTexture(startimg, nullptr, ETextureType::SWCanvas);
|
|
auto endtex = MakeGameTexture(endimg, nullptr, ETextureType::SWCanvas);
|
|
auto wiper = Wiper::Create(wipe_type);
|
|
wiper->SetTextures(starttex, endtex);
|
|
|
|
wipestart = I_msTime();
|
|
|
|
do
|
|
{
|
|
do
|
|
{
|
|
I_WaitVBL(2);
|
|
nowtime = I_msTime();
|
|
diff = (nowtime - wipestart) * 40 / 1000; // Using 35 here feels too slow.
|
|
} while (diff < 1);
|
|
wipestart = nowtime;
|
|
twod->Begin(screen->GetWidth(), screen->GetHeight());
|
|
done = wiper->Run(1);
|
|
if (overlaydrawer) overlaydrawer();
|
|
twod->End();
|
|
screen->Update();
|
|
twod->OnFrameDone();
|
|
|
|
} while (!done);
|
|
delete wiper;
|
|
I_FreezeTime(false);
|
|
GSnd->SetSfxPaused(false, 1);
|
|
|
|
}
|