/* ** 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 ConstructTileset() { TilesetBuildInfo info {}; TArray images; TArray 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; } for (auto& a : info.aliases) { TexMan.AddAlias(a.first.GetChars(), min(maxarttile, a.second) + firstarttile); } }