2019-10-12 06:54:06 +00:00
/*
* * buildtexture . cpp
* * Handling Build textures
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 2019 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"
2020-05-24 05:58:56 +00:00
# include "buildtiles.h"
2019-10-12 06:54:06 +00:00
# include "image.h"
2019-12-17 22:25:07 +00:00
2019-10-14 21:11:01 +00:00
# include "baselayer.h"
# include "palette.h"
# include "m_crc32.h"
# include "build.h"
2019-12-11 22:41:05 +00:00
# include "gamecontrol.h"
2020-05-24 05:58:56 +00:00
# include "palettecontainer.h"
2020-05-24 17:12:22 +00:00
# include "texturemanager.h"
2019-10-12 06:54:06 +00:00
2019-10-12 20:49:46 +00:00
enum
2019-10-12 06:54:06 +00:00
{
2019-10-12 20:49:46 +00:00
MAXARTFILES_BASE = 200 ,
MAXARTFILES_TOTAL = 220
2019-10-12 06:54:06 +00:00
} ;
2019-10-14 21:11:01 +00:00
2019-10-21 07:22:55 +00:00
BuildTiles TileFiles ;
2019-10-14 21:11:01 +00:00
//==========================================================================
//
//
//
//==========================================================================
2020-05-24 10:31:38 +00:00
picanm_t tileConvertAnimFormat ( int32_t const picanimraw , int * lo , int * to )
2019-10-12 06:54:06 +00:00
{
2020-05-24 19:19:33 +00:00
// Unpack a 4 byte packed anim descriptor into something more accessible
2019-10-12 20:49:46 +00:00
picanm_t anm ;
2019-10-17 07:42:11 +00:00
anm . num = picanimraw & 63 ;
2020-05-24 10:31:38 +00:00
* lo = ( int8_t ) ( ( picanimraw > > 8 ) & 255 ) ;
* to = ( int8_t ) ( ( picanimraw > > 16 ) & 255 ) ;
2019-10-17 07:42:11 +00:00
anm . sf = ( ( picanimraw > > 24 ) & 15 ) | ( picanimraw & 192 ) ;
anm . extra = ( picanimraw > > 28 ) & 15 ;
2019-10-12 20:49:46 +00:00
return anm ;
}
2019-10-12 06:54:06 +00:00
2019-10-12 20:49:46 +00:00
//==========================================================================
//
// Base class for Build tile textures
// This needs a few subclasses for different use cases.
//
//==========================================================================
2020-04-10 16:17:26 +00:00
int FTileTexture : : CopyPixels ( FBitmap * bmp , int conversion )
2019-10-12 06:54:06 +00:00
{
2019-10-14 21:11:01 +00:00
TArray < uint8_t > buffer ;
2020-04-10 16:17:26 +00:00
bmp - > Create ( Width , Height ) ;
2020-05-24 17:12:22 +00:00
auto ppix = GetRawData ( ) ;
if ( ppix )
{
2020-04-10 16:17:26 +00:00
bmp - > CopyPixelData ( 0 , 0 , ppix , Width , Height , Height , 1 , 0 , GPalette . BaseColors ) ;
2020-05-24 17:12:22 +00:00
}
2020-04-10 16:17:26 +00:00
return 0 ;
2019-10-12 20:49:46 +00:00
}
2020-04-10 16:17:26 +00:00
TArray < uint8_t > FTileTexture : : CreatePalettedPixels ( int conversion )
2019-10-12 20:49:46 +00:00
{
2020-05-24 17:12:22 +00:00
TArray < uint8_t > buffer ( Width * Height , true ) ;
auto p = GetRawData ( ) ;
if ( p ) memcpy ( buffer . Data ( ) , p , buffer . Size ( ) ) ;
else memset ( buffer . Data ( ) , 0 , buffer . Size ( ) ) ;
return buffer ;
2019-10-12 20:49:46 +00:00
}
//==========================================================================
//
2020-04-10 16:17:26 +00:00
//
2019-10-12 20:49:46 +00:00
//
//==========================================================================
2020-05-25 21:59:07 +00:00
static FGameTexture * GetTileTexture ( const char * name , const TArray < uint8_t > & backingstore , uint32_t offset , int width , int height )
2019-10-12 20:49:46 +00:00
{
2020-05-24 17:12:22 +00:00
auto tex = new FArtTile ( backingstore , offset , width , height ) ;
2020-05-24 19:19:33 +00:00
auto p = & backingstore [ offset ] ;
auto siz = width * height ;
for ( int i = 0 ; i < siz ; i + + , p + + )
{
// move transparent color to index 0 to get in line with the rest of the texture management.
if ( * p = = 0 ) * p = 255 ;
else if ( * p = = 255 ) * p = 0 ;
}
2020-05-24 11:53:27 +00:00
2019-10-12 20:49:46 +00:00
if ( tex )
2019-10-12 06:54:06 +00:00
{
2020-05-25 21:59:07 +00:00
return MakeGameTexture ( new FImageTexture ( tex ) , name , ETextureType : : Any ) ;
2019-10-12 06:54:06 +00:00
}
2020-04-10 16:17:26 +00:00
return nullptr ;
2019-10-12 20:49:46 +00:00
}
2019-10-12 06:54:06 +00:00
2020-05-24 21:26:47 +00:00
void BuildTiles : : Init ( )
{
2020-05-25 21:59:07 +00:00
Placeholder = TexMan . GameByIndex ( 0 ) ;
2020-05-24 21:26:47 +00:00
for ( auto & tile : tiledata )
{
tile . texture = Placeholder ;
tile . backup = Placeholder ;
tile . picanm = { } ;
tile . RotTile = { - 1 , - 1 } ;
tile . replacement = ReplacementType : : Art ;
}
}
2019-10-14 21:11:01 +00:00
//==========================================================================
//
//
//
//==========================================================================
2020-05-25 21:59:07 +00:00
void BuildTiles : : AddTile ( int tilenum , FGameTexture * tex , bool permap )
2019-10-14 21:11:01 +00:00
{
2020-05-24 21:26:47 +00:00
assert ( ! tex - > GetID ( ) . isValid ( ) ) ; // must not be added yet.
2020-05-25 21:59:07 +00:00
TexMan . AddGameTexture ( tex ) ;
2020-05-24 13:02:20 +00:00
tiledata [ tilenum ] . texture = tex ;
if ( ! permap ) tiledata [ tilenum ] . backup = tex ;
2019-10-14 21:11:01 +00:00
}
2019-10-12 06:54:06 +00:00
//===========================================================================
//
// AddTiles
//
// Adds all the tiles in an artfile to the texture manager.
//
//===========================================================================
2020-05-24 20:37:50 +00:00
void BuildTiles : : AddTiles ( int firsttile , TArray < uint8_t > & RawData , const char * mapname )
2019-10-12 06:54:06 +00:00
{
const uint8_t * tiles = RawData . Data ( ) ;
// int numtiles = LittleLong(((uint32_t *)tiles)[1]); // This value is not reliable
2019-10-14 21:11:01 +00:00
int tilestart = LittleLong ( ( ( int * ) tiles ) [ 2 ] ) ;
int tileend = LittleLong ( ( ( int * ) tiles ) [ 3 ] ) ;
2019-10-12 06:54:06 +00:00
const uint16_t * tilesizx = & ( ( const uint16_t * ) tiles ) [ 8 ] ;
const uint16_t * tilesizy = & tilesizx [ tileend - tilestart + 1 ] ;
const uint32_t * picanm = ( const uint32_t * ) & tilesizy [ tileend - tilestart + 1 ] ;
const uint8_t * tiledata = ( const uint8_t * ) & picanm [ tileend - tilestart + 1 ] ;
2019-10-14 21:11:01 +00:00
if ( firsttile ! = - 1 )
{
tileend = tileend - tilestart + firsttile ;
tilestart = firsttile ;
}
2019-10-12 06:54:06 +00:00
for ( int i = tilestart ; i < = tileend ; + + i )
{
int pic = i - tilestart ;
int width = LittleShort ( tilesizx [ pic ] ) ;
int height = LittleShort ( tilesizy [ pic ] ) ;
uint32_t anm = LittleLong ( picanm [ pic ] ) ;
int size = width * height ;
if ( width < = 0 | | height < = 0 ) continue ;
2020-04-08 19:22:30 +00:00
FString texname ;
if ( mapname ) texname . Format ( " maptile_%s_%05d " , mapname , i ) ;
else texname . Format ( " #%05d " , i ) ;
auto tex = GetTileTexture ( texname , RawData , uint32_t ( tiledata - tiles ) , width , height ) ;
2019-10-14 21:11:01 +00:00
AddTile ( i , tex ) ;
2020-05-24 11:53:27 +00:00
int leftoffset , topoffset ;
2020-05-24 18:30:02 +00:00
this - > tiledata [ i ] . picanmbackup = this - > tiledata [ i ] . picanm = tileConvertAnimFormat ( anm , & leftoffset , & topoffset ) ;
2020-05-24 11:53:27 +00:00
tex - > SetOffsets ( leftoffset , topoffset ) ;
2019-10-12 06:54:06 +00:00
tiledata + = size ;
}
}
2020-05-24 06:47:45 +00:00
//===========================================================================
//
// Replacement textures
//
//===========================================================================
void BuildTiles : : AddReplacement ( int picnum , const HightileReplacement & replace )
{
auto & Hightiles = tiledata [ picnum ] . Hightiles ;
for ( auto & ht : Hightiles )
{
if ( replace . palnum = = ht . palnum & & ( replace . faces [ 1 ] = = nullptr ) = = ( ht . faces [ 1 ] = = nullptr ) )
{
ht = replace ;
return ;
}
}
Hightiles . Push ( replace ) ;
}
void BuildTiles : : DeleteReplacement ( int picnum , int palnum )
{
auto & Hightiles = tiledata [ picnum ] . Hightiles ;
for ( int i = Hightiles . Size ( ) - 1 ; i > = 0 ; i - - )
{
if ( Hightiles [ i ] . palnum = = palnum ) Hightiles . Delete ( i ) ;
}
}
//===========================================================================
//
//
//
//===========================================================================
HightileReplacement * BuildTiles : : FindReplacement ( int picnum , int palnum , bool skybox )
{
auto & Hightiles = tiledata [ picnum ] . Hightiles ;
for ( ; ; )
{
for ( auto & rep : Hightiles )
{
if ( rep . palnum = = palnum & & ( rep . faces [ 1 ] ! = nullptr ) = = skybox ) return & rep ;
}
if ( ! palnum | | palnum > = MAXPALOOKUPS - RESERVEDPALS ) break ;
palnum = 0 ;
}
return nullptr ; // no replacement found
}
2019-10-12 06:54:06 +00:00
//===========================================================================
//
// CountTiles
//
// Returns the number of tiles provided by an artfile
//
//===========================================================================
2019-10-14 21:11:01 +00:00
int CountTiles ( const char * fn , const uint8_t * RawData )
2019-10-12 06:54:06 +00:00
{
int version = LittleLong ( * ( uint32_t * ) RawData ) ;
if ( version ! = 1 )
{
2020-04-11 21:45:45 +00:00
Printf ( " %s: Invalid art file version. Must be 1, got %d \n " , fn , version ) ;
2019-10-12 06:54:06 +00:00
return 0 ;
}
int tilestart = LittleLong ( ( ( uint32_t * ) RawData ) [ 2 ] ) ;
int tileend = LittleLong ( ( ( uint32_t * ) RawData ) [ 3 ] ) ;
2019-10-14 21:11:01 +00:00
if ( ( unsigned ) tilestart > = MAXUSERTILES | | ( unsigned ) tileend > = MAXUSERTILES )
{
2020-04-11 21:45:45 +00:00
Printf ( " %s: Invalid tilestart or tileend \n " , fn ) ;
2019-10-14 21:11:01 +00:00
return 0 ;
}
if ( tileend < tilestart )
{
2020-04-11 21:45:45 +00:00
Printf ( " %s: tileend < tilestart \n " , fn ) ;
2019-10-14 21:11:01 +00:00
return 0 ;
}
2019-10-12 06:54:06 +00:00
return tileend > = tilestart ? tileend - tilestart + 1 : 0 ;
}
2020-03-29 12:01:46 +00:00
//===========================================================================
//
// InvalidateTile
//
//===========================================================================
2019-10-18 09:37:07 +00:00
2019-10-21 07:22:55 +00:00
void BuildTiles : : InvalidateTile ( int num )
2019-10-18 09:37:07 +00:00
{
if ( ( unsigned ) num < MAXTILES )
{
2020-05-24 13:02:20 +00:00
auto tex = tiledata [ num ] . texture ;
2020-05-25 21:59:07 +00:00
tex - > GetTexture ( ) - > SystemTextures . Clean ( ) ;
2020-05-24 06:47:45 +00:00
for ( auto & rep : tiledata [ num ] . Hightiles )
2019-10-18 09:37:07 +00:00
{
2019-10-18 17:06:57 +00:00
for ( auto & reptex : rep . faces )
2019-10-18 09:37:07 +00:00
{
2020-05-25 21:59:07 +00:00
if ( reptex ) reptex - > GetTexture ( ) - > SystemTextures . Clean ( ) ;
2019-10-18 09:37:07 +00:00
}
}
2020-05-24 19:19:33 +00:00
tiledata [ num ] . rawCache . data . Clear ( ) ;
2019-10-18 09:37:07 +00:00
}
}
2020-03-29 12:01:46 +00:00
//===========================================================================
//
// MakeCanvas
//
// Turns texture into a canvas (i.e. camera texture)
//
//===========================================================================
void BuildTiles : : MakeCanvas ( int tilenum , int width , int height )
{
2020-05-24 13:02:20 +00:00
auto canvas = ValidateCustomTile ( tilenum , ReplacementType : : Canvas ) ;
2020-05-24 17:12:22 +00:00
canvas - > SetSize ( width , height ) ;
2020-03-29 12:01:46 +00:00
}
2019-10-12 06:54:06 +00:00
//===========================================================================
//
// LoadArtFile
//
2019-10-17 07:42:11 +00:00
// Returns the number of tiles found.
2019-10-12 06:54:06 +00:00
//
// let's load everything into memory on startup.
2019-10-17 07:42:11 +00:00
// Even for Ion Fury this will merely add 80 MB, because the engine already needs to cache the data, albeit in a compressed-per-lump form,
2019-10-12 06:54:06 +00:00
// so its 100MB art file will only have a partial impact on memory.
//
//===========================================================================
2020-05-24 20:37:50 +00:00
int BuildTiles : : LoadArtFile ( const char * fn , const char * mapname , int firsttile )
2019-10-12 06:54:06 +00:00
{
auto old = FindFile ( fn ) ;
2019-10-12 20:49:46 +00:00
if ( old > = ArtFiles . Size ( ) ) // Do not process if already loaded.
2019-10-12 06:54:06 +00:00
{
2020-04-11 21:54:33 +00:00
FileReader fr = fileSystem . OpenFileReader ( fn ) ;
2019-10-12 06:54:06 +00:00
if ( fr . isOpen ( ) )
{
auto artdata = fr . Read ( ) ;
const uint8_t * artptr = artdata . Data ( ) ;
if ( artdata . Size ( ) > 16 )
{
2019-10-18 22:26:43 +00:00
if ( memcmp ( artptr , " BUILDART " , 8 ) = = 0 )
{
artdata . Delete ( 0 , 8 ) ;
}
2019-10-12 06:54:06 +00:00
// Only load the data if the header is present
2019-10-14 21:11:01 +00:00
if ( CountTiles ( fn , artptr ) > 0 )
2019-10-12 06:54:06 +00:00
{
2019-10-14 21:11:01 +00:00
auto file = new BuildArtFile ;
2020-05-24 21:26:47 +00:00
ArtFiles . Push ( file ) ;
2019-10-14 21:11:01 +00:00
file - > filename = fn ;
file - > RawData = std : : move ( artdata ) ;
2020-05-24 20:37:50 +00:00
AddTiles ( firsttile , file - > RawData , mapname ) ;
2019-10-12 06:54:06 +00:00
}
}
}
2019-10-14 21:11:01 +00:00
else
{
2020-04-11 21:45:45 +00:00
//Printf("%s: file not found\n", fn);
2019-10-15 18:02:37 +00:00
return - 1 ;
2019-10-14 21:11:01 +00:00
}
2019-10-12 06:54:06 +00:00
}
2019-10-15 18:02:37 +00:00
return 0 ;
2019-10-12 06:54:06 +00:00
}
2019-10-14 21:11:01 +00:00
//==========================================================================
//
//
//
//==========================================================================
2019-10-21 07:22:55 +00:00
void BuildTiles : : LoadArtSet ( const char * filename )
2019-10-12 06:54:06 +00:00
{
2019-10-14 21:11:01 +00:00
for ( int index = 0 ; index < MAXARTFILES_BASE ; index + + )
2019-10-12 20:49:46 +00:00
{
2019-10-14 21:11:01 +00:00
FStringf fn ( filename , index ) ;
2020-05-24 20:37:50 +00:00
LoadArtFile ( fn , nullptr ) ;
2019-10-12 20:49:46 +00:00
}
2019-12-11 22:41:05 +00:00
for ( auto & addart : addedArt )
{
2020-05-24 20:37:50 +00:00
LoadArtFile ( addart , nullptr ) ;
2019-12-11 22:41:05 +00:00
}
2019-10-12 06:54:06 +00:00
}
2019-10-14 21:11:01 +00:00
//==========================================================================
//
// Checks if a custom tile has alredy been added to the list.
// For each tile index there may only be one replacement and its
// type may never change!
//
2019-10-17 07:42:11 +00:00
// All these uses will need some review further down the line so that the texture manager's content is immutable.
//
2019-10-14 21:11:01 +00:00
//==========================================================================
2020-05-25 21:59:07 +00:00
FGameTexture * BuildTiles : : ValidateCustomTile ( int tilenum , ReplacementType type )
2019-10-12 20:49:46 +00:00
{
2019-10-14 21:11:01 +00:00
if ( tilenum < 0 | | tilenum > = MAXTILES ) return nullptr ;
2020-05-24 21:26:47 +00:00
auto & td = tiledata [ tilenum ] ;
if ( td . texture ! = td . backup ) return nullptr ; // no mucking around with map tiles.
auto tile = td . texture ;
auto reptype = td . replacement ;
if ( reptype = = type ) return tile ; // already created
if ( reptype > ReplacementType : : Art ) return nullptr ; // different custom type - cannot replace again.
2019-10-14 21:11:01 +00:00
FTexture * replacement = nullptr ;
2020-05-24 13:02:20 +00:00
if ( type = = ReplacementType : : Writable )
2019-10-12 20:49:46 +00:00
{
2019-10-17 07:42:11 +00:00
// Creates an empty writable tile.
// Current use cases are:
// Camera textures (should be made to be creatable by the hardware renderer instead of falling back on the software renderer.)
// thumbnails for savegame and loadgame (should bypass the texture manager entirely.)
// view tilting in the software renderer (this should just use a local buffer instead of relying on the texture manager.)
// Movie playback (like thumbnails this should bypass the texture manager entirely.)
// Blood's 'lens' effect (apparently MP only) - combination of a camera texture with a distortion map - should be made a shader effect to be applied to the camera texture.
2020-04-10 16:17:26 +00:00
replacement = new FImageTexture ( new FWritableTile ) ;
2019-10-12 20:49:46 +00:00
}
2020-05-24 13:02:20 +00:00
else if ( type = = ReplacementType : : Restorable )
2019-10-14 21:11:01 +00:00
{
// This is for modifying an existing tile.
2019-11-24 12:59:36 +00:00
// It only gets used for the crosshair and a few specific effects:
2019-10-14 21:11:01 +00:00
// A) the fire in Blood.
// B) the pin display in Redneck Rampage's bowling lanes.
2019-11-24 12:59:36 +00:00
// C) Exhumed's menu plus one special effect tile.
2019-10-17 07:42:11 +00:00
// All of these effects should probably be redone without actual texture hacking...
2020-05-24 17:12:22 +00:00
if ( tile - > GetTexelWidth ( ) = = 0 | | tile - > GetTexelHeight ( ) = = 0 ) return nullptr ; // The base must have a size for this to work.
2019-10-14 21:11:01 +00:00
// todo: invalidate hardware textures for tile.
2020-05-25 21:59:07 +00:00
replacement = new FImageTexture ( new FRestorableTile ( tile - > GetTexture ( ) - > GetImage ( ) ) ) ;
2019-10-14 21:11:01 +00:00
}
2020-05-24 13:02:20 +00:00
else if ( type = = ReplacementType : : Canvas )
2020-03-29 12:01:46 +00:00
{
2020-05-25 21:59:07 +00:00
replacement = new FCanvasTexture ( 0 , 0 ) ;
2020-03-29 12:01:46 +00:00
}
2019-10-14 21:11:01 +00:00
else return nullptr ;
2020-05-25 21:59:07 +00:00
auto reptex = MakeGameTexture ( replacement , " " , ETextureType : : Any ) ;
AddTile ( tilenum , reptex ) ;
return reptex ;
2019-10-12 06:54:06 +00:00
}
2019-10-15 18:02:37 +00:00
//==========================================================================
//
// global interface
//
//==========================================================================
2019-10-21 07:22:55 +00:00
int32_t BuildTiles : : artLoadFiles ( const char * filename )
2019-10-15 18:02:37 +00:00
{
TileFiles . LoadArtSet ( filename ) ;
2019-10-16 18:39:59 +00:00
memset ( gotpic , 0 , sizeof ( gotpic ) ) ;
2019-10-15 18:02:37 +00:00
return 0 ;
}
2019-10-14 22:19:31 +00:00
//==========================================================================
//
// Creates a tile for displaying custom content
//
//==========================================================================
2019-10-21 07:22:55 +00:00
uint8_t * BuildTiles : : tileCreate ( int tilenum , int width , int height )
2019-10-14 22:19:31 +00:00
{
if ( width < = 0 | | height < = 0 ) return nullptr ;
2020-05-24 13:02:20 +00:00
auto tex = ValidateCustomTile ( tilenum , ReplacementType : : Writable ) ;
2019-10-14 22:19:31 +00:00
if ( tex = = nullptr ) return nullptr ;
2020-05-25 21:59:07 +00:00
auto wtex = static_cast < FWritableTile * > ( tex - > GetTexture ( ) - > GetImage ( ) ) ;
2020-04-10 16:17:26 +00:00
if ( ! wtex - > ResizeImage ( width , height ) ) return nullptr ;
tex - > SetSize ( width , height ) ;
2020-05-24 17:12:22 +00:00
return wtex - > GetRawData ( ) ;
2019-10-14 22:19:31 +00:00
}
//==========================================================================
//
// Makes a tile writable - only used for a handful of special cases
// (todo: Investigate how to get rid of this)
//
//==========================================================================
2020-05-24 17:12:22 +00:00
uint8_t * BuildTiles : : tileMakeWritable ( int num )
2019-10-14 22:19:31 +00:00
{
2020-05-24 13:02:20 +00:00
auto tex = ValidateCustomTile ( num , ReplacementType : : Restorable ) ;
2020-05-25 21:59:07 +00:00
auto wtex = static_cast < FWritableTile * > ( tex - > GetTexture ( ) - > GetImage ( ) ) ;
2020-05-24 17:12:22 +00:00
return wtex ? wtex - > GetRawData ( ) : nullptr ;
2019-10-14 22:19:31 +00:00
}
2019-10-15 21:29:47 +00:00
//==========================================================================
//
2019-10-15 21:38:01 +00:00
// Returns checksum for a given tile
//
//==========================================================================
2019-12-29 21:47:40 +00:00
int32_t tileGetCRC32 ( int tileNum )
2019-10-15 21:38:01 +00:00
{
if ( ( unsigned ) tileNum > = ( unsigned ) MAXTILES ) return 0 ;
2020-05-25 21:59:07 +00:00
auto tile = dynamic_cast < FArtTile * > ( TileFiles . tiledata [ tileNum ] . texture - > GetTexture ( ) - > GetImage ( ) ) ; // only consider original ART tiles.
2020-04-10 16:17:26 +00:00
if ( ! tile ) return 0 ;
auto pixels = tile - > GetRawData ( ) ;
if ( ! pixels ) return 0 ;
auto size = tile - > GetWidth ( ) * tile - > GetHeight ( ) ;
2019-10-15 21:38:01 +00:00
if ( size = = 0 ) return 0 ;
2020-05-24 19:19:33 +00:00
// Temporarily revert the data to its original form with 255 being transparent. Otherwise the CRC won't match.
auto p = pixels ;
for ( int i = 0 ; i < size ; i + + , p + + )
{
// move transparent color to index 0 to get in line with the rest of the texture management.
if ( * p = = 0 ) * p = 255 ;
else if ( * p = = 255 ) * p = 0 ;
}
auto crc = crc32 ( 0 , ( const Bytef * ) pixels , size ) ;
// ... and back again.
p = pixels ;
for ( int i = 0 ; i < size ; i + + , p + + )
{
// move transparent color to index 0 to get in line with the rest of the texture management.
if ( * p = = 0 ) * p = 255 ;
else if ( * p = = 255 ) * p = 0 ;
}
return crc ;
2019-10-15 21:38:01 +00:00
}
//==========================================================================
//
// Import a tile from an external image.
// This has been signifcantly altered so it may not cover everything yet.
//
//==========================================================================
int tileImportFromTexture ( const char * fn , int tilenum , int alphacut , int istexture )
{
2020-05-24 17:12:22 +00:00
FTextureID texid = TexMan . CheckForTexture ( fn , ETextureType : : Any ) ;
if ( ! texid . isValid ( ) ) return - 1 ;
2020-05-25 21:59:07 +00:00
auto tex = TexMan . GetGameTexture ( texid ) ;
2020-05-24 17:12:22 +00:00
//tex->alphaThreshold = 255 - alphacut;
2019-10-15 21:38:01 +00:00
2020-05-24 17:12:22 +00:00
int32_t xsiz = tex - > GetTexelWidth ( ) , ysiz = tex - > GetTexelHeight ( ) ;
2019-10-15 21:38:01 +00:00
if ( xsiz < = 0 | | ysiz < = 0 )
return - 2 ;
2020-05-24 13:02:20 +00:00
TileFiles . tiledata [ tilenum ] . texture = tex ;
2019-10-16 20:02:27 +00:00
# pragma message("tileImportFromTexture needs rework!") // Reminder so that this place isn't forgotten.
//#if 0
2020-05-24 13:02:20 +00:00
// Does this make any difference when the texture gets *properly* inserted into the tile array? Answer: Yes, it affects how translations affect it.
2019-10-16 20:02:27 +00:00
//if (istexture)
2019-10-17 10:45:25 +00:00
tileSetHightileReplacement ( tilenum , 0 , fn , ( float ) ( 255 - alphacut ) * ( 1.f / 255.f ) , 1.0f , 1.0f , 1.0 , 1.0 , 0 ) ; // At the moment this is the only way to load the texture. The texture creation code is not ready yet for downconverting an image.
2019-10-16 20:02:27 +00:00
//#endif
2019-10-15 21:38:01 +00:00
return 0 ;
}
//==========================================================================
//
// Copies a tile into another and optionally translates its palette.
//
//==========================================================================
void tileCopy ( int tile , int source , int pal , int xoffset , int yoffset , int flags )
{
// Todo. Since I do not know if some mod needs this it's of low priority now.
// Let's get things working first.
picanm_t * picanm = nullptr ;
picanm_t * sourceanm = nullptr ;
2020-05-24 10:31:38 +00:00
int srcxo , srcyo ;
2020-05-25 21:59:07 +00:00
FGameTexture * tex ;
2019-10-15 21:38:01 +00:00
if ( pal = = - 1 & & tile = = source )
{
// Only modify the picanm info.
2020-05-24 13:02:20 +00:00
tex = tileGetTexture ( tile ) ;
2019-10-15 21:38:01 +00:00
if ( ! tex ) return ;
2020-05-24 11:53:27 +00:00
picanm = & TileFiles . tiledata [ tile ] . picanm ;
2019-10-15 21:38:01 +00:00
sourceanm = picanm ;
2020-05-24 17:12:22 +00:00
srcxo = tex - > GetTexelLeftOffset ( 0 ) ;
srcyo = tex - > GetTexelTopOffset ( 0 ) ;
2019-10-15 21:38:01 +00:00
}
else
{
if ( source = = - 1 ) source = tile ;
2020-05-24 13:02:20 +00:00
tex = tileGetTexture ( source ) ;
2019-10-15 21:38:01 +00:00
if ( ! tex ) return ;
2020-05-24 11:53:27 +00:00
sourceanm = & TileFiles . tiledata [ source ] . picanm ;
2020-05-24 17:12:22 +00:00
srcxo = tex - > GetTexelLeftOffset ( 0 ) ;
srcyo = tex - > GetTexelTopOffset ( 0 ) ;
2019-10-15 21:38:01 +00:00
2020-05-25 21:59:07 +00:00
TArray < uint8_t > buffer = tex - > GetTexture ( ) - > Get8BitPixels ( false ) ;
2019-10-15 21:38:01 +00:00
if ( pal ! = - 1 )
{
2020-05-27 20:19:02 +00:00
auto remap = lookups . getTable ( pal ) ;
2019-10-15 21:38:01 +00:00
for ( auto & pixel : buffer )
{
2020-05-23 12:40:54 +00:00
pixel = remap [ pixel ] ;
2019-10-15 21:38:01 +00:00
}
}
2020-05-25 21:59:07 +00:00
tex = MakeGameTexture ( new FImageTexture ( new FLooseTile ( buffer , tex - > GetTexelWidth ( ) , tex - > GetTexelHeight ( ) ) ) , " " , ETextureType : : Any ) ;
2020-05-24 11:53:27 +00:00
picanm = & TileFiles . tiledata [ tile ] . picanm ;
2019-10-15 21:38:01 +00:00
TileFiles . AddTile ( tile , tex ) ;
}
2020-05-24 10:31:38 +00:00
if ( xoffset ! = - 1024 ) srcxo = clamp ( xoffset , - 128 , 127 ) ;
if ( yoffset ! = - 1024 ) srcyo = clamp ( yoffset , - 128 , 127 ) ;
tex - > SetOffsets ( srcxo , srcyo ) ;
2019-10-15 21:38:01 +00:00
picanm - > sf = ( picanm - > sf & ~ PICANM_MISC_MASK ) | ( sourceanm - > sf & PICANM_MISC_MASK ) | flags ;
}
2019-10-15 21:56:29 +00:00
//==========================================================================
//
// Clear map specific ART
//
//==========================================================================
void artClearMapArt ( void )
{
2020-05-24 13:02:20 +00:00
for ( auto & td : TileFiles . tiledata )
{
td . texture = td . backup ;
td . picanm = td . picanmbackup ;
}
2020-05-24 08:30:09 +00:00
TileFiles . SetupReverseTileMap ( ) ;
2019-10-15 21:56:29 +00:00
}
//==========================================================================
//
2019-12-09 23:31:55 +00:00
// Load map specficied ART
2019-10-15 21:56:29 +00:00
//
//==========================================================================
2019-12-09 23:31:55 +00:00
static FString currentMapArt ;
2019-10-15 21:56:29 +00:00
void artSetupMapArt ( const char * filename )
{
2019-12-09 23:31:55 +00:00
if ( currentMapArt . CompareNoCase ( filename ) ) return ;
currentMapArt = filename ;
2019-10-15 21:56:29 +00:00
artClearMapArt ( ) ;
2020-05-24 21:26:47 +00:00
FString lcfilename = filename ;
lcfilename . MakeLower ( ) ;
// Re-get from the texture manager if this map's tiles have already been created.
if ( TileFiles . maptilesadded . Find ( lcfilename ) < TileFiles . maptilesadded . Size ( ) )
{
for ( int i = 0 ; i < MAXTILES ; i + + )
{
FStringf name ( " maptile_%s_%05d " , lcfilename . GetChars ( ) , i ) ;
auto texid = TexMan . CheckForTexture ( name , ETextureType : : Any ) ;
if ( texid . isValid ( ) )
{
2020-05-25 21:59:07 +00:00
TileFiles . tiledata [ i ] . texture = TexMan . GetGameTexture ( texid ) ;
2020-05-24 21:26:47 +00:00
}
}
return ;
}
TileFiles . maptilesadded . Push ( lcfilename ) ;
FStringf firstname ( " %s_00.art " , lcfilename . GetChars ( ) ) ;
2020-04-11 21:54:33 +00:00
auto fr = fileSystem . OpenFileReader ( firstname ) ;
2019-10-15 21:56:29 +00:00
if ( ! fr . isOpen ( ) ) return ;
2020-05-24 13:02:20 +00:00
for ( auto & td : TileFiles . tiledata )
{
td . picanmbackup = td . picanm ;
}
2019-10-15 21:56:29 +00:00
2020-05-24 21:26:47 +00:00
2019-10-15 21:56:29 +00:00
for ( bssize_t i = 0 ; i < MAXARTFILES_TOTAL - MAXARTFILES_BASE ; i + + )
{
FStringf fullname ( " %s_%02d.art " , filename , i ) ;
2020-05-24 20:37:50 +00:00
TileFiles . LoadArtFile ( fullname , filename ) ;
2019-10-15 21:56:29 +00:00
}
2020-05-24 08:30:09 +00:00
TileFiles . SetupReverseTileMap ( ) ;
2019-10-15 21:56:29 +00:00
}
//==========================================================================
//
2019-10-15 21:29:47 +00:00
//
//
//==========================================================================
void tileDelete ( int tile )
{
2020-05-24 13:02:20 +00:00
TileFiles . TextureToTile . Remove ( tileGetTexture ( tile ) ) ;
2020-05-25 21:59:07 +00:00
TileFiles . tiledata [ tile ] . texture = TileFiles . tiledata [ tile ] . backup = TexMan . GameByIndex ( 0 ) ;
2019-10-15 21:29:47 +00:00
vox_undefine ( tile ) ;
md_undefinetile ( tile ) ;
2019-10-17 10:45:25 +00:00
tileRemoveReplacement ( tile ) ;
}
//==========================================================================
//
//
//
//==========================================================================
2019-10-15 21:29:47 +00:00
2019-10-17 10:45:25 +00:00
void tileRemoveReplacement ( int tile )
{
2020-05-24 19:19:33 +00:00
if ( ( unsigned ) tile > = MAXTILES ) return ;
2020-05-24 06:47:45 +00:00
TileFiles . DeleteReplacements ( tile ) ;
2019-10-15 21:29:47 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
void tileSetDummy ( int tile , int width , int height )
{
if ( width = = 0 | | height = = 0 )
{
tileDelete ( tile ) ;
}
else if ( width > 0 & & height > 0 )
{
2020-05-25 21:59:07 +00:00
auto dtile = MakeGameTexture ( new FImageTexture ( new FDummyTile ( width , height ) ) , " " , ETextureType : : Any ) ;
2019-10-15 21:29:47 +00:00
TileFiles . AddTile ( tile , dtile ) ;
}
}
2019-10-15 21:56:29 +00:00
//==========================================================================
//
//
//
//==========================================================================
2019-10-21 07:22:55 +00:00
int BuildTiles : : findUnusedTile ( void )
2019-10-15 21:56:29 +00:00
{
static int lastUnusedTile = MAXUSERTILES - 1 ;
for ( ; lastUnusedTile > = 0 ; - - lastUnusedTile )
{
2020-05-24 13:02:20 +00:00
auto tex = tileGetTexture ( lastUnusedTile ) ;
2020-05-29 13:55:08 +00:00
if ( ! tex | | ! tex - > isValid ( ) ) return lastUnusedTile ;
2019-10-15 21:56:29 +00:00
}
return - 1 ;
}
2020-04-10 16:17:26 +00:00
//==========================================================================
//
// fixme: This *really* needs to be done by rotating the texture coordinates,
// not by creating an entirely new texture.
// Unfortunately it's in all the wrong place in the rendering code so it
// has to wait for later.
//
//==========================================================================
2019-10-21 07:22:55 +00:00
int BuildTiles : : tileCreateRotated ( int tileNum )
2019-10-15 21:56:29 +00:00
{
if ( ( unsigned ) tileNum > = MAXTILES ) return tileNum ;
2020-05-24 13:02:20 +00:00
auto tex = tileGetTexture ( tileNum ) ;
2020-05-24 17:12:22 +00:00
if ( ! tex | | tex - > GetTexelWidth ( ) < = 0 | | tex - > GetTexelHeight ( ) < = 0 ) return tileNum ;
2020-05-25 21:59:07 +00:00
TArray < uint8_t > buffer = tex - > GetTexture ( ) - > Get8BitPixels ( false ) ;
2020-05-24 17:12:22 +00:00
TArray < uint8_t > dbuffer ( tex - > GetTexelWidth ( ) * tex - > GetTexelHeight ( ) , true ) ;
2019-10-15 21:56:29 +00:00
auto src = buffer . Data ( ) ;
auto dst = dbuffer . Data ( ) ;
2020-05-24 17:12:22 +00:00
auto width = tex - > GetTexelWidth ( ) ;
auto height = tex - > GetTexelHeight ( ) ;
for ( int x = 0 ; x < width ; + + x )
2019-10-15 21:56:29 +00:00
{
2020-05-24 17:12:22 +00:00
int xofs = width - x - 1 ;
int yofs = height * x ;
2019-10-15 21:56:29 +00:00
2020-05-24 17:12:22 +00:00
for ( int y = 0 ; y < height ; + + y )
* ( dst + y * width + xofs ) = * ( src + y + yofs ) ;
2019-10-15 21:56:29 +00:00
}
2020-05-25 21:59:07 +00:00
auto dtex = MakeGameTexture ( new FImageTexture ( new FLooseTile ( dbuffer , tex - > GetTexelHeight ( ) , tex - > GetTexelWidth ( ) ) ) , " " , ETextureType : : Any ) ;
2019-10-15 21:56:29 +00:00
int index = findUnusedTile ( ) ;
2020-05-24 13:02:20 +00:00
bool mapart = TileFiles . tiledata [ tileNum ] . texture ! = TileFiles . tiledata [ tileNum ] . backup ;
2019-10-15 21:56:29 +00:00
TileFiles . AddTile ( index , dtex , mapart ) ;
return index ;
}
2019-10-16 19:16:40 +00:00
//==========================================================================
//
//
//
//==========================================================================
2019-10-21 07:22:55 +00:00
void BuildTiles : : CloseAll ( )
2019-10-16 19:16:40 +00:00
{
ArtFiles . DeleteAndClear ( ) ;
}
2019-10-17 10:45:25 +00:00
//==========================================================================
//
// Specifies a replacement texture for an ART tile.
//
//==========================================================================
2020-05-24 17:12:22 +00:00
int tileSetHightileReplacement ( int picnum , int palnum , const char * filename , float alphacut , float xscale , float yscale , float specpower , float specfactor , uint8_t flags )
2019-10-17 10:45:25 +00:00
{
2020-05-24 17:12:22 +00:00
if ( ( uint32_t ) picnum > = ( uint32_t ) MAXTILES ) return - 1 ;
if ( ( uint32_t ) palnum > = ( uint32_t ) MAXPALOOKUPS ) return - 1 ;
2019-10-17 10:45:25 +00:00
2020-05-24 13:02:20 +00:00
auto tex = tileGetTexture ( picnum ) ;
2020-05-24 17:12:22 +00:00
if ( tex - > GetTexelWidth ( ) < = 0 | | tex - > GetTexelHeight ( ) < = 0 )
2019-10-17 10:45:25 +00:00
{
2020-04-11 21:45:45 +00:00
Printf ( " Warning: defined hightile replacement for empty tile %d. " , picnum ) ;
2019-10-17 10:45:25 +00:00
return - 1 ; // cannot add replacements to empty tiles, must create one beforehand
}
HightileReplacement replace = { } ;
2020-05-24 17:12:22 +00:00
FTextureID texid = TexMan . CheckForTexture ( filename , ETextureType : : Any ) ;
2020-05-24 19:19:33 +00:00
if ( ! texid . isValid ( ) )
2019-10-17 10:45:25 +00:00
{
2020-04-11 21:45:45 +00:00
Printf ( " %s: Replacement for tile %d does not exist or is invalid \n " , filename , picnum ) ;
2019-10-17 10:45:25 +00:00
return - 1 ;
}
2020-05-24 17:12:22 +00:00
2020-05-25 21:59:07 +00:00
replace . faces [ 0 ] = TexMan . GetGameTexture ( texid ) ;
2020-05-24 17:12:22 +00:00
if ( replace . faces [ 0 ] = = nullptr )
2020-05-24 19:19:33 +00:00
replace . alphacut = min ( alphacut , 1.f ) ;
2019-10-17 10:45:25 +00:00
replace . scale = { xscale , yscale } ;
2020-05-24 17:12:22 +00:00
replace . specpower = specpower ; // currently unused
replace . specfactor = specfactor ; // currently unused
replace . flags = flags ;
2019-10-17 18:29:58 +00:00
replace . palnum = ( uint16_t ) palnum ;
2020-05-24 06:47:45 +00:00
TileFiles . AddReplacement ( picnum , replace ) ;
2020-05-24 17:12:22 +00:00
return 0 ;
2019-10-17 10:45:25 +00:00
}
//==========================================================================
//
// Define the faces of a skybox
//
//==========================================================================
int tileSetSkybox ( int picnum , int palnum , const char * * facenames , int flags )
{
if ( ( uint32_t ) picnum > = ( uint32_t ) MAXTILES ) return - 1 ;
if ( ( uint32_t ) palnum > = ( uint32_t ) MAXPALOOKUPS ) return - 1 ;
2020-05-24 13:02:20 +00:00
auto tex = tileGetTexture ( picnum ) ;
2020-05-24 17:12:22 +00:00
if ( tex - > GetTexelWidth ( ) < = 0 | | tex - > GetTexelHeight ( ) < = 0 )
2019-10-17 10:45:25 +00:00
{
2020-04-11 21:45:45 +00:00
Printf ( " Warning: defined skybox replacement for empty tile %d. " , picnum ) ;
2019-10-17 10:45:25 +00:00
return - 1 ; // cannot add replacements to empty tiles, must create one beforehand
}
HightileReplacement replace = { } ;
for ( auto & face : replace . faces )
{
2020-05-24 19:19:33 +00:00
FTextureID texid = TexMan . CheckForTexture ( * facenames , ETextureType : : Any ) ;
if ( ! texid . isValid ( ) )
2019-10-17 10:45:25 +00:00
{
2020-04-11 21:45:45 +00:00
Printf ( " %s: Skybox image for tile %d does not exist or is invalid \n " , * facenames , picnum ) ;
2019-10-17 10:45:25 +00:00
return - 1 ;
}
2020-05-25 21:59:07 +00:00
face = TexMan . GetGameTexture ( texid ) ;
2019-10-17 10:45:25 +00:00
}
replace . flags = flags ;
2019-10-17 18:29:58 +00:00
replace . palnum = ( uint16_t ) palnum ;
2020-05-24 06:47:45 +00:00
TileFiles . AddReplacement ( picnum , replace ) ;
2019-10-17 10:45:25 +00:00
return 0 ;
}
//==========================================================================
//
// Remove a replacement
//
//==========================================================================
int tileDeleteReplacement ( int picnum , int palnum )
{
if ( ( uint32_t ) picnum > = ( uint32_t ) MAXTILES ) return - 1 ;
if ( ( uint32_t ) palnum > = ( uint32_t ) MAXPALOOKUPS ) return - 1 ;
2020-05-24 06:47:45 +00:00
TileFiles . DeleteReplacement ( picnum , palnum ) ;
2019-10-17 10:45:25 +00:00
return 0 ;
}
2020-01-28 11:42:17 +00:00
//==========================================================================
//
// Copy a block of a tile.
// Only used by RR's bowling lane.
//
//==========================================================================
void tileCopySection ( int tilenum1 , int sx1 , int sy1 , int xsiz , int ysiz , int tilenum2 , int sx2 , int sy2 )
{
2020-04-05 21:30:37 +00:00
int xsiz1 = tileWidth ( tilenum1 ) ;
int ysiz1 = tileHeight ( tilenum1 ) ;
int xsiz2 = tileWidth ( tilenum2 ) ;
int ysiz2 = tileHeight ( tilenum2 ) ;
2020-01-28 11:42:17 +00:00
if ( xsiz1 > 0 & & ysiz1 > 0 & & xsiz2 > 0 & & ysiz2 > 0 )
{
auto p1 = tilePtr ( tilenum1 ) ;
auto p2 = tileData ( tilenum2 ) ;
if ( p2 = = nullptr ) return ; // Error: Destination is not writable.
int x1 = sx1 ;
int x2 = sx2 ;
for ( int i = 0 ; i < xsiz ; i + + )
{
int y1 = sy1 ;
int y2 = sy2 ;
for ( int j = 0 ; j < ysiz ; j + + )
{
if ( x2 > = 0 & & y2 > = 0 & & x2 < xsiz2 & & y2 < ysiz2 )
{
auto src = p1 [ x1 * ysiz1 + y1 ] ;
2020-04-11 22:04:02 +00:00
if ( src ! = TRANSPARENT_INDEX )
2020-01-28 11:42:17 +00:00
p2 [ x2 * ysiz2 + y2 ] = src ;
}
y1 + + ;
y2 + + ;
if ( y1 > = ysiz1 ) y1 = 0 ;
}
x1 + + ;
x2 + + ;
if ( x1 > = xsiz1 ) x1 = 0 ;
}
}
TileFiles . InvalidateTile ( tilenum2 ) ;
}
2019-10-17 10:45:25 +00:00
2019-10-16 19:16:40 +00:00
2019-10-16 18:39:59 +00:00
TileSiz tilesiz ;
PicAnm picanm ;