1206 lines
32 KiB
C++
1206 lines
32 KiB
C++
/*
|
|
** r_anim.cpp
|
|
** Routines for handling texture animation.
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** 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 "doomtype.h"
|
|
#include "cmdlib.h"
|
|
#include "engineerrors.h"
|
|
#include "r_sky.h"
|
|
#include "m_random.h"
|
|
#include "d_player.h"
|
|
#include "p_spec.h"
|
|
#include "filesystem.h"
|
|
#include "serializer.h"
|
|
#include "animations.h"
|
|
#include "texturemanager.h"
|
|
#include "image.h"
|
|
|
|
// MACROS ------------------------------------------------------------------
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
FTextureAnimator TexAnim;
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
static FRandom pr_animatepictures ("AnimatePics");
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
void FTextureAnimator::DeleteAll()
|
|
{
|
|
for (unsigned int i = 0; i < mFireTextures.Size(); i++)
|
|
{
|
|
mFireTextures[i].texture->CleanHardwareData(true);
|
|
}
|
|
mAnimations.Clear();
|
|
mFireTextures.Clear();
|
|
|
|
for (unsigned i = 0; i < mSwitchDefs.Size(); i++)
|
|
{
|
|
if (mSwitchDefs[i] != NULL)
|
|
{
|
|
M_Free(mSwitchDefs[i]);
|
|
mSwitchDefs[i] = NULL;
|
|
}
|
|
}
|
|
mSwitchDefs.Clear();
|
|
mAnimatedDoors.Clear();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: AddAnim
|
|
//
|
|
// Adds a new animation to the array. If one with the same basepic as the
|
|
// new one already exists, it is replaced.
|
|
//
|
|
//==========================================================================
|
|
|
|
FAnimDef *FTextureAnimator::AddAnim (FAnimDef& anim)
|
|
{
|
|
// Search for existing duplicate.
|
|
uint16_t * index = mAnimationIndices.CheckKey(anim.BasePic);
|
|
|
|
if(index)
|
|
{ // Found one!
|
|
mAnimations[*index] = anim;
|
|
return &mAnimations[*index];
|
|
}
|
|
else
|
|
{ // Didn't find one, so add it at the end.
|
|
mAnimationIndices.Insert(anim.BasePic, mAnimations.Size());
|
|
mAnimations.Push (anim);
|
|
return &mAnimations.Last();
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: AddSimpleAnim
|
|
//
|
|
// Creates an animation with simple characteristics. This is used for
|
|
// original Doom (non-ANIMDEFS-style) animations and Build animations.
|
|
//
|
|
//==========================================================================
|
|
|
|
FAnimDef *FTextureAnimator::AddSimpleAnim (FTextureID picnum, int animcount, uint32_t speedmin, uint32_t speedrange)
|
|
{
|
|
if (TexMan.AreTexturesCompatible(picnum, picnum + (animcount - 1)))
|
|
{
|
|
FAnimDef anim;
|
|
anim.CurFrame = 0;
|
|
anim.BasePic = picnum;
|
|
anim.NumFrames = animcount;
|
|
anim.AnimType = FAnimDef::ANIM_Forward;
|
|
anim.bDiscrete = false;
|
|
anim.SwitchTime = 0;
|
|
anim.Frames = (FAnimDef::FAnimFrame*)ImageArena.Alloc(sizeof(FAnimDef::FAnimFrame));
|
|
anim.Frames[0].SpeedMin = speedmin;
|
|
anim.Frames[0].SpeedRange = speedrange;
|
|
anim.Frames[0].FramePic = anim.BasePic;
|
|
return AddAnim (anim);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: AddComplexAnim
|
|
//
|
|
// Creates an animation with individually defined frames.
|
|
//
|
|
//==========================================================================
|
|
|
|
FAnimDef *FTextureAnimator::AddComplexAnim (FTextureID picnum, const TArray<FAnimDef::FAnimFrame> &frames)
|
|
{
|
|
FAnimDef anim;
|
|
anim.BasePic = picnum;
|
|
anim.NumFrames = frames.Size();
|
|
anim.CurFrame = 0;
|
|
anim.AnimType = FAnimDef::ANIM_Forward;
|
|
anim.bDiscrete = true;
|
|
anim.SwitchTime = 0;
|
|
anim.Frames = (FAnimDef::FAnimFrame*)ImageArena.Alloc(frames.Size() * sizeof(frames[0]));
|
|
memcpy (&anim.Frames[0], &frames[0], frames.Size() * sizeof(frames[0]));
|
|
return AddAnim (anim);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: Initanimated
|
|
//
|
|
// [description copied from BOOM]
|
|
// Load the table of animation definitions, checking for existence of
|
|
// the start and end of each frame. If the start doesn't exist the sequence
|
|
// is skipped, if the last doesn't exist, BOOM exits.
|
|
//
|
|
// Wall/Flat animation sequences, defined by name of first and last frame,
|
|
// The full animation sequence is given using all lumps between the start
|
|
// and end entry, in the order found in the WAD file.
|
|
//
|
|
// This routine modified to read its data from a predefined lump or
|
|
// PWAD lump called ANIMATED rather than a static table in this module to
|
|
// allow wad designers to insert or modify animation sequences.
|
|
//
|
|
// Lump format is an array of byte packed animdef_t structures, terminated
|
|
// by a structure with istexture == -1. The lump can be generated from a
|
|
// text source file using SWANTBLS.EXE, distributed with the BOOM utils.
|
|
// The standard list of switches and animations is contained in the example
|
|
// source text file DEFSWANI.DAT also in the BOOM util distribution.
|
|
//
|
|
// [RH] Rewritten to support BOOM ANIMATED lump but also make absolutely
|
|
// no assumptions about how the compiler packs the animdefs array.
|
|
//
|
|
// Since animdef_t no longer exists in ZDoom, here is some documentation
|
|
// of the format:
|
|
//
|
|
// This is an array of <n> entries, terminated by a 0xFF byte. Each entry
|
|
// is 23 bytes long and consists of the following fields:
|
|
// Byte 0: Bit 1 set for wall texture, clear for flat texture.
|
|
// Bytes 1-9: '\0'-terminated name of first texture.
|
|
// Bytes 10-18: '\0'-terminated name of last texture.
|
|
// Bytes 19-22: Tics per frame (stored in little endian order).
|
|
//
|
|
//==========================================================================
|
|
|
|
CVAR(Bool, debuganimated, false, 0)
|
|
|
|
void FTextureAnimator::InitAnimated (void)
|
|
{
|
|
const BITFIELD texflags = FTextureManager::TEXMAN_Overridable;
|
|
// I think better not! This is only for old ANIMATED definitions that
|
|
// don't know about ZDoom's more flexible texture system.
|
|
// | FTextureAnimator::FTextureManager::TEXMAN_TryAny;
|
|
|
|
int lumpnum = fileSystem.CheckNumForName ("ANIMATED");
|
|
if (lumpnum != -1)
|
|
{
|
|
auto animatedlump = fileSystem.ReadFile (lumpnum);
|
|
int animatedlen = fileSystem.FileLength(lumpnum);
|
|
auto animdefs = animatedlump.bytes();
|
|
const uint8_t *anim_p;
|
|
FTextureID pic1, pic2;
|
|
int animtype;
|
|
uint32_t animspeed;
|
|
|
|
// Init animation
|
|
animtype = FAnimDef::ANIM_Forward;
|
|
|
|
for (anim_p = animdefs; *anim_p != 0xFF; anim_p += 23)
|
|
{
|
|
// make sure the current chunk of data is inside the lump boundaries.
|
|
if (anim_p + 22 >= animdefs + animatedlen)
|
|
{
|
|
I_Error("Tried to read past end of ANIMATED lump.");
|
|
}
|
|
if (*anim_p /* .istexture */ & 1)
|
|
{
|
|
// different episode ?
|
|
if (!(pic1 = TexMan.CheckForTexture ((const char*)(anim_p + 10) /* .startname */, ETextureType::Wall, texflags)).Exists() ||
|
|
!(pic2 = TexMan.CheckForTexture ((const char*)(anim_p + 1) /* .endname */, ETextureType::Wall, texflags)).Exists())
|
|
continue;
|
|
|
|
// [RH] Bit 1 set means allow decals on walls with this texture
|
|
bool nodecals = !(*anim_p & 2);
|
|
TexMan.GameTexture(pic2)->SetNoDecals(nodecals);
|
|
TexMan.GameTexture(pic1)->SetNoDecals(nodecals);
|
|
}
|
|
else
|
|
{
|
|
if (!(pic1 = TexMan.CheckForTexture ((const char*)(anim_p + 10) /* .startname */, ETextureType::Flat, texflags)).Exists() ||
|
|
!(pic2 = TexMan.CheckForTexture ((const char*)(anim_p + 1) /* .startname */, ETextureType::Flat, texflags)).Exists())
|
|
continue;
|
|
}
|
|
|
|
auto tex1 = TexMan.GameTexture(pic1);
|
|
auto tex2 = TexMan.GameTexture(pic2);
|
|
|
|
animspeed = (anim_p[19] << 0) | (anim_p[20] << 8) |
|
|
(anim_p[21] << 16) | (anim_p[22] << 24);
|
|
|
|
// SMMU-style swirly hack? Don't apply on already-warping texture
|
|
if (animspeed > 65535 && tex1 != NULL && !tex1->isWarped())
|
|
{
|
|
tex1->SetWarpStyle(2);
|
|
}
|
|
// These tests were not really relevant for swirling textures, or even potentially
|
|
// harmful, so they have been moved to the else block.
|
|
else
|
|
{
|
|
if (tex1->GetUseType() != tex2->GetUseType())
|
|
{
|
|
// not the same type -
|
|
continue;
|
|
}
|
|
|
|
if (debuganimated)
|
|
{
|
|
Printf("Defining animation '%s' (texture %d, lump %d, file %d) to '%s' (texture %d, lump %d, file %d)\n",
|
|
tex1->GetName().GetChars(), pic1.GetIndex(), tex1->GetSourceLump(), fileSystem.GetFileContainer(tex1->GetSourceLump()),
|
|
tex2->GetName().GetChars(), pic2.GetIndex(), tex2->GetSourceLump(), fileSystem.GetFileContainer(tex2->GetSourceLump()));
|
|
}
|
|
|
|
if (pic1 == pic2)
|
|
{
|
|
// This animation only has one frame. Skip it. (Doom aborted instead.)
|
|
Printf ("Animation %s in ANIMATED has only one frame\n", (const char*)(anim_p + 10));
|
|
continue;
|
|
}
|
|
// [RH] Allow for backward animations as well as forward.
|
|
else if (pic1 > pic2)
|
|
{
|
|
std::swap (pic1, pic2);
|
|
animtype = FAnimDef::ANIM_Backward;
|
|
}
|
|
|
|
// Speed is stored as tics, but we want ms so scale accordingly.
|
|
FAnimDef *adef = AddSimpleAnim (pic1, pic2 - pic1 + 1, Scale (animspeed, 1000, TICRATE));
|
|
if (adef != NULL) adef->AnimType = animtype;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: InitAnimDefs
|
|
//
|
|
// This uses a Hexen ANIMDEFS lump to define the animation sequences
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::InitAnimDefs ()
|
|
{
|
|
const BITFIELD texflags = FTextureManager::TEXMAN_Overridable | FTextureManager::TEXMAN_TryAny;
|
|
int lump, lastlump = 0;
|
|
|
|
while ((lump = fileSystem.FindLump ("ANIMDEFS", &lastlump)) != -1)
|
|
{
|
|
FScanner sc(lump);
|
|
|
|
while (sc.GetString ())
|
|
{
|
|
if (sc.Compare ("flat"))
|
|
{
|
|
ParseAnim (sc, ETextureType::Flat);
|
|
}
|
|
else if (sc.Compare ("texture"))
|
|
{
|
|
ParseAnim (sc, ETextureType::Wall);
|
|
}
|
|
else if (sc.Compare ("switch"))
|
|
{
|
|
ProcessSwitchDef (sc);
|
|
}
|
|
// [GRB] Added warping type 2
|
|
else if (sc.Compare ("warp") || sc.Compare ("warp2"))
|
|
{
|
|
ParseWarp(sc);
|
|
}
|
|
else if (sc.Compare("canvastexture"))
|
|
{
|
|
ParseCanvasTexture(sc);
|
|
}
|
|
else if (sc.Compare ("cameratexture"))
|
|
{
|
|
ParseCameraTexture(sc);
|
|
}
|
|
else if (sc.Compare ("animatedDoor"))
|
|
{
|
|
ParseAnimatedDoor (sc);
|
|
}
|
|
else if (sc.Compare("skyoffset"))
|
|
{
|
|
sc.MustGetString ();
|
|
FTextureID picnum = TexMan.CheckForTexture (sc.String, ETextureType::Wall, texflags);
|
|
sc.MustGetNumber();
|
|
if (picnum.Exists())
|
|
{
|
|
TexMan.GameTexture(picnum)->SetSkyOffset(sc.Number);
|
|
}
|
|
}
|
|
else if (sc.Compare("firetexture"))
|
|
{
|
|
ParseFireTexture (sc);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError (NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: ParseAnim
|
|
//
|
|
// Parse a single animation definition out of an ANIMDEFS lump and
|
|
// create the corresponding animation structure.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::ParseAnim (FScanner &sc, ETextureType usetype)
|
|
{
|
|
const BITFIELD texflags = FTextureManager::TEXMAN_Overridable | FTextureManager::TEXMAN_TryAny;
|
|
TArray<FAnimDef::FAnimFrame> frames (32);
|
|
FTextureID picnum;
|
|
int defined = 0;
|
|
bool optional = false, missing = false;
|
|
FAnimDef *ani = NULL;
|
|
uint8_t type = FAnimDef::ANIM_Forward;
|
|
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("optional"))
|
|
{
|
|
optional = true;
|
|
sc.MustGetString ();
|
|
}
|
|
picnum = TexMan.CheckForTexture (sc.String, usetype, texflags);
|
|
|
|
if (!picnum.Exists())
|
|
{
|
|
if (optional)
|
|
{
|
|
missing = true;
|
|
}
|
|
else
|
|
{
|
|
Printf (PRINT_BOLD, "ANIMDEFS: Can't find %s\n", sc.String);
|
|
}
|
|
}
|
|
|
|
// no decals on animating textures, by default
|
|
if (picnum.isValid())
|
|
{
|
|
TexMan.GameTexture(picnum)->SetNoDecals(true);
|
|
}
|
|
|
|
while (sc.GetString ())
|
|
{
|
|
if (sc.Compare ("allowdecals"))
|
|
{
|
|
if (picnum.isValid())
|
|
{
|
|
TexMan.GameTexture(picnum)->SetNoDecals(false);
|
|
}
|
|
continue;
|
|
}
|
|
else if (sc.Compare ("Oscillate"))
|
|
{
|
|
if (type == FAnimDef::ANIM_Random)
|
|
{
|
|
sc.ScriptError ("You cannot use \"random\" and \"oscillate\" together in a single animation.");
|
|
}
|
|
type = FAnimDef::ANIM_OscillateUp;
|
|
}
|
|
else if (sc.Compare("Random"))
|
|
{
|
|
if (type == FAnimDef::ANIM_OscillateUp)
|
|
{
|
|
sc.ScriptError ("You cannot use \"random\" and \"oscillate\" together in a single animation.");
|
|
}
|
|
type = FAnimDef::ANIM_Random;
|
|
}
|
|
else if (sc.Compare ("range"))
|
|
{
|
|
if (picnum.Exists() && TexMan.GameTexture(picnum)->GetName().IsEmpty())
|
|
{
|
|
// long texture name: We cannot do ranged anims on these because they have no defined order
|
|
sc.ScriptError ("You cannot use \"range\" for long texture names.");
|
|
}
|
|
if (defined == 2)
|
|
{
|
|
sc.ScriptError ("You cannot use \"pic\" and \"range\" together in a single animation.");
|
|
}
|
|
if (defined == 1)
|
|
{
|
|
sc.ScriptError ("You can only use one \"range\" per animation.");
|
|
}
|
|
defined = 1;
|
|
ani = ParseRangeAnim (sc, picnum, usetype, missing);
|
|
}
|
|
else if (sc.Compare("notrim"))
|
|
{
|
|
if (picnum.isValid())
|
|
{
|
|
auto tex = TexMan.GetGameTexture(picnum);
|
|
|
|
if (tex) tex->SetNoTrimming(true);
|
|
else sc.ScriptError("NoTrim: %s not found", sc.String);
|
|
}
|
|
else sc.ScriptError("NoTrim: %s is not a sprite", sc.String);
|
|
}
|
|
else if (sc.Compare ("pic"))
|
|
{
|
|
if (defined == 1)
|
|
{
|
|
sc.ScriptError ("You cannot use \"pic\" and \"range\" together in a single animation.");
|
|
}
|
|
defined = 2;
|
|
ParsePicAnim (sc, picnum, usetype, missing, frames);
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If base pic is not present, don't add this anim
|
|
// ParseRangeAnim adds the anim itself, but ParsePicAnim does not.
|
|
if (picnum.isValid() && defined == 2)
|
|
{
|
|
if (frames.Size() < 2)
|
|
{
|
|
sc.ScriptError ("Animation needs at least 2 frames");
|
|
}
|
|
ani = AddComplexAnim (picnum, frames);
|
|
}
|
|
if (ani != NULL && type != FAnimDef::ANIM_Forward)
|
|
{
|
|
if (ani->AnimType == FAnimDef::ANIM_Backward && type == FAnimDef::ANIM_OscillateUp) ani->AnimType = FAnimDef::ANIM_OscillateDown;
|
|
else ani->AnimType = type;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: ParseRangeAnim
|
|
//
|
|
// Parse an animation defined using "range". Not that one range entry is
|
|
// enough to define a complete animation, unlike "pic".
|
|
//
|
|
//==========================================================================
|
|
|
|
FAnimDef *FTextureAnimator::ParseRangeAnim (FScanner &sc, FTextureID picnum, ETextureType usetype, bool missing)
|
|
{
|
|
int type;
|
|
FTextureID framenum;
|
|
uint32_t min, max;
|
|
|
|
type = FAnimDef::ANIM_Forward;
|
|
framenum = ParseFramenum (sc, picnum, usetype, missing);
|
|
|
|
ParseTime (sc, min, max);
|
|
|
|
if (framenum == picnum || !picnum.Exists() || !framenum.Exists())
|
|
{
|
|
return NULL; // Animation is only one frame or does not exist
|
|
}
|
|
|
|
if (TexMan.GameTexture(framenum)->GetName().IsEmpty())
|
|
{
|
|
// long texture name: We cannot do ranged anims on these because they have no defined order
|
|
sc.ScriptError ("You cannot use \"range\" for long texture names.");
|
|
}
|
|
|
|
if (framenum < picnum)
|
|
{
|
|
type = FAnimDef::ANIM_Backward;
|
|
TexMan.GameTexture(framenum)->SetNoDecals(TexMan.GameTexture(picnum)->allowNoDecals());
|
|
std::swap (framenum, picnum);
|
|
}
|
|
FAnimDef *ani = AddSimpleAnim (picnum, framenum - picnum + 1, min, max - min);
|
|
if (ani != NULL) ani->AnimType = type;
|
|
return ani;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: ParsePicAnim
|
|
//
|
|
// Parse a single frame from ANIMDEFS defined using "pic".
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::ParsePicAnim (FScanner &sc, FTextureID picnum, ETextureType usetype, bool missing, TArray<FAnimDef::FAnimFrame> &frames)
|
|
{
|
|
FTextureID framenum;
|
|
uint32_t min = 1, max = 1;
|
|
|
|
framenum = ParseFramenum (sc, picnum, usetype, missing);
|
|
ParseTime (sc, min, max);
|
|
|
|
if (picnum.isValid())
|
|
{
|
|
FAnimDef::FAnimFrame frame;
|
|
|
|
frame.SpeedMin = min;
|
|
frame.SpeedRange = max - min;
|
|
frame.FramePic = framenum;
|
|
frames.Push (frame);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: ParseFramenum
|
|
//
|
|
// Reads a frame's texture from ANIMDEFS. It can either be an integral
|
|
// offset from basepicnum or a specific texture name.
|
|
//
|
|
//==========================================================================
|
|
|
|
FTextureID FTextureAnimator::ParseFramenum (FScanner &sc, FTextureID basepicnum, ETextureType usetype, bool allowMissing)
|
|
{
|
|
const BITFIELD texflags = FTextureManager::TEXMAN_Overridable | FTextureManager::TEXMAN_TryAny;
|
|
FTextureID framenum;
|
|
|
|
sc.MustGetString ();
|
|
if (IsNum (sc.String))
|
|
{
|
|
framenum = basepicnum + (atoi(sc.String) - 1);
|
|
}
|
|
else
|
|
{
|
|
framenum = TexMan.CheckForTexture (sc.String, usetype, texflags);
|
|
if (!framenum.Exists() && !allowMissing)
|
|
{
|
|
sc.ScriptError ("Unknown texture %s", sc.String);
|
|
}
|
|
}
|
|
return framenum;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: ParseTime
|
|
//
|
|
// Reads a tics or rand time definition from ANIMDEFS.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::ParseTime (FScanner &sc, uint32_t &min, uint32_t &max)
|
|
{
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("tics"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
min = max = uint32_t(sc.Float * 1000 / TICRATE);
|
|
}
|
|
else if (sc.Compare ("rand"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
min = uint32_t(sc.Float * 1000 / TICRATE);
|
|
sc.MustGetFloat ();
|
|
max = uint32_t(sc.Float * 1000 / TICRATE);
|
|
}
|
|
else
|
|
{
|
|
min = max = 1;
|
|
sc.ScriptError ("Must specify a duration for animation frame");
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: ParseWarp
|
|
//
|
|
// Parses a warping texture definition
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::ParseWarp(FScanner &sc)
|
|
{
|
|
const BITFIELD texflags = FTextureManager::TEXMAN_Overridable | FTextureManager::TEXMAN_TryAny;
|
|
bool isflat = false;
|
|
bool type2 = sc.Compare ("warp2"); // [GRB]
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("flat"))
|
|
{
|
|
isflat = true;
|
|
sc.MustGetString ();
|
|
}
|
|
else if (sc.Compare ("texture"))
|
|
{
|
|
isflat = false;
|
|
sc.MustGetString ();
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError (NULL);
|
|
}
|
|
FTextureID picnum = TexMan.CheckForTexture (sc.String, isflat ? ETextureType::Flat : ETextureType::Wall, texflags);
|
|
if (picnum.isValid())
|
|
{
|
|
|
|
auto warper = TexMan.GameTexture(picnum);
|
|
|
|
if (warper->GetName().IsEmpty())
|
|
{
|
|
// long texture name: We cannot do warps on these due to the way the texture manager implements warping as a texture replacement.
|
|
sc.ScriptError ("You cannot use \"warp\" for long texture names.");
|
|
}
|
|
|
|
|
|
// don't warp a texture more than once
|
|
if (!warper->isWarped())
|
|
{
|
|
warper->SetWarpStyle(type2 ? 2 : 1);
|
|
}
|
|
|
|
if (sc.CheckFloat())
|
|
{
|
|
warper->SetShaderSpeed(float(sc.Float));
|
|
}
|
|
|
|
// No decals on warping textures, by default.
|
|
// Warping information is taken from the last warp
|
|
// definition for this texture.
|
|
warper->SetNoDecals(true);
|
|
if (sc.GetString ())
|
|
{
|
|
if (sc.Compare ("allowdecals"))
|
|
{
|
|
warper->SetNoDecals(false);
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseCameraTexture
|
|
//
|
|
// Parses a canvas texture definition
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::ParseCanvasTexture(FScanner& sc)
|
|
{
|
|
// This is currently identical to camera textures.
|
|
ParseCameraTexture(sc);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseCameraTexture
|
|
//
|
|
// Parses a camera texture definition
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::ParseCameraTexture(FScanner &sc)
|
|
{
|
|
const BITFIELD texflags = FTextureManager::TEXMAN_Overridable | FTextureManager::TEXMAN_TryAny | FTextureManager::TEXMAN_ShortNameOnly;
|
|
int width, height;
|
|
double fitwidth, fitheight;
|
|
FString picname;
|
|
|
|
sc.MustGetString ();
|
|
picname = sc.String;
|
|
sc.MustGetNumber ();
|
|
width = sc.Number;
|
|
sc.MustGetNumber ();
|
|
height = sc.Number;
|
|
FTextureID picnum = TexMan.CheckForTexture (picname.GetChars(), ETextureType::Flat, texflags);
|
|
auto canvas = new FCanvasTexture(width, height);
|
|
FGameTexture *viewer = MakeGameTexture(canvas, picname.GetChars(), ETextureType::Wall);
|
|
if (picnum.Exists())
|
|
{
|
|
auto oldtex = TexMan.GameTexture(picnum);
|
|
fitwidth = oldtex->GetDisplayWidth ();
|
|
fitheight = oldtex->GetDisplayHeight ();
|
|
viewer->SetUseType(oldtex->GetUseType());
|
|
TexMan.ReplaceTexture (picnum, viewer, true);
|
|
}
|
|
else
|
|
{
|
|
fitwidth = width;
|
|
fitheight = height;
|
|
// [GRB] No need for oldtex
|
|
TexMan.AddGameTexture (viewer);
|
|
}
|
|
if (sc.GetString())
|
|
{
|
|
if (sc.Compare ("hdr"))
|
|
{
|
|
canvas->SetHDR(true);
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet();
|
|
}
|
|
}
|
|
if (sc.GetString())
|
|
{
|
|
if (sc.Compare ("fit"))
|
|
{
|
|
sc.MustGetNumber ();
|
|
fitwidth = sc.Number;
|
|
sc.MustGetNumber ();
|
|
fitheight = sc.Number;
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet ();
|
|
}
|
|
}
|
|
if (sc.GetString())
|
|
{
|
|
if (sc.Compare("WorldPanning"))
|
|
{
|
|
viewer->SetWorldPanning(true);
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet();
|
|
}
|
|
}
|
|
canvas->aspectRatio = (float)fitwidth / (float)fitheight;
|
|
viewer->SetDisplaySize((float)fitwidth, (float)fitheight);
|
|
}
|
|
|
|
void FTextureAnimator::ParseFireTexture(FScanner& sc)
|
|
{
|
|
FString picname;
|
|
FFireTexture fireTexture;
|
|
TArray<PalEntry> palette;
|
|
uint32_t duration;
|
|
|
|
sc.MustGetString();
|
|
picname = sc.String;
|
|
sc.MustGetStringName("tics");
|
|
sc.MustGetValue(true);
|
|
duration = uint32_t(sc.Float * 1000 / TICRATE);
|
|
|
|
FGameTexture* gametex = MakeGameTexture(new FireTexture(), picname.GetChars(), ETextureType::Wall);
|
|
// No decals here.
|
|
gametex->SetNoDecals(true);
|
|
if (sc.GetString())
|
|
{
|
|
if (sc.Compare("allowdecals"))
|
|
{
|
|
gametex->SetNoDecals(false);
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet();
|
|
}
|
|
}
|
|
while (sc.GetString())
|
|
{
|
|
if (sc.Compare("color"))
|
|
{
|
|
uint8_t r, g, b, a;
|
|
sc.MustGetValue(false);
|
|
r = sc.Number;
|
|
sc.MustGetValue(false);
|
|
g = sc.Number;
|
|
sc.MustGetValue(false);;
|
|
b = sc.Number;
|
|
sc.MustGetValue(false);
|
|
a = sc.Number;
|
|
palette.Push(PalEntry(a, r, g, b));
|
|
if (a != 255 && a != 0)
|
|
{
|
|
gametex->SetTranslucent(true);
|
|
}
|
|
|
|
if (palette.Size() > 256)
|
|
{
|
|
sc.ScriptError("Too many colors specified!");
|
|
}
|
|
}
|
|
else if (sc.Compare("palette"))
|
|
{
|
|
uint8_t index = 0;
|
|
sc.MustGetValue(false);
|
|
index = sc.Number;
|
|
PalEntry pal = GPalette.BaseColors[index];
|
|
pal.a = pal.isBlack() ? 0 : 255;
|
|
palette.Push(pal);
|
|
|
|
if (palette.Size() > 256)
|
|
{
|
|
sc.ScriptError("Too many colors specified!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet();
|
|
break;
|
|
}
|
|
}
|
|
|
|
fireTexture.texture = gametex;
|
|
fireTexture.Duration = duration;
|
|
fireTexture.SwitchTime = 0;
|
|
static_cast<FireTexture*>(gametex->GetTexture())->SetPalette(palette);
|
|
mFireTextures.Push(fireTexture);
|
|
TexMan.AddGameTexture(gametex);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: FixAnimations
|
|
//
|
|
// Copy the "front sky" flag from an animated texture to the rest
|
|
// of the textures in the animation, and make every texture in an
|
|
// animation range use the same setting for bNoDecals.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::FixAnimations ()
|
|
{
|
|
unsigned int i;
|
|
int j;
|
|
|
|
for (i = 0; i < mAnimations.Size(); ++i)
|
|
{
|
|
const FAnimDef *anim = &mAnimations[i];
|
|
if (!anim->bDiscrete)
|
|
{
|
|
bool nodecals;
|
|
bool noremap = false;
|
|
const char *name;
|
|
|
|
name = TexMan.GameTexture(anim->BasePic)->GetName().GetChars();
|
|
nodecals = TexMan.GameTexture(anim->BasePic)->allowNoDecals();
|
|
for (j = 0; j < anim->NumFrames; ++j)
|
|
{
|
|
auto tex = TexMan.GameTexture(anim->BasePic + j);
|
|
tex->SetNoDecals(nodecals);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseAnimatedDoor
|
|
//
|
|
// Parses an animated door definition
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::ParseAnimatedDoor(FScanner &sc)
|
|
{
|
|
const BITFIELD texflags = FTextureManager::TEXMAN_Overridable | FTextureManager::TEXMAN_TryAny;
|
|
FDoorAnimation anim;
|
|
TArray<FTextureID> frames;
|
|
bool error = false;
|
|
FTextureID v;
|
|
|
|
sc.MustGetString();
|
|
anim.BaseTexture = TexMan.CheckForTexture (sc.String, ETextureType::Wall, texflags);
|
|
anim.OpenSound = anim.CloseSound = NAME_None;
|
|
|
|
if (!anim.BaseTexture.Exists())
|
|
{
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
TexMan.GameTexture(anim.BaseTexture)->SetNoDecals(true);
|
|
}
|
|
while (sc.GetString())
|
|
{
|
|
if (sc.Compare ("opensound"))
|
|
{
|
|
sc.MustGetString ();
|
|
anim.OpenSound = sc.String;
|
|
}
|
|
else if (sc.Compare ("closesound"))
|
|
{
|
|
sc.MustGetString ();
|
|
anim.CloseSound = sc.String;
|
|
}
|
|
else if (sc.Compare ("pic"))
|
|
{
|
|
sc.MustGetString ();
|
|
if (IsNum (sc.String))
|
|
{
|
|
v = anim.BaseTexture + (atoi(sc.String) - 1);
|
|
}
|
|
else
|
|
{
|
|
v = TexMan.CheckForTexture (sc.String, ETextureType::Wall, texflags);
|
|
if (!v.Exists() && anim.BaseTexture.Exists() && !error)
|
|
{
|
|
sc.ScriptError ("Unknown texture %s", sc.String);
|
|
}
|
|
}
|
|
frames.Push(v);
|
|
}
|
|
else if (sc.Compare("allowdecals"))
|
|
{
|
|
if (anim.BaseTexture.Exists()) TexMan.GameTexture(anim.BaseTexture)->SetNoDecals(false);
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet ();
|
|
break;
|
|
}
|
|
}
|
|
if (!error)
|
|
{
|
|
anim.TextureFrames = (FTextureID*)ImageArena.Alloc(sizeof(FTextureID) * frames.Size());
|
|
memcpy (anim.TextureFrames, &frames[0], sizeof(FTextureID) * frames.Size());
|
|
anim.NumTextureFrames = frames.Size();
|
|
mAnimatedDoors.Push (anim);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Return index into "DoorAnimations" array for which door type to use
|
|
//
|
|
//==========================================================================
|
|
|
|
FDoorAnimation *FTextureAnimator::FindAnimatedDoor (FTextureID picnum)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < mAnimatedDoors.Size(); ++i)
|
|
{
|
|
if (picnum == mAnimatedDoors[i].BaseTexture)
|
|
return &mAnimatedDoors[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FAnimDef :: SetSwitchTime
|
|
//
|
|
// Determines when to switch to the next frame.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FAnimDef::SetSwitchTime (uint64_t mstime)
|
|
{
|
|
int speedframe = bDiscrete ? CurFrame : 0;
|
|
|
|
SwitchTime = mstime + Frames[speedframe].SpeedMin;
|
|
if (Frames[speedframe].SpeedRange != 0)
|
|
{
|
|
SwitchTime += pr_animatepictures(Frames[speedframe].SpeedRange);
|
|
}
|
|
}
|
|
|
|
static void AdvanceFrame(uint16_t &frame, uint8_t &AnimType, const FAnimDef &anim)
|
|
{
|
|
switch (AnimType)
|
|
{
|
|
default:
|
|
case FAnimDef::ANIM_Forward:
|
|
frame = (frame + 1) % anim.NumFrames;
|
|
break;
|
|
|
|
case FAnimDef::ANIM_Backward:
|
|
if (frame == 0)
|
|
{
|
|
frame = anim.NumFrames - 1;
|
|
}
|
|
else
|
|
{
|
|
frame--;
|
|
}
|
|
break;
|
|
case FAnimDef::ANIM_Random:
|
|
// select a random frame other than the current one
|
|
if (anim.NumFrames > 1)
|
|
{
|
|
uint16_t rndFrame = (uint16_t)pr_animatepictures(anim.NumFrames - 1);
|
|
if(rndFrame == frame) rndFrame++;
|
|
frame = rndFrame % anim.NumFrames;
|
|
}
|
|
break;
|
|
|
|
case FAnimDef::ANIM_OscillateUp:
|
|
frame = frame + 1;
|
|
assert(frame < anim.NumFrames);
|
|
if (frame == anim.NumFrames - 1)
|
|
{
|
|
AnimType = FAnimDef::ANIM_OscillateDown;
|
|
}
|
|
break;
|
|
|
|
case FAnimDef::ANIM_OscillateDown:
|
|
frame = frame - 1;
|
|
if (frame == 0)
|
|
{
|
|
AnimType = FAnimDef::ANIM_OscillateUp;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
constexpr double msPerTic = 1'000.0 / TICRATE;
|
|
|
|
bool FTextureAnimator::InitStandaloneAnimation(FStandaloneAnimation &animInfo, FTextureID tex, uint32_t curTic)
|
|
{
|
|
animInfo.ok = false;
|
|
uint16_t * index = mAnimationIndices.CheckKey(tex);
|
|
if(!index) return false;
|
|
FAnimDef * anim = &mAnimations[*index];
|
|
|
|
animInfo.ok = true;
|
|
animInfo.AnimIndex = *index;
|
|
animInfo.CurFrame = 0;
|
|
animInfo.SwitchTic = curTic;
|
|
animInfo.AnimType = (anim->AnimType == FAnimDef::ANIM_OscillateDown) ? FAnimDef::ANIM_OscillateUp : anim->AnimType;
|
|
uint32_t time = anim->Frames[0].SpeedMin;
|
|
if(anim->Frames[0].SpeedRange != 0)
|
|
{
|
|
time += pr_animatepictures(anim->Frames[0].SpeedRange);
|
|
}
|
|
animInfo.SwitchTic += time / msPerTic;
|
|
return true;
|
|
}
|
|
|
|
FTextureID FTextureAnimator::UpdateStandaloneAnimation(FStandaloneAnimation &animInfo, double curTic)
|
|
{
|
|
if(!animInfo.ok) return nullptr;
|
|
auto &anim = mAnimations[animInfo.AnimIndex];
|
|
if(animInfo.SwitchTic <= curTic)
|
|
{
|
|
uint16_t frame = animInfo.CurFrame;
|
|
uint16_t speedframe = anim.bDiscrete ? frame : 0;
|
|
while(animInfo.SwitchTic <= curTic)
|
|
{
|
|
AdvanceFrame(frame, animInfo.AnimType, anim);
|
|
|
|
if(anim.bDiscrete) speedframe = frame;
|
|
|
|
uint32_t time = anim.Frames[speedframe].SpeedMin;
|
|
if(anim.Frames[speedframe].SpeedRange != 0)
|
|
{
|
|
time += pr_animatepictures(anim.Frames[speedframe].SpeedRange);
|
|
}
|
|
|
|
animInfo.SwitchTic += time / msPerTic;
|
|
}
|
|
animInfo.CurFrame = frame;
|
|
}
|
|
return anim.bDiscrete ? anim.Frames[animInfo.CurFrame].FramePic : (anim.BasePic + animInfo.CurFrame);
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// FTextureAnimator :: UpdateAnimations
|
|
//
|
|
// Updates texture translations for each animation and scrolls the skies.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FTextureAnimator::UpdateAnimations (uint64_t mstime)
|
|
{
|
|
for (unsigned int i = 0; i < mFireTextures.Size(); i++)
|
|
{
|
|
FFireTexture* fire = &mFireTextures[i];
|
|
bool updated = false;
|
|
|
|
if (fire->SwitchTime == 0)
|
|
{
|
|
fire->SwitchTime = mstime + fire->Duration;
|
|
}
|
|
else while (fire->SwitchTime <= mstime)
|
|
{
|
|
static_cast<FireTexture*>(fire->texture->GetTexture())->Update();
|
|
fire->SwitchTime = mstime + fire->Duration;
|
|
updated = true;
|
|
}
|
|
|
|
if (updated)
|
|
{
|
|
fire->texture->CleanHardwareData();
|
|
|
|
if (fire->texture->GetSoftwareTexture())
|
|
delete fire->texture->GetSoftwareTexture();
|
|
|
|
fire->texture->SetSoftwareTexture(nullptr);
|
|
}
|
|
}
|
|
for (unsigned int j = 0; j < mAnimations.Size(); ++j)
|
|
{
|
|
FAnimDef *anim = &mAnimations[j];
|
|
|
|
// If this is the first time through R_UpdateAnimations, just
|
|
// initialize the anim's switch time without actually animating.
|
|
if (anim->SwitchTime == 0)
|
|
{
|
|
anim->SetSwitchTime (mstime);
|
|
}
|
|
else while (anim->SwitchTime <= mstime)
|
|
{ // Multiple frames may have passed since the last time calling
|
|
// R_UpdateAnimations, so be sure to loop through them all.
|
|
|
|
AdvanceFrame(anim->CurFrame, anim->AnimType, *anim);
|
|
anim->SetSwitchTime (mstime);
|
|
}
|
|
|
|
if (anim->bDiscrete)
|
|
{
|
|
TexMan.SetTranslation (anim->BasePic, anim->Frames[anim->CurFrame].FramePic);
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int i = 0; i < anim->NumFrames; i++)
|
|
{
|
|
TexMan.SetTranslation (anim->BasePic + i, anim->BasePic + (i + anim->CurFrame) % anim->NumFrames);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// operator<<
|
|
//
|
|
//==========================================================================
|
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDoorAnimation *&p, FDoorAnimation **def)
|
|
{
|
|
FTextureID tex = p? p->BaseTexture : FNullTextureID();
|
|
Serialize(arc, key, tex, def ? &(*def)->BaseTexture : nullptr);
|
|
if (arc.isReading())
|
|
{
|
|
p = TexAnim.FindAnimatedDoor(tex);
|
|
}
|
|
return arc;
|
|
}
|
|
|