2020-11-10 19:12:46 +00:00
/*
* * hightile . cpp
* * Handling hires replacement definitions
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 2020 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 "files.h"
# include "zstring.h"
# include "buildtiles.h"
# include "image.h"
# include "palette.h"
# include "m_crc32.h"
# include "build.h"
# include "gamecontrol.h"
# include "palettecontainer.h"
# include "texturemanager.h"
# include "c_dispatch.h"
# include "sc_man.h"
# include "gamestruct.h"
# include "hw_renderstate.h"
2021-04-07 22:47:07 +00:00
# include "skyboxtexture.h"
2020-11-10 19:12:46 +00:00
CVARD ( Bool , hw_shadeinterpolate , true , CVAR_ARCHIVE | CVAR_GLOBALCONFIG , " enable/disable shade interpolation " )
struct HightileReplacement
{
2021-04-07 22:47:07 +00:00
FGameTexture * image ;
2020-11-10 19:12:46 +00:00
FVector2 scale ;
float alphacut , specpower , specfactor ;
2021-04-07 22:47:07 +00:00
uint16_t palnum ;
bool issky ;
2020-11-10 19:12:46 +00:00
} ;
static TMap < int , TArray < HightileReplacement > > tileReplacements ;
static TMap < int , TArray < HightileReplacement > > textureReplacements ;
2020-11-10 20:34:49 +00:00
struct FontCharInf
{
FGameTexture * base ;
FGameTexture * untranslated ;
FGameTexture * translated ;
} ;
static TArray < FontCharInf > deferredChars ;
void FontCharCreated ( FGameTexture * base , FGameTexture * untranslated , FGameTexture * translated )
{
// Store these in a list for now - they can only be processed in the finalization step.
if ( translated = = untranslated ) translated = nullptr ;
FontCharInf fci = { base , untranslated , translated } ;
deferredChars . Push ( fci ) ;
}
2020-11-10 19:12:46 +00:00
//===========================================================================
//
// Replacement textures
//
//===========================================================================
static void AddReplacement ( int picnum , const HightileReplacement & replace )
{
auto & Hightiles = tileReplacements [ picnum ] ;
for ( auto & ht : Hightiles )
{
2021-04-07 22:47:07 +00:00
if ( replace . palnum = = ht . palnum & & replace . issky = = ht . issky )
2020-11-10 19:12:46 +00:00
{
ht = replace ;
return ;
}
}
Hightiles . Push ( replace ) ;
}
//==========================================================================
//
// Remove a replacement
//
//==========================================================================
void tileRemoveReplacement ( int picnum )
{
tileReplacements . Remove ( picnum ) ;
}
//===========================================================================
//
//
//
//===========================================================================
static HightileReplacement * FindReplacement ( FTextureID picnum , int palnum , bool skybox )
{
auto Hightiles = textureReplacements . CheckKey ( picnum . GetIndex ( ) ) ;
if ( ! Hightiles ) return nullptr ;
for ( ; ; )
{
for ( auto & rep : * Hightiles )
{
2021-04-07 22:47:07 +00:00
if ( rep . palnum = = palnum & & rep . issky = = skybox ) return & rep ;
2020-11-10 19:12:46 +00:00
}
if ( ! palnum | | palnum > = MAXPALOOKUPS - RESERVEDPALS ) break ;
palnum = 0 ;
}
return nullptr ; // no replacement found
}
int checkTranslucentReplacement ( FTextureID picnum , int pal )
{
FGameTexture * tex = nullptr ;
auto si = FindReplacement ( picnum , pal , 0 ) ;
2021-04-07 22:47:07 +00:00
if ( si & & hw_hightile ) tex = si - > image ;
2020-11-10 19:12:46 +00:00
if ( ! tex | | tex - > GetTexelWidth ( ) = = 0 | | tex - > GetTexelHeight ( ) = = 0 ) return false ;
return tex & & tex - > GetTranslucency ( ) ;
}
2021-04-07 22:47:07 +00:00
FGameTexture * SkyboxReplacement ( FTextureID picnum , int palnum )
{
auto hr = FindReplacement ( picnum , palnum , true ) ;
if ( ! hr ) return nullptr ;
return hr - > image ;
}
2020-11-10 19:12:46 +00:00
//==========================================================================
//
// Processes data from .def files into the textures
//
//==========================================================================
void PostLoadSetup ( )
{
for ( int i = 0 ; i < MAXTILES ; i + + )
{
auto tex = tileGetTexture ( i ) ;
if ( ! tex - > isValid ( ) ) continue ;
auto Hightile = tileReplacements . CheckKey ( i ) ;
if ( ! Hightile ) continue ;
FGameTexture * detailTex = nullptr , * glowTex = nullptr , * normalTex = nullptr , * specTex = nullptr ;
float scalex = 1.f , scaley = 1.f ;
for ( auto & rep : * Hightile )
{
if ( rep . palnum = = GLOWPAL )
{
2021-04-07 22:47:07 +00:00
glowTex = rep . image ;
2020-11-10 19:12:46 +00:00
}
if ( rep . palnum = = NORMALPAL )
{
2021-04-07 22:47:07 +00:00
normalTex = rep . image ;
2020-11-10 19:12:46 +00:00
}
if ( rep . palnum = = SPECULARPAL )
{
2021-04-07 22:47:07 +00:00
specTex = rep . image ;
2020-11-10 19:12:46 +00:00
}
if ( rep . palnum = = DETAILPAL )
{
2021-04-07 22:47:07 +00:00
detailTex = rep . image ;
2020-11-10 19:12:46 +00:00
scalex = rep . scale . X ;
scaley = rep . scale . Y ;
}
}
if ( detailTex | | glowTex | | normalTex | | specTex )
{
for ( auto & rep : * Hightile )
{
2021-04-07 22:47:07 +00:00
if ( rep . issky ) continue ; // do not muck around with skyboxes (yet)
2020-11-10 19:12:46 +00:00
if ( rep . palnum < NORMALPAL )
{
2021-04-07 22:47:07 +00:00
auto tex = rep . image ;
2020-11-10 19:12:46 +00:00
// Make a copy so that multiple appearances of the same texture with different layers can be handled. They will all refer to the same internal texture anyway.
tex = MakeGameTexture ( tex - > GetTexture ( ) , " " , ETextureType : : Any ) ;
if ( glowTex ) tex - > SetGlowmap ( glowTex - > GetTexture ( ) ) ;
if ( detailTex ) tex - > SetDetailmap ( detailTex - > GetTexture ( ) ) ;
if ( normalTex ) tex - > SetNormalmap ( normalTex - > GetTexture ( ) ) ;
if ( specTex ) tex - > SetSpecularmap ( specTex - > GetTexture ( ) ) ;
tex - > SetDetailScale ( scalex , scaley ) ;
2021-04-07 22:47:07 +00:00
rep . image = tex ;
2020-11-10 19:12:46 +00:00
}
}
}
textureReplacements . Insert ( tex - > GetID ( ) . GetIndex ( ) , std : : move ( * Hightile ) ) ;
}
tileReplacements . Clear ( ) ;
2020-11-10 20:34:49 +00:00
int i = 0 ;
for ( auto & ci : deferredChars )
{
i + + ;
auto rep = textureReplacements . CheckKey ( ci . base - > GetID ( ) . GetIndex ( ) ) ;
if ( rep )
{
if ( ci . untranslated )
{
auto rrep = * rep ;
textureReplacements . Insert ( ci . untranslated - > GetID ( ) . GetIndex ( ) , std : : move ( rrep ) ) ;
}
if ( ci . translated )
{
//auto reptex = FindReplacement(ci.base->GetID(), 0, false);
//if (reptex)
{
// Todo: apply the translation.
//auto rrep = *rep;
//textureReplacements.Insert(ci.translated->GetID().GetIndex(), std::move(rrep));
}
}
}
}
2020-11-10 19:12:46 +00:00
}
//==========================================================================
//
// Specifies a replacement texture for an ART tile.
//
//==========================================================================
2021-04-07 22:47:07 +00:00
int tileSetHightileReplacement ( int picnum , int palnum , const char * filename , float alphacut , float xscale , float yscale , float specpower , float specfactor )
2020-11-10 19:12:46 +00:00
{
if ( ( uint32_t ) picnum > = ( uint32_t ) MAXTILES ) return - 1 ;
if ( ( uint32_t ) palnum > = ( uint32_t ) MAXPALOOKUPS ) return - 1 ;
auto tex = tileGetTexture ( picnum ) ;
if ( tex - > GetTexelWidth ( ) < = 0 | | tex - > GetTexelHeight ( ) < = 0 )
{
Printf ( " Warning: defined hightile replacement for empty tile %d. " , picnum ) ;
return - 1 ; // cannot add replacements to empty tiles, must create one beforehand
}
HightileReplacement replace = { } ;
FTextureID texid = TexMan . CheckForTexture ( filename , ETextureType : : Any ) ;
if ( ! texid . isValid ( ) )
{
Printf ( " %s: Replacement for tile %d does not exist or is invalid \n " , filename , picnum ) ;
return - 1 ;
}
2021-04-07 22:47:07 +00:00
replace . image = TexMan . GetGameTexture ( texid ) ;
2020-11-10 19:12:46 +00:00
replace . alphacut = min ( alphacut , 1.f ) ;
replace . scale = { xscale , yscale } ;
replace . specpower = specpower ; // currently unused
replace . specfactor = specfactor ; // currently unused
2021-04-07 22:47:07 +00:00
replace . issky = 0 ;
2020-11-10 19:12:46 +00:00
replace . palnum = ( uint16_t ) palnum ;
AddReplacement ( picnum , replace ) ;
return 0 ;
}
//==========================================================================
//
// Define the faces of a skybox
//
//==========================================================================
2021-04-07 22:47:07 +00:00
int tileSetSkybox ( int picnum , int palnum , FString * facenames )
2020-11-10 19:12:46 +00:00
{
2021-04-07 22:47:07 +00:00
if ( ( uint32_t ) picnum > = ( uint32_t ) MAXTILES ) return - 1 ;
if ( ( uint32_t ) palnum > = ( uint32_t ) MAXPALOOKUPS ) return - 1 ;
2020-11-10 19:12:46 +00:00
auto tex = tileGetTexture ( picnum ) ;
if ( tex - > GetTexelWidth ( ) < = 0 | | tex - > GetTexelHeight ( ) < = 0 )
{
Printf ( " Warning: defined skybox replacement for empty tile %d. " , picnum ) ;
return - 1 ; // cannot add replacements to empty tiles, must create one beforehand
}
HightileReplacement replace = { } ;
2021-04-07 22:47:07 +00:00
FGameTexture * faces [ 6 ] ;
for ( int i = 0 ; i < 6 ; i + + )
2020-11-10 19:12:46 +00:00
{
2021-04-07 22:47:07 +00:00
FTextureID texid = TexMan . CheckForTexture ( facenames [ i ] , ETextureType : : Any ) ;
2020-11-10 19:12:46 +00:00
if ( ! texid . isValid ( ) )
{
2021-04-07 22:47:07 +00:00
Printf ( " %s: Skybox image for tile %d does not exist or is invalid \n " , facenames [ i ] . GetChars ( ) , picnum ) ;
2020-11-10 19:12:46 +00:00
return - 1 ;
}
2021-04-08 11:54:58 +00:00
faces [ i ] = TexMan . GetGameTexture ( texid ) ;
2020-11-10 19:12:46 +00:00
}
2021-04-07 22:47:07 +00:00
FSkyBox * sbtex = new FSkyBox ( " " ) ;
memcpy ( sbtex - > faces , faces , sizeof ( faces ) ) ;
sbtex - > previous = faces [ 0 ] ; // won't ever be used, just to be safe.
sbtex - > fliptop = true ;
replace . image = MakeGameTexture ( sbtex , " " , ETextureType : : Override ) ;
TexMan . AddGameTexture ( replace . image , false ) ;
replace . issky = 1 ;
2020-11-10 19:12:46 +00:00
replace . palnum = ( uint16_t ) palnum ;
AddReplacement ( picnum , replace ) ;
return 0 ;
}
//===========================================================================
//
// Picks a texture for rendering for a given tilenum/palette combination
//
//===========================================================================
2021-02-27 12:30:52 +00:00
bool PickTexture ( FRenderState * state , FGameTexture * tex , int paletteid , TexturePick & pick )
2020-11-10 19:12:46 +00:00
{
if ( ! tex - > isValid ( ) | | tex - > GetTexelWidth ( ) < = 0 | | tex - > GetTexelHeight ( ) < = 0 ) return false ;
2021-01-29 10:47:55 +00:00
2020-11-10 22:59:04 +00:00
int usepalette = paletteid = = 0 ? 0 : GetTranslationType ( paletteid ) - Translation_Remap ;
2020-11-10 19:12:46 +00:00
int usepalswap = GetTranslationIndex ( paletteid ) ;
2021-02-27 12:30:52 +00:00
bool foggy = state & & ( state - > GetFogColor ( ) & 0xffffff ) ;
int TextureType = hw_int_useindexedcolortextures & & ! foggy ? TT_INDEXED : TT_TRUECOLOR ;
2020-11-10 19:12:46 +00:00
pick . translation = paletteid ;
pick . basepalTint = 0xffffff ;
auto & h = lookups . tables [ usepalswap ] ;
bool applytint = false ;
// Canvas textures must be treated like hightile replacements in the following code.
2021-01-29 12:05:52 +00:00
int hipalswap = usepalette > = 0 ? usepalswap : 0 ;
auto rep = ( hw_hightile & & ! ( h . tintFlags & TINTF_ALWAYSUSEART ) ) ? FindReplacement ( tex - > GetID ( ) , hipalswap , false ) : nullptr ;
2020-11-10 19:12:46 +00:00
if ( rep | | tex - > GetTexture ( ) - > isHardwareCanvas ( ) )
{
2021-01-29 12:05:52 +00:00
if ( usepalette > 0 )
2020-11-10 19:12:46 +00:00
{
// This is a global setting for the entire scene, so let's do it here, right at the start. (Fixme: Store this in a static table instead of reusing the same entry for all palettes.)
auto & hh = lookups . tables [ MAXPALOOKUPS - 1 ] ;
// This sets a tinting color for global palettes, e.g. water or slime - only used for hires replacements (also an option for low-resource hardware where duplicating the textures may be problematic.)
pick . basepalTint = hh . tintColor ;
}
if ( rep )
{
2021-04-07 22:47:07 +00:00
tex = rep - > image ;
2020-11-10 19:12:46 +00:00
}
2021-01-29 12:05:52 +00:00
if ( ! rep | | rep - > palnum ! = hipalswap | | ( h . tintFlags & TINTF_APPLYOVERALTPAL ) )
2020-11-10 22:59:04 +00:00
applytint = true ;
2020-11-10 19:12:46 +00:00
pick . translation = 0 ;
}
else
{
// Only look up the palette if we really want to use it (i.e. when creating a true color texture of an ART tile.)
if ( TextureType = = TT_TRUECOLOR )
{
if ( h . tintFlags & ( TINTF_ALWAYSUSEART | TINTF_USEONART ) )
{
applytint = true ;
if ( ! ( h . tintFlags & TINTF_APPLYOVERPALSWAP ) ) usepalswap = 0 ;
}
2020-11-11 19:30:25 +00:00
pick . translation = paletteid = = 0 ? 0 : TRANSLATION ( usepalette + Translation_Remap , usepalswap ) ;
2020-11-10 19:12:46 +00:00
}
else pick . translation | = 0x80000000 ;
}
if ( applytint & & h . tintFlags )
{
pick . tintFlags = h . tintFlags ;
pick . tintColor = h . tintColor ;
}
else
{
pick . tintFlags = - 1 ;
pick . tintColor = 0xffffff ;
}
pick . texture = tex ;
return true ;
}
bool PreBindTexture ( FRenderState * state , FGameTexture * & tex , EUpscaleFlags & flags , int & scaleflags , int & clampmode , int & translation , int & overrideshader )
{
TexturePick pick ;
auto t = tex ;
2020-11-12 18:26:16 +00:00
if ( tex - > GetUseType ( ) = = ETextureType : : Special ) return true ;
2021-02-27 12:30:52 +00:00
if ( PickTexture ( state , tex , translation , pick ) )
2020-11-10 19:12:46 +00:00
{
int TextureType = ( pick . translation & 0x80000000 ) ? TT_INDEXED : TT_TRUECOLOR ;
int lookuppal = pick . translation & 0x7fffffff ;
2021-02-27 12:30:52 +00:00
if ( pick . translation & 0x80000000 )
{
scaleflags | = CTF_Indexed ;
if ( state ) state - > EnableFog ( 0 ) ;
}
2020-11-10 19:12:46 +00:00
tex = pick . texture ;
translation = lookuppal ;
FVector4 addcol ( 0 , 0 , 0 , 0 ) ;
2020-11-11 06:57:25 +00:00
FVector4 modcol ( pick . basepalTint . r * ( 1.f / 255.f ) , pick . basepalTint . g * ( 1.f / 255.f ) , pick . basepalTint . b * ( 1.f / 255.f ) , 0 ) ;
2020-11-10 19:12:46 +00:00
FVector4 blendcol ( 0 , 0 , 0 , 0 ) ;
int flags = 0 ;
if ( pick . basepalTint ! = 0xffffff ) flags | = TextureManipulation : : ActiveBit ;
if ( pick . tintFlags ! = - 1 )
{
flags | = TextureManipulation : : ActiveBit ;
if ( pick . tintFlags & TINTF_COLORIZE )
{
modcol . X * = pick . tintColor . r * ( 1.f / 64.f ) ;
modcol . Y * = pick . tintColor . g * ( 1.f / 64.f ) ;
modcol . Z * = pick . tintColor . b * ( 1.f / 64.f ) ;
}
if ( pick . tintFlags & TINTF_GRAYSCALE )
modcol . W = 1.f ;
if ( pick . tintFlags & TINTF_INVERT )
flags | = TextureManipulation : : InvertBit ;
if ( pick . tintFlags & TINTF_BLENDMASK )
{
blendcol = modcol ; // WTF???, but the tinting code really uses the same color for both!
flags | = ( ( ( pick . tintFlags & TINTF_BLENDMASK ) > > 6 ) + 1 ) & TextureManipulation : : BlendMask ;
}
}
addcol . W = flags ;
if ( ( pick . translation & 0x80000000 ) & & hw_shadeinterpolate ) addcol . W + = 16384 ; // hijack a free bit in here.
state - > SetTextureColors ( & modcol . X , & addcol . X , & blendcol . X ) ;
}
return tex - > GetTexelWidth ( ) > t - > GetTexelWidth ( ) & & tex - > GetTexelHeight ( ) > t - > GetTexelHeight ( ) ; // returning 'true' means to disable programmatic upscaling.
}