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"
2022-09-30 17:06:44 +00:00
# include "gamefuncs.h"
2020-11-10 19:12:46 +00:00
2022-12-07 16:10:27 +00:00
enum ETexType
{
TT_INDEXED ,
TT_TRUECOLOR ,
} ;
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 ;
2021-07-13 07:48:14 +00:00
bool indexed ;
2020-11-10 19:12:46 +00:00
} ;
static TMap < int , TArray < HightileReplacement > > tileReplacements ;
static TMap < int , TArray < HightileReplacement > > textureReplacements ;
2021-05-30 21:00:06 +00:00
static TMap < FGameTexture * , FGameTexture * > deferredChars ;
2020-11-10 20:34:49 +00:00
2021-05-30 21:00:06 +00:00
FGameTexture * GetBaseForChar ( FGameTexture * t )
{
auto c = deferredChars . CheckKey ( t ) ;
if ( c ) return * c ;
return t ;
}
2020-11-10 20:34:49 +00:00
2021-05-30 21:00:06 +00:00
void FontCharCreated ( FGameTexture * base , FGameTexture * glyph )
2020-11-10 20:34:49 +00:00
{
2021-06-01 09:05:26 +00:00
if ( base - > GetName ( ) . IsNotEmpty ( ) )
deferredChars . Insert ( glyph , base ) ;
2020-11-10 20:34:49 +00:00
}
2020-11-10 19:12:46 +00:00
//===========================================================================
//
// Replacement textures
//
//===========================================================================
2022-12-31 14:14:29 +00:00
static void AddReplacement ( int tilenum , const HightileReplacement & replace )
2020-11-10 19:12:46 +00:00
{
2022-12-31 14:14:29 +00:00
auto & Hightiles = tileReplacements [ tilenum ] ;
2020-11-10 19:12:46 +00:00
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
//
//==========================================================================
2022-12-31 14:14:29 +00:00
void tileRemoveReplacement ( int tilenum )
2020-11-10 19:12:46 +00:00
{
2022-12-31 14:14:29 +00:00
tileReplacements . Remove ( tilenum ) ;
2020-11-10 19:12:46 +00:00
}
//===========================================================================
//
//
//
//===========================================================================
2022-12-31 14:14:29 +00:00
static HightileReplacement * FindReplacement ( FTextureID texid , int palnum , bool skybox )
2020-11-10 19:12:46 +00:00
{
2022-12-31 14:14:29 +00:00
auto Hightiles = textureReplacements . CheckKey ( texid . GetIndex ( ) ) ;
2020-11-10 19:12:46 +00:00
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
}
2022-12-31 14:14:29 +00:00
int checkTranslucentReplacement ( FTextureID texid , int pal )
2020-11-10 19:12:46 +00:00
{
FGameTexture * tex = nullptr ;
2022-12-31 14:14:29 +00:00
auto si = FindReplacement ( texid , 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 ( ) ;
}
2022-12-31 14:14:29 +00:00
FGameTexture * SkyboxReplacement ( FTextureID texid , int palnum )
2021-04-07 22:47:07 +00:00
{
2022-12-31 14:14:29 +00:00
auto hr = FindReplacement ( texid , palnum , true ) ;
2021-04-07 22:47:07 +00:00
if ( ! hr ) return nullptr ;
return hr - > image ;
}
2020-11-10 19:12:46 +00:00
//==========================================================================
//
// Processes data from .def files into the textures
//
//==========================================================================
2021-06-01 09:05:26 +00:00
void highTileSetup ( )
2020-11-10 19:12:46 +00:00
{
for ( int i = 0 ; i < MAXTILES ; i + + )
{
auto tex = tileGetTexture ( i ) ;
if ( ! tex - > isValid ( ) ) continue ;
auto Hightile = tileReplacements . CheckKey ( i ) ;
if ( ! Hightile ) continue ;
2021-06-01 09:05:26 +00:00
textureReplacements . Insert ( tex - > GetID ( ) . GetIndex ( ) , std : : move ( * Hightile ) ) ;
}
tileReplacements . Clear ( ) ;
decltype ( deferredChars ) : : Iterator it ( deferredChars ) ;
decltype ( deferredChars ) : : Pair * pair ;
while ( it . NextPair ( pair ) )
{
auto rep = textureReplacements . CheckKey ( pair - > Value - > GetID ( ) . GetIndex ( ) ) ;
if ( rep )
{
auto myrep = * rep ; // don't create copies directly from the map we're inserting in!
auto chk = textureReplacements . CheckKey ( pair - > Key - > GetID ( ) . GetIndex ( ) ) ;
if ( ! chk ) textureReplacements . Insert ( pair - > Key - > GetID ( ) . GetIndex ( ) , std : : move ( myrep ) ) ;
}
}
deferredChars . Clear ( ) ;
decltype ( textureReplacements ) : : Iterator it2 ( textureReplacements ) ;
decltype ( textureReplacements ) : : Pair * pair2 ;
while ( it2 . NextPair ( pair2 ) )
{
auto tex = TexMan . GameByIndex ( pair2 - > Key ) ;
if ( ! tex - > isValid ( ) ) continue ;
auto Hightile = & pair2 - > Value ;
if ( ! Hightile ) continue ;
2020-11-10 19:12:46 +00:00
2021-06-01 09:05:26 +00:00
FGameTexture * detailTex = nullptr , * glowTex = nullptr , * normalTex = nullptr , * specTex = nullptr ;
2020-11-10 19:12:46 +00:00
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-12-24 08:56:02 +00:00
auto imgtex = 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.
2021-12-24 08:56:02 +00:00
imgtex = MakeGameTexture ( imgtex - > GetTexture ( ) , " " , ETextureType : : Any ) ;
if ( glowTex ) imgtex - > SetGlowmap ( glowTex - > GetTexture ( ) ) ;
if ( detailTex ) imgtex - > SetDetailmap ( detailTex - > GetTexture ( ) ) ;
if ( normalTex ) imgtex - > SetNormalmap ( normalTex - > GetTexture ( ) ) ;
if ( specTex ) imgtex - > SetSpecularmap ( specTex - > GetTexture ( ) ) ;
imgtex - > SetDetailScale ( scalex , scaley ) ;
rep . image = imgtex ;
2020-11-10 19:12:46 +00:00
}
}
}
2020-11-10 20:34:49 +00:00
}
2020-11-10 19:12:46 +00:00
}
//==========================================================================
//
// Specifies a replacement texture for an ART tile.
//
//==========================================================================
2022-12-31 14:14:29 +00:00
int tileSetHightileReplacement ( int tilenum , int palnum , FTextureID texid , float alphacut , float xscale , float yscale , float specpower , float specfactor , bool indexed )
2020-11-10 19:12:46 +00:00
{
2022-12-07 16:10:27 +00:00
// assumes the texture was already validated.
2020-11-10 19:12:46 +00:00
HightileReplacement replace = { } ;
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 ;
2021-07-13 07:48:14 +00:00
replace . indexed = indexed ;
2020-11-10 19:12:46 +00:00
replace . palnum = ( uint16_t ) palnum ;
2022-12-31 14:14:29 +00:00
AddReplacement ( tilenum , replace ) ;
2020-11-10 19:12:46 +00:00
return 0 ;
}
//==========================================================================
//
// Define the faces of a skybox
//
//==========================================================================
2022-12-31 14:14:29 +00:00
int tileSetSkybox ( int tilenum , int palnum , FString * facenames , bool indexed )
2020-11-10 19:12:46 +00:00
{
2022-12-31 14:14:29 +00:00
if ( ( uint32_t ) tilenum > = ( uint32_t ) MAXTILES ) return - 1 ;
2021-04-07 22:47:07 +00:00
if ( ( uint32_t ) palnum > = ( uint32_t ) MAXPALOOKUPS ) return - 1 ;
2020-11-10 19:12:46 +00:00
2022-12-31 14:14:29 +00:00
auto tex = tileGetTexture ( tilenum ) ;
2020-11-10 19:12:46 +00:00
if ( tex - > GetTexelWidth ( ) < = 0 | | tex - > GetTexelHeight ( ) < = 0 )
{
2022-12-31 14:14:29 +00:00
Printf ( " Warning: defined skybox replacement for empty tile %d. " , tilenum ) ;
2020-11-10 19:12:46 +00:00
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
{
2023-10-08 07:15:32 +00:00
FTextureID texid = TexMan . CheckForTexture ( facenames [ i ] . GetChars ( ) , ETextureType : : Any , FTextureManager : : TEXMAN_TryAny | FTextureManager : : TEXMAN_ForceLookup ) ;
2020-11-10 19:12:46 +00:00
if ( ! texid . isValid ( ) )
{
2022-12-31 14:14:29 +00:00
Printf ( " %s: Skybox image for tile %d does not exist or is invalid \n " , facenames [ i ] . GetChars ( ) , tilenum ) ;
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 ;
2021-07-13 07:48:14 +00:00
replace . indexed = indexed ;
2020-11-10 19:12:46 +00:00
replace . palnum = ( uint16_t ) palnum ;
2022-12-31 14:14:29 +00:00
AddReplacement ( tilenum , replace ) ;
2020-11-10 19:12:46 +00:00
return 0 ;
}
//===========================================================================
//
// Picks a texture for rendering for a given tilenum/palette combination
//
//===========================================================================
2021-06-01 09:29:39 +00:00
bool PickTexture ( FGameTexture * tex , int paletteid , TexturePick & pick , bool wantindexed )
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
2021-05-24 11:16:50 +00:00
int usepalette = 0 , useremap = 0 ;
if ( ! IsLuminosityTranslation ( paletteid ) )
{
usepalette = paletteid = = 0 ? 0 : GetTranslationType ( paletteid ) - Translation_Remap ;
useremap = GetTranslationIndex ( paletteid ) ;
}
2021-06-01 09:29:39 +00:00
int TextureType = wantindexed ? TT_INDEXED : TT_TRUECOLOR ;
2020-11-10 19:12:46 +00:00
pick . translation = paletteid ;
pick . basepalTint = 0xffffff ;
2021-05-24 11:16:50 +00:00
auto & h = lookups . tables [ useremap ] ;
2020-11-10 19:12:46 +00:00
bool applytint = false ;
// Canvas textures must be treated like hightile replacements in the following code.
2021-01-29 12:05:52 +00:00
2021-05-24 11:16:50 +00:00
int hipalswap = usepalette > = 0 ? useremap : 0 ;
2021-01-29 12:05:52 +00:00
auto rep = ( hw_hightile & & ! ( h . tintFlags & TINTF_ALWAYSUSEART ) ) ? FindReplacement ( tex - > GetID ( ) , hipalswap , false ) : nullptr ;
2021-06-01 09:29:39 +00:00
if ( rep | | tex - > GetTexture ( ) - > isHardwareCanvas ( ) )
2020-11-10 19:12:46 +00:00
{
if ( rep )
{
2021-04-07 22:47:07 +00:00
tex = rep - > image ;
2020-11-10 19:12:46 +00:00
}
2021-07-16 21:38:58 +00:00
if ( rep & & rep - > indexed & & TextureType = = TT_INDEXED )
{
pick . translation | = 0x80000000 ;
}
else if ( ! rep | | ! rep - > indexed )
2021-07-13 07:48:14 +00:00
{
if ( usepalette > 0 )
{
// 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 | | rep - > palnum ! = hipalswap | | ( h . tintFlags & TINTF_APPLYOVERALTPAL ) )
applytint = true ;
if ( ! IsLuminosityTranslation ( paletteid ) ) pick . translation = 0 ;
}
2020-11-10 19:12:46 +00:00
}
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 ;
2021-05-24 11:16:50 +00:00
if ( ! ( h . tintFlags & TINTF_APPLYOVERPALSWAP ) ) useremap = 0 ;
2020-11-10 19:12:46 +00:00
}
2023-11-09 18:22:32 +00:00
pick . translation = IsLuminosityTranslation ( paletteid ) ? paletteid : paletteid = = 0 ? 0 : TRANSLATION ( usepalette + Translation_Remap , useremap ) . index ( ) ;
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-06-01 09:29:39 +00:00
bool foggy = state & & ( state - > GetFogColor ( ) & 0xffffff ) ;
if ( PickTexture ( tex , translation , pick , hw_int_useindexedcolortextures & & ! foggy ) )
2020-11-10 19:12:46 +00:00
{
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 ) ;
2021-12-24 08:56:02 +00:00
int tmflags = 0 ;
2020-11-10 19:12:46 +00:00
2021-12-24 08:56:02 +00:00
if ( pick . basepalTint ! = 0xffffff ) tmflags | = TextureManipulation : : ActiveBit ;
2020-11-10 19:12:46 +00:00
if ( pick . tintFlags ! = - 1 )
{
2021-12-24 08:56:02 +00:00
tmflags | = TextureManipulation : : ActiveBit ;
2020-11-10 19:12:46 +00:00
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 )
2021-12-24 08:56:02 +00:00
tmflags | = TextureManipulation : : InvertBit ;
2020-11-10 19:12:46 +00:00
if ( pick . tintFlags & TINTF_BLENDMASK )
{
blendcol = modcol ; // WTF???, but the tinting code really uses the same color for both!
2021-12-24 08:56:02 +00:00
tmflags | = ( ( ( pick . tintFlags & TINTF_BLENDMASK ) > > 6 ) + 1 ) & TextureManipulation : : BlendMask ;
2020-11-10 19:12:46 +00:00
}
}
2021-12-24 08:56:02 +00:00
addcol . W = ( float ) tmflags ;
2020-11-10 19:12:46 +00:00
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.
}