raze/source/core/textures/tilesetbuilder.cpp
Christoph Oelckers 6fd4368592 - moved Duke/RR's tile name list to raze.pk3, now that internally it is not used anymore.
This way it can later also be used by editors.
2023-04-29 09:55:48 +02:00

344 lines
11 KiB
C++

/*
** tilesetbuilder.cpp
** Constructs the full tile set and adds it to the texture manager.
**
**---------------------------------------------------------------------------
** Copyright 2019-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 "image.h"
#include "texturemanager.h"
#include "m_crc32.h"
#include "c_dispatch.h"
#include "tiletexture.h"
#include "tilesetbuilder.h"
#include "gamecontrol.h"
#include "startupinfo.h"
#include "printf.h"
#include "m_argv.h"
#include "gamestruct.h"
const char* G_DefFile(void);
void loaddefinitionsfile(TilesetBuildInfo& info, const char* fn, bool cumulative = false, bool maingrp = false);
//==========================================================================
//
// Returns checksum for a given tile or texture
//
//==========================================================================
int32_t tileGetCRC32(FImageSource* image)
{
if (image == nullptr) return 0;
auto pixels = image->GetPalettedPixels(0);
if (pixels.Size() == 0) return 0;
// To get proper CRCs as the calling code expects we need to put the translucent index back to 255.
for (auto& p : pixels)
{
if (p == 0) p = 255;
else if (p == 255) p = 0;
}
return crc32(0, (const Bytef*)pixels.Data(), pixels.Size());
}
CCMD(tilecrc)
{
if (argv.argc() > 1)
{
char* p;
int tile = strtol(argv[1], &p, 10);
FGameTexture* tex;
if (tile >= 0 && tile < MAXTILES && !*p)
{
// tex = tileGetTexture(tile);
}
else
{
tex = TexMan.FindGameTexture(argv[1], ETextureType::Any);
}
auto img = tex? tex->GetTexture()->GetImage() : nullptr;
if (!img) Printf("%s: not a valid texture", argv[1]);
else Printf("%d\n", tileGetCRC32(img));
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void LoadDefinitions(TilesetBuildInfo& info)
{
for (unsigned i = 0; i < info.tile.Size(); i++)
{
if (info.tile[i].extinfo.tiletovox > info.nextvoxid) info.nextvoxid = info.tile[i].extinfo.tiletovox;
}
info.nextvoxid++;
const char* defsfile = G_DefFile();
FString razedefsfile = defsfile;
razedefsfile.Substitute(".def", "-raze.def");
loaddefinitionsfile(info, "engine/engine.def", true, true); // Internal stuff that is required.
// check what we have.
// user .defs override the default ones and are not cumulative.
// if we fine even one Raze-specific file, all of those will be loaded cumulatively.
// otherwise the default rules inherited from older ports apply.
if (userConfig.UserDef.IsNotEmpty())
{
loaddefinitionsfile(info, userConfig.UserDef, false);
}
else
{
if (fileSystem.FileExists(razedefsfile))
{
loaddefinitionsfile(info, razedefsfile, true);
}
else if (fileSystem.FileExists(defsfile))
{
loaddefinitionsfile(info, defsfile, false);
}
}
if (userConfig.AddDefs)
{
for (auto& m : *userConfig.AddDefs)
{
loaddefinitionsfile(info, m, false);
}
userConfig.AddDefs.reset();
}
if (GameStartupInfo.def.IsNotEmpty())
{
loaddefinitionsfile(info, GameStartupInfo.def); // Stuff from gameinfo.
}
// load the widescreen replacements last. This ensures that mods still get the correct CRCs for their own tile replacements.
if (fileSystem.FindFile("engine/widescreen.def") >= 0 && !Args->CheckParm("-nowidescreen"))
{
loaddefinitionsfile(info, "engine/widescreen.def");
}
fileSystem.InitHashChains(); // make sure that any resources that got added can be found again.
}
//==========================================================================
//
//
//
//==========================================================================
picanm_t tileConvertAnimFormat(int32_t const picanimraw)
{
// Unpack a 4 byte packed anim descriptor into something more accessible
picanm_t anm;
anm.num = picanimraw & 63;
anm.sf = ((picanimraw >> 24) & 15) | (picanimraw & 192);
anm.extra = (picanimraw >> 28) & 15;
return anm;
}
//==========================================================================
//
//
//
//==========================================================================
FImageSource* createWritableTile(int width, int height);
FImageSource* makeTileWritable(FImageSource* img);
void TilesetBuildInfo::MakeWritable(int tileno)
{
if (tile[tileno].tileimage != nullptr)
{
auto newtex = makeTileWritable(tile[tileno].tileimage);
tile[tileno].tileimage = newtex;
tile[tileno].imported = nullptr;
}
}
void TilesetBuildInfo::CreateWritable(int tileno, int w, int h)
{
auto newtex = createWritableTile(w, h);
tile[tileno].tileimage = newtex;
tile[tileno].imported = nullptr;
}
//===========================================================================
//
// MakeCanvas
//
// Turns texture into a canvas (i.e. camera texture)
//
//===========================================================================
void TilesetBuildInfo::MakeCanvas(int tilenum, int width, int height)
{
auto ftex = new FCanvasTexture(width * 4, height * 4);
ftex->aspectRatio = (float)width / height;
auto canvas = MakeGameTexture(ftex, FStringf("#%05d", tilenum), ETextureType::Any);
canvas->SetSize(width * 4, height * 4);
canvas->SetDisplaySize((float)width, (float)height);
canvas->GetTexture()->SetSize(width * 4, height * 4);
tile[tilenum].imported = canvas;
tile[tilenum].tileimage = nullptr;
}
void LoadAliases(int firsttileid, int maxarttile)
{
int lump, lastlump = 0;
while ((lump = fileSystem.FindLump("TEXNAMES", &lastlump, false)) != -1)
{
FScanner sc;
sc.OpenLumpNum(lump);
while (sc.GetNumber())
{
int tile = sc.Number;
if (tile < 0 || tile > maxarttile) tile = maxarttile;
sc.MustGetStringName("=");
sc.MustGetString();
TexMan.AddAlias(sc.String, FSetTextureID(firsttileid + tile));
}
}
}
//==========================================================================
//
//
//
//==========================================================================
void ConstructTileset()
{
TilesetBuildInfo info {};
TArray<FImageSource*> images;
TArray<unsigned> rawpicanm;
GetArtImages(images, rawpicanm);
info.tile.Resize(MAXTILES);
memset(info.tile.Data(), 0, info.tile.Size() * sizeof(info.tile[0]));
// fill up the arrays to the maximum allowed but remember the highest original number.
for (unsigned i = 0; i < images.Size(); i++)
{
info.tile[i].orgimage = info.tile[i].tileimage = images[i];
if (images[i])
{
auto s = images[i]->GetOffsets();
info.tile[i].leftOffset = s.first;
info.tile[i].topOffset = s.second;
}
info.tile[i].extinfo.picanm = tileConvertAnimFormat(rawpicanm[i]);
}
images.Reset();
rawpicanm.Reset();
for (auto& a : info.tile)
{
a.alphathreshold = 0.5f;
a.extinfo.tiletovox = -1;
}
gi->LoadTextureInfo(info); // initialize game data that must be done before loading .DEF
LoadDefinitions(info);
gi->SetupSpecialTextures(info); // initialize game data that needs .DEF being processed.
// now that everything has been set up, we can add the textures to the texture manager.
// To keep things simple everything from .ART files and its replacements will remain in order and
// converting between a Build tilenum and a texture ID can done with a single addition.
// Even though this requires adding quite a few empty textures to the texture manager, it makes things a lot easier,
// because it ensures an unambiguous mapping and allows communicating with features that only work with tile numbers.
// as long as no named textures are used.
auto nulltex = TexMan.GameByIndex(0); // Use the null texture's backing data for all empty placeholders.
// Only the outward facing FGameTexture needs to be different
firstarttile = TexMan.NumTextures();
maxarttile = MAXTILES - 1;
while (maxarttile >= 0 && info.tile[maxarttile].tileimage == nullptr) maxarttile--;
if (maxarttile < 0) return; // should never happen, but who knows - maybe someone will make a game without ART files later... :D
maxarttile++; // create a placeholder in the first unused spot. This will later get used for all out of range tile numbers.
int lastid = firstarttile - 1;
for (int i = 0; i <= maxarttile; i++)
{
FTexture* ftex = nullptr;
FGameTexture* gtex;
FStringf tname("#%05d", i);
if (info.tile[i].tileimage == nullptr)
{
if (info.tile[i].imported == nullptr)
{
ftex = nulltex->GetTexture();
gtex = MakeGameTexture(ftex, tname, ETextureType::Null);
}
else
{
// Canvas textures can be used directly without wrapping them again.
gtex = info.tile[i].imported;
}
}
else
{
if (info.tile[i].imported) ftex = info.tile[i].imported->GetTexture();
else ftex = new FImageTexture(info.tile[i].tileimage);
gtex = MakeGameTexture(ftex, tname, ETextureType::Any);
gtex->SetOffsets(info.tile[i].leftOffset, info.tile[i].topOffset);
}
if (info.tile[i].extinfo.picanm.sf & PICANM_NOFULLBRIGHT_BIT)
{
gtex->SetDisableFullbright(true);
}
auto id = TexMan.AddGameTexture(gtex, true);
if (id.GetIndex() != lastid + 1)
{
// this should never happen unless the texture manager gets redone in an incompatible fashion.
I_FatalError("Unable to assign consecutive texture IDs to tile set.");
}
lastid = id.GetIndex();
}
// Now create the extended info. This will leave room for all regular textures as well so that later code can assign info to these, too.
// Textures being added afterward will always see the default extinfo, even if they are not covered by this array.
texExtInfo.Resize(TexMan.NumTextures());
memset(texExtInfo.Data(), 0, sizeof(texExtInfo[0]) * texExtInfo.Size());
for (auto& x : texExtInfo) x.tiletovox = -1;
// now copy all extinfo stuff that got parsed by .DEF or some game specific setup.
for (int i = 0; i <= maxarttile; i++)
{
texExtInfo[i + firstarttile] = info.tile[i].extinfo;
}
LoadAliases(firstarttile, maxarttile);
for (auto& a : info.aliases)
{
TexMan.AddAlias(a.first.GetChars(), min(maxarttile, a.second) + firstarttile);
}
}