diff --git a/include/ishaders.h b/include/ishaders.h index 234165f..7d08d28 100644 --- a/include/ishaders.h +++ b/include/ishaders.h @@ -110,6 +110,8 @@ virtual qtexture_t* getBump() const = 0; virtual qtexture_t* getSpecular() const = 0; // get shader name virtual const char* getName() const = 0; +virtual const char* getWadName() const = 0; +virtual void setWadName( const char* name ) = 0; virtual bool IsInUse() const = 0; virtual void SetInUse( bool bInUse ) = 0; // get the editor flags (QER_NOCARVE QER_TRANS) diff --git a/libs/os/path.h b/libs/os/path.h index b764666..5b87a4d 100644 --- a/libs/os/path.h +++ b/libs/os/path.h @@ -72,6 +72,12 @@ inline bool path_equal( const char* path, const char* other ){ #endif } +/// \brief Returns true if \p path and \p other refer to the same file or directory, case insensitively. +/// O(n) +inline bool path_equal_i( const char* path, const char* other ){ + return string_equal_nocase( path, other ); +} + /// \brief Returns true if the first \p n bytes of \p path and \p other form paths that refer to the same file or directory. /// If the paths are UTF-8 encoded, [\p path, \p path + \p n) must be a complete path. /// O(n) @@ -175,6 +181,12 @@ inline bool extension_equal( const char* extension, const char* other ){ return path_equal( extension, other ); } +/// \brief Returns true if \p extension is of the same type as \p other, case insensitively. +/// O(n) +inline bool extension_equal_i( const char* extension, const char* other ){ + return path_equal_i( extension, other ); +} + template class MatchFileExtension { diff --git a/plugins/Makefile b/plugins/Makefile index ca34600..87e9306 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -1,11 +1,15 @@ all: mkdir -p ../build/plugins cd archivezip && $(MAKE) + cd archivewad && $(MAKE) + cd archivepak && $(MAKE) cd entity && $(MAKE) cd image && $(MAKE) + cd imagehl && $(MAKE) cd iqmmodel && $(MAKE) cd mapq3 && $(MAKE) cd model && $(MAKE) + cd matsys && $(MAKE) cd shaders && $(MAKE) cd vfspk3 && $(MAKE) cd brushexport && $(MAKE) @@ -13,12 +17,16 @@ all: clean: -rm -rf ../build/plugins cd archivezip && $(MAKE) clean + cd archivewad && $(MAKE) clean + cd archivepak && $(MAKE) clean cd entity && $(MAKE) clean cd image && $(MAKE) clean + cd imagehl && $(MAKE) clean cd iqmmodel && $(MAKE) clean cd mapq3 && $(MAKE) clean cd model && $(MAKE) clean cd shaders && $(MAKE) clean + cd matsys && $(MAKE) clean cd vfspk3 && $(MAKE) clean cd brushexport && $(MAKE) clean cd prtview && $(MAKE) clean diff --git a/plugins/archivepak/Makefile b/plugins/archivepak/Makefile new file mode 100644 index 0000000..3a863f6 --- /dev/null +++ b/plugins/archivepak/Makefile @@ -0,0 +1,23 @@ +# WorldSpawn Plugin Makefile + +PLUGIN_CFLAGS=$(CFLAGS) -I../../include -I../../libs -fPIC -fvisibility=hidden +PLUGIN_LDFLAGS=$(LDFLAGS) -shared + +DO_CXX=$(CXX) $(PLUGIN_CFLAGS) -o $@ -c $< + +.cpp.o: + $(DO_CXX) + +WS_OBJS = \ + archive.o plugin.o + +# binary target +../../build/plugins/libarchivepak.so: $(WS_OBJS) + $(CXX) -o $@ $(WS_OBJS) $(PLUGIN_LDFLAGS) + +# object files +archive.o: archive.cpp archive.h +plugin.o: plugin.cpp + +clean: + -rm -f *.o ../../build/plugins/libarchivepak.so diff --git a/plugins/archivepak/archive.cpp b/plugins/archivepak/archive.cpp new file mode 100644 index 0000000..ee19333 --- /dev/null +++ b/plugins/archivepak/archive.cpp @@ -0,0 +1,208 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "archive.h" + +#include "idatastream.h" +#include "cmdlib.h" +#include "bytestreamutils.h" +#include +#include "stream/filestream.h" + +#include "iarchive.h" + +#include "archivelib.h" + + +#include +#include "string/string.h" +#include "fs_filesystem.h" + +inline void buffer_findreplace( char* buffer, char find, char replace ){ + while ( *buffer != '\0' ) + { + if ( *buffer == find ) { + *buffer = replace; + } + ++buffer; + } +} + +#include "pak.h" + +class PakArchive : public Archive +{ +class PakRecord +{ +public: +PakRecord( unsigned int position, unsigned int stream_size ) + : m_position( position ), m_stream_size( stream_size ){ +} +unsigned int m_position; +unsigned int m_stream_size; +}; +typedef GenericFileSystem PakFileSystem; +PakFileSystem m_filesystem; +FileInputStream m_pakfile; +CopiedString m_name; + +public: + +PakArchive( const char* name ) + : m_pakfile( name ), m_name( name ){ + if ( !m_pakfile.failed() ) { + pakheader_t header; + + m_pakfile.read( reinterpret_cast( header.magic ), 4 ); + header.diroffset = istream_read_uint32_le( m_pakfile ); + header.dirsize = istream_read_uint32_le( m_pakfile ); + + if ( strncmp( header.magic, "PACK", 4 ) == 0 ) { + m_pakfile.seek( header.diroffset ); + + for ( unsigned int i = 0; i < header.dirsize; i += sizeof( pakentry_t ) ) + { + pakentry_t entry; + + m_pakfile.read( reinterpret_cast( entry.filename ), 0x38 ); + entry.offset = istream_read_uint32_le( m_pakfile ); + entry.size = istream_read_uint32_le( m_pakfile ); + + buffer_findreplace( entry.filename, '\\', '/' ); + + PakFileSystem::entry_type& file = m_filesystem[entry.filename]; + if ( !file.is_directory() ) { + globalOutputStream() << "Warning: pak archive " << makeQuoted( m_name.c_str() ) << " contains duplicated file: " << makeQuoted( entry.filename ) << "\n"; + } + else + { + file = new PakRecord( entry.offset, entry.size ); + } + } + } + } +} + +~PakArchive(){ + for ( PakFileSystem::iterator i = m_filesystem.begin(); i != m_filesystem.end(); ++i ) + delete i->second.file(); +} + +void release(){ + delete this; +} +ArchiveFile* openFile( const char* name ){ + PakFileSystem::iterator i = m_filesystem.find( name ); + if ( i != m_filesystem.end() && !i->second.is_directory() ) { + PakRecord* file = i->second.file(); + return StoredArchiveFile::create( name, m_name.c_str(), file->m_position, file->m_stream_size, file->m_stream_size ); + } + return 0; +} +virtual ArchiveTextFile* openTextFile( const char* name ){ + PakFileSystem::iterator i = m_filesystem.find( name ); + if ( i != m_filesystem.end() && !i->second.is_directory() ) { + PakRecord* file = i->second.file(); + return StoredArchiveTextFile::create( name, m_name.c_str(), file->m_position, file->m_stream_size ); + } + return 0; +} +bool containsFile( const char* name ){ + PakFileSystem::iterator i = m_filesystem.find( name ); + return i != m_filesystem.end() && !i->second.is_directory(); +} +void forEachFile( VisitorFunc visitor, const char* root ){ + m_filesystem.traverse( visitor, root ); +} +}; + + +Archive* OpenArchive( const char* name ){ + return new PakArchive( name ); +} + +#if 0 + +class TestArchive +{ +public: +TestArchive(){ + Archive* archive = OpenArchive( "c:/quake3/baseq3/pak0.pak" ); + ArchiveFile* file = archive->openFile( "gfx/palette.lmp" ); + if ( file != 0 ) { + char buffer[1024]; + file->getInputStream().read( (InputStream::byte_type*)buffer, 1024 ); + file->release(); + } + archive->release(); +} +}; + +TestArchive g_test; + +#endif + +#if 0 + +class TestArchive +{ +class TestVisitor : public Archive::IVisitor +{ +public: +void visit( const char* name ){ + int bleh = 0; +} +}; +public: +TestArchive(){ + { + Archive* archive = OpenArchive( "" ); + archive->release(); + } + { + Archive* archive = OpenArchive( "NONEXISTANTFILE" ); + archive->release(); + } + { + Archive* archive = OpenArchive( "c:/quake/id1/pak0.pak" ); + ArchiveFile* file = archive->openFile( "gfx/palette.lmp" ); + if ( file != 0 ) { + char buffer[1024]; + file->getInputStream().read( (InputStream::byte_type*)buffer, 1024 ); + file->release(); + } + TestVisitor visitor; + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 0 ), "" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFiles, 0 ), "progs/" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFiles, 0 ), "maps/" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFiles, 1 ), "sound/ambience/" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "sound/" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eDirectories, 1 ), "sound/" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 2 ), "sound/" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 2 ), "" ); + archive->release(); + } +} +}; + +TestArchive g_test; + +#endif diff --git a/plugins/archivepak/archive.h b/plugins/archivepak/archive.h new file mode 100644 index 0000000..309cef5 --- /dev/null +++ b/plugins/archivepak/archive.h @@ -0,0 +1,23 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +class Archive; +Archive* OpenArchive( const char* name ); diff --git a/plugins/archivepak/pak.h b/plugins/archivepak/pak.h new file mode 100644 index 0000000..2205156 --- /dev/null +++ b/plugins/archivepak/pak.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_PAK_H ) +#define INCLUDED_PAK_H + +struct pakheader_t +{ + char magic[4]; // Name of the new WAD format ("PACK") + unsigned int diroffset; // Position of WAD directory from start of file + unsigned int dirsize; // Number of entries * 0x40 (64 char) +}; + +struct pakentry_t +{ + char filename[0x38]; // Name of the file, Unix style, with extension, 50 chars, padded with '\0'. + unsigned int offset; // Position of the entry in PACK file + unsigned int size; // Size of the entry in PACK file +}; + +#endif diff --git a/plugins/archivepak/plugin.cpp b/plugins/archivepak/plugin.cpp new file mode 100644 index 0000000..489af0b --- /dev/null +++ b/plugins/archivepak/plugin.cpp @@ -0,0 +1,53 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "iarchive.h" + +#include "debugging/debugging.h" +#include "modulesystem/singletonmodule.h" + +#include "archive.h" + +class ArchivePakAPI +{ +_QERArchiveTable m_archivepak; +public: +typedef _QERArchiveTable Type; +STRING_CONSTANT( Name, "pak" ); + +ArchivePakAPI(){ + m_archivepak.m_pfnOpenArchive = &OpenArchive; +} +_QERArchiveTable* getTable(){ + return &m_archivepak; +} +}; + +typedef SingletonModule ArchivePakModule; + +ArchivePakModule g_ArchivePakModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){ + initialiseModule( server ); + + g_ArchivePakModule.selfRegister(); +} diff --git a/plugins/archivewad/Makefile b/plugins/archivewad/Makefile new file mode 100644 index 0000000..0e1adfa --- /dev/null +++ b/plugins/archivewad/Makefile @@ -0,0 +1,23 @@ +# WorldSpawn Plugin Makefile + +PLUGIN_CFLAGS=$(CFLAGS) -I../../include -I../../libs -fPIC -fvisibility=hidden +PLUGIN_LDFLAGS=$(LDFLAGS) -shared + +DO_CXX=$(CXX) $(PLUGIN_CFLAGS) -o $@ -c $< + +.cpp.o: + $(DO_CXX) + +WS_OBJS = \ + archive.o plugin.o + +# binary target +../../build/plugins/libarchivewad.so: $(WS_OBJS) + $(CXX) -o $@ $(WS_OBJS) $(PLUGIN_LDFLAGS) + +# object files +archive.o: archive.cpp archive.h +plugin.o: plugin.cpp + +clean: + -rm -f *.o ../../build/plugins/libarchivewad.so diff --git a/plugins/archivewad/archive.cpp b/plugins/archivewad/archive.cpp new file mode 100644 index 0000000..357f9cd --- /dev/null +++ b/plugins/archivewad/archive.cpp @@ -0,0 +1,211 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "archive.h" + +#include "idatastream.h" +#include "bytestreamutils.h" +#include +#include "stream/filestream.h" + +#include "iarchive.h" + +#include "archivelib.h" + + +#include +#include "string/string.h" + +#include "wad.h" + +class WadArchive final : public Archive +{ + class wad_record_t + { + public: + wad_record_t( unsigned int position, unsigned int stream_size, unsigned int file_size ) + : m_position( position ), m_stream_size( stream_size ), m_file_size( file_size ) + {} + unsigned int m_position; + unsigned int m_stream_size; + unsigned int m_file_size; + }; + + enum EWadVersion + { + eNotValid, + eWAD2, + eWAD3, + }; + + typedef std::map files_t; + files_t m_files; + CopiedString m_name; + FileInputStream m_wadfile; + + EWadVersion wad_version( const char* identification ){ + if ( strncmp( identification, "WAD2", 4 ) == 0 ) { + return eWAD2; + } + if ( strncmp( identification, "WAD3", 4 ) == 0 ) { + return eWAD3; + } + return eNotValid; + } + + const char* type_for_version( EWadVersion version ){ + switch ( version ) + { + case eWAD2: + return ".mip"; + case eWAD3: + return ".hlw"; + default: + break; + } + return ""; + } + + int miptex_type_for_version( EWadVersion version ){ + switch ( version ) + { + case eWAD2: + return TYP_MIPTEX; + case eWAD3: + return 67; + default: + break; + } + return -1; + } + +public: + WadArchive( const char* name ) + : m_name( name ), m_wadfile( name ){ + if ( !m_wadfile.failed() ) { + wadinfo_t wadinfo; + istream_read_wadinfo( m_wadfile, wadinfo ); + + EWadVersion version = wad_version( wadinfo.identification ); + int miptexType = miptex_type_for_version( version ); + + if ( version != eNotValid ) { + m_wadfile.seek( wadinfo.infotableofs ); + + for ( int i = 0; i < wadinfo.numlumps; ++i ) + { + char buffer[32]; + lumpinfo_t lumpinfo; + istream_read_lumpinfo( m_wadfile, lumpinfo ); + if ( lumpinfo.type == miptexType ) { + strcpy( buffer, "textures/" ); + strcat( buffer, lumpinfo.name ); + strcat( buffer, type_for_version( version ) ); + m_files.insert( files_t::value_type( buffer, wad_record_t( lumpinfo.filepos, lumpinfo.disksize, lumpinfo.size ) ) ); + } + } + } + } + } + + void release(){ + delete this; + } + ArchiveFile* openFile( const char* name ){ + files_t::iterator i = m_files.find( name ); + if ( i != m_files.end() ) { + return StoredArchiveFile::create( name, m_name.c_str(), i->second.m_position, i->second.m_stream_size, i->second.m_file_size ); + } + return 0; + } + virtual ArchiveTextFile* openTextFile( const char* name ){ + files_t::iterator i = m_files.find( name ); + if ( i != m_files.end() ) { + return StoredArchiveTextFile::create( name, m_name.c_str(), i->second.m_position, i->second.m_stream_size ); + } + return 0; + } + bool containsFile( const char* name ){ + return m_files.find( name ) != m_files.end(); + } + void forEachFile( VisitorFunc visitor, const char* root ){ + if ( root[0] == '\0' ) { + if ( visitor.directory( "textures/", 1 ) ) { + return; + } + } + else if ( strcmp( root, "textures/" ) != 0 ) { + return; + } + + for ( files_t::iterator i = m_files.begin(); i != m_files.end(); ++i ) + visitor.file( i->first.c_str() ); + } +}; + + +Archive* OpenArchive( const char* name ){ + return new WadArchive( name ); +} + +#if 0 + +class TestArchive +{ + class TestVisitor : public Archive::IVisitor + { + public: + void visit( const char* name ){ + int bleh = 0; + } + }; +public: + TestArchive(){ + { + Archive* archive = OpenArchive( "" ); + archive->release(); + } + { + Archive* archive = OpenArchive( "NONEXISTANTFILE" ); + archive->release(); + } + { + Archive* archive = OpenArchive( "c:/quake/id1/quake101.wad" ); + ArchiveFile* file = archive->openFile( "textures/sky1.mip" ); + if ( file != 0 ) { + unsigned char* buffer = new unsigned char[file->size()]; + file->getInputStream().read( (InputStream::byte_type*)buffer, file->size() ); + delete[] buffer; + file->release(); + } + TestVisitor visitor; + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 0 ), "" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 0 ), "textures/" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "textures/" ); + archive->release(); + } + } +}; + +TestArchive g_test; + +#endif diff --git a/plugins/archivewad/archive.h b/plugins/archivewad/archive.h new file mode 100644 index 0000000..309cef5 --- /dev/null +++ b/plugins/archivewad/archive.h @@ -0,0 +1,23 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +class Archive; +Archive* OpenArchive( const char* name ); diff --git a/plugins/archivewad/plugin.cpp b/plugins/archivewad/plugin.cpp new file mode 100644 index 0000000..66d0fed --- /dev/null +++ b/plugins/archivewad/plugin.cpp @@ -0,0 +1,53 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "iarchive.h" + +#include "debugging/debugging.h" +#include "modulesystem/singletonmodule.h" + +#include "archive.h" + +class ArchiveWadAPI +{ + _QERArchiveTable m_archivewad; +public: + typedef _QERArchiveTable Type; + STRING_CONSTANT( Name, "wad" ); + + ArchiveWadAPI(){ + m_archivewad.m_pfnOpenArchive = &OpenArchive; + } + _QERArchiveTable* getTable(){ + return &m_archivewad; + } +}; + +typedef SingletonModule ArchiveWadModule; + +ArchiveWadModule g_ArchiveWadModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){ + initialiseModule( server ); + + g_ArchiveWadModule.selfRegister(); +} diff --git a/plugins/archivewad/wad.h b/plugins/archivewad/wad.h new file mode 100644 index 0000000..617900c --- /dev/null +++ b/plugins/archivewad/wad.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_WAD_H ) +#define INCLUDED_WAD_H + +#include "bytestreamutils.h" +#include "idatastream.h" + +#define CMP_NONE 0 +#define CMP_LZSS 1 + +#define TYP_NONE 0 +#define TYP_LABEL 1 + +#define TYP_LUMPY 64 // 64 + grab command number +#define TYP_PALETTE 64 +#define TYP_QTEX 65 +#define TYP_QPIC 66 +#define TYP_SOUND 67 +#define TYP_MIPTEX 68 + +typedef struct +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +inline void istream_read_wadinfo( InputStream& istream, wadinfo_t& wadinfo ){ + istream.read( reinterpret_cast( wadinfo.identification ), 4 ); + wadinfo.numlumps = istream_read_int32_le( istream ); + wadinfo.infotableofs = istream_read_int32_le( istream ); +} + +inline void istream_read_lumpinfo( InputStream& istream, lumpinfo_t& lumpinfo ){ + lumpinfo.filepos = istream_read_int32_le( istream ); + lumpinfo.disksize = istream_read_int32_le( istream ); + lumpinfo.size = istream_read_int32_le( istream ); + lumpinfo.type = istream_read_byte( istream ); + lumpinfo.compression = istream_read_byte( istream ); + lumpinfo.pad1 = istream_read_byte( istream ); + lumpinfo.pad2 = istream_read_byte( istream ); + istream.read( reinterpret_cast( lumpinfo.name ), 16 ); +} + +#endif diff --git a/plugins/imagehl/Makefile b/plugins/imagehl/Makefile new file mode 100644 index 0000000..353867b --- /dev/null +++ b/plugins/imagehl/Makefile @@ -0,0 +1,28 @@ +# WorldSpawn Plugin Makefile + +GLIB_CFLAGS=$(shell pkg-config --cflags gtk+-2.0) -DGTK_TARGET=2 +GLIB_LDFLAGS=$(shell pkg-config --libs gtk+-2.0) + +PLUGIN_CFLAGS=$(CFLAGS) $(GLIB_CFLAGS) -I../../include -I../../libs -fPIC -fvisibility=hidden +PLUGIN_LDFLAGS=$(LDFLAGS) $(GLIB_LDFLAGS) -shared + +DO_CXX=$(CXX) $(PLUGIN_CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +.cpp.o: + $(DO_CXX) + +WS_OBJS = \ + hlw.o imagehl.o mip.o sprite.o + +# binary target +../../build/plugins/libimagehl.so: $(WS_OBJS) + $(CXX) -o $@ $(WS_OBJS) ../../libs/libuilib.a ../../libs/libgtkutil.a $(PLUGIN_LDFLAGS) + +# object files +hlw.o: hlw.cpp hlw.h +imagehl.o: imagehl.cpp +mip.o: mip.cpp mip.h +sprite.o: sprite.cpp sprite.h + +clean: + -rm -f *.o ../../build/plugins/libimagehl.so diff --git a/plugins/imagehl/hlw.cpp b/plugins/imagehl/hlw.cpp new file mode 100644 index 0000000..7327554 --- /dev/null +++ b/plugins/imagehl/hlw.cpp @@ -0,0 +1,153 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// by Hydra - hydra@hydras-world.com +// +// HLW = Half-Life-WAD, I don't know if the actual in data in the WAD files +// has it's own name, so I'm just calling the individal textures .HLW files :) +// +// Thanks to the guys that made Wally for releasing an example WAD loader. +// without it this would not have been possible. + +#include "hlw.h" + +#include +#include +#include + +typedef unsigned char byte; + +#include "ifilesystem.h" + +#include "imagelib.h" + + +/* + ============================================================================ + + HLW IMAGE + + HalfLife WAD files contain files that look like this: + + Mip section + First mip + Mip header + First mip (width * height) + Second mip (width * height / 4) + Third mip (width * height / 16) + Fourth mip (width * height / 64) + Palette size (WORD) + Palette (Palette size * 3) + Padding (WORD) + + ============================================================================ + */ + +#define GET_MIP_DATA_SIZE( WIDTH, HEIGHT ) ( sizeof( WAD3_MIP ) + ( WIDTH * HEIGHT ) + ( WIDTH * HEIGHT / 4 ) + ( WIDTH * HEIGHT / 16 ) + ( WIDTH * HEIGHT / 64 ) ) + +typedef struct +{ + char name[16]; + unsigned int width, height; + unsigned int offsets[4]; // four mip maps stored +} WAD3_MIP, *LPWAD3_MIP; + +/* + ========================================================= + + HLW LOADING + + Hydra: this code isn't bullet proof and probably won't + like corrupt WAD files, but it works for now. + + TODO: make it more robust. + ========================================================= + */ + +/* + ============= + LoadHLW + ============= + */ + +Image* LoadHLWBuff( byte* buffer ){ + byte *buf_p; + unsigned long mipdatasize; + int columns, rows; + byte *pixbuf; + int row, column; + byte *palette; + LPWAD3_MIP lpMip; + unsigned char red, green, blue, alphabyte; + + lpMip = (LPWAD3_MIP)buffer; //!\todo Make endian-safe. + + mipdatasize = GET_MIP_DATA_SIZE( lpMip->width,lpMip->height ); + + palette = buffer + mipdatasize + 2; + + buf_p = buffer + lpMip->offsets[0]; + + columns = lpMip->width; + rows = lpMip->height; + + RGBAImage* image = new RGBAImage( columns, rows ); + + for ( row = 0; row < rows; row++ ) + { + pixbuf = image->getRGBAPixels() + row * columns * 4; + + for ( column = 0; column < columns; column++ ) + { + int palIndex; + + palIndex = *buf_p++; + + red = *( palette + ( palIndex * 3 ) ); + green = *( palette + ( palIndex * 3 ) + 1 ); + blue = *( palette + ( palIndex * 3 ) + 2 ); + + // HalfLife engine makes pixels that are BLUE transparent. + // So show them that way in the editor. + if ( blue == 0xff && red == 0x00 && green == 0x00 ) { + alphabyte = 0x00; + blue = 0x00; // don't set the resulting pixel to blue + } + else + { + alphabyte = 0xff; + } + + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + + *pixbuf++ = alphabyte; + } + } + + return image; +} + +Image* LoadHLW( ArchiveFile& file ){ + ScopedArchiveBuffer buffer( file ); + return LoadHLWBuff( buffer.buffer ); +} diff --git a/plugins/imagehl/hlw.h b/plugins/imagehl/hlw.h new file mode 100644 index 0000000..3313823 --- /dev/null +++ b/plugins/imagehl/hlw.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_HLW_H ) +#define INCLUDED_HLW_H + +class Image; +class ArchiveFile; + +Image* LoadHLW( ArchiveFile& file ); + +#endif diff --git a/plugins/imagehl/imagehl.cpp b/plugins/imagehl/imagehl.cpp new file mode 100644 index 0000000..dfdf98b --- /dev/null +++ b/plugins/imagehl/imagehl.cpp @@ -0,0 +1,104 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "debugging/debugging.h" +#include "ifilesystem.h" +#include "iimage.h" + +#include "hlw.h" +#include "mip.h" +#include "sprite.h" + +#include "modulesystem/singletonmodule.h" + + +class ImageDependencies : public GlobalFileSystemModuleRef +{ +}; + +class ImageHLWAPI +{ +_QERPlugImageTable m_imagehlw; +public: +typedef _QERPlugImageTable Type; +STRING_CONSTANT( Name, "hlw" ); + +ImageHLWAPI(){ + m_imagehlw.loadImage = LoadHLW; +} +_QERPlugImageTable* getTable(){ + return &m_imagehlw; +} +}; + +typedef SingletonModule ImageHLWModule; + +ImageHLWModule g_ImageHLWModule; + + +class ImageMipAPI +{ +_QERPlugImageTable m_imagemip; +public: +typedef _QERPlugImageTable Type; +STRING_CONSTANT( Name, "mip" ); + +ImageMipAPI(){ + m_imagemip.loadImage = LoadMIP; +} +_QERPlugImageTable* getTable(){ + return &m_imagemip; +} +}; + +typedef SingletonModule ImageMipModule; + +ImageMipModule g_ImageMipModule; + + +class ImageSpriteAPI +{ +_QERPlugImageTable m_imagesprite; +public: +typedef _QERPlugImageTable Type; +STRING_CONSTANT( Name, "spr" ); + +ImageSpriteAPI(){ + m_imagesprite.loadImage = LoadIDSP; +} +_QERPlugImageTable* getTable(){ + return &m_imagesprite; +} +}; + +typedef SingletonModule ImageSpriteModule; + +ImageSpriteModule g_ImageSpriteModule; + + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){ + initialiseModule( server ); + + g_ImageHLWModule.selfRegister(); + g_ImageMipModule.selfRegister(); + g_ImageSpriteModule.selfRegister(); +} diff --git a/plugins/imagehl/imagehl.txt b/plugins/imagehl/imagehl.txt new file mode 100644 index 0000000..e442e64 --- /dev/null +++ b/plugins/imagehl/imagehl.txt @@ -0,0 +1,30 @@ +ImageHL +======= + +Coding by Dominic Clifton - Hydra - hydra@hydras-world.com + +What is it ? +------------ + +This GTKRadiant 1.2+ plugin handles the loading of textures from .WAD files. +I'll refer to these textures as .HLW files, even though they don't have any +extension when they're stored in the .WAD file itself. + +You need a VFS plugin to go with this plugin that can open and read .WAD files +My VFSWAD plugin does just this. + +Developer Notes +--------------- + +The project file will copy the compiled DLL file and this .TXT file to +"$(HLRADIANTDIR)modules" so make sure you have that environment variable +defined. + +For my GTKRadiant 1.2 HalfLife game pack files I use the directory: +"E:\games\HalfLife\Tools\GTKR12N". Under which there are the directories +"modules" and "plugins" + +Credits +------- +Thanks to the guys that made Wally for releasing an example WAD loader. +without it this would not have been possible. diff --git a/plugins/imagehl/mip.cpp b/plugins/imagehl/mip.cpp new file mode 100644 index 0000000..a9d5a71 --- /dev/null +++ b/plugins/imagehl/mip.cpp @@ -0,0 +1,203 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mip.h" + +#include +#include +#include + +typedef unsigned char byte; + +#include "ifilesystem.h" + +#include "imagelib.h" +#include "bytestreamutils.h" + +/* + ============================================================================ + + MIP IMAGE + + Quake WAD files contain miptex files that look like this: + + Mip section + First mip + Mip header + First mip (width * height) + Second mip (width * height / 4) + Third mip (width * height / 16) + Fourth mip (width * height / 64) + + ============================================================================ + */ + +#define GET_MIP_DATA_SIZE( WIDTH, HEIGHT ) ( sizeof( WAD3_MIP ) + ( WIDTH * HEIGHT ) + ( WIDTH * HEIGHT / 4 ) + ( WIDTH * HEIGHT / 16 ) + ( WIDTH * HEIGHT / 64 ) ) + +const int MIP_NAME_LENGTH = 16; +const int MIP_MIPMAP_COUNT = 4; +typedef struct +{ + char name[MIP_NAME_LENGTH]; + unsigned int width, height; + unsigned int offsets[MIP_MIPMAP_COUNT]; // four mip maps stored +} WAD3_MIP, *LPWAD3_MIP; + +static const byte quakepalette[768] = +{ + 0x00,0x00,0x00, 0x0f,0x0f,0x0f, 0x1f,0x1f,0x1f, 0x2f,0x2f,0x2f, + 0x3f,0x3f,0x3f, 0x4b,0x4b,0x4b, 0x5b,0x5b,0x5b, 0x6b,0x6b,0x6b, + 0x7b,0x7b,0x7b, 0x8b,0x8b,0x8b, 0x9b,0x9b,0x9b, 0xab,0xab,0xab, + 0xbb,0xbb,0xbb, 0xcb,0xcb,0xcb, 0xdb,0xdb,0xdb, 0xeb,0xeb,0xeb, + 0x0f,0x0b,0x07, 0x17,0x0f,0x0b, 0x1f,0x17,0x0b, 0x27,0x1b,0x0f, + 0x2f,0x23,0x13, 0x37,0x2b,0x17, 0x3f,0x2f,0x17, 0x4b,0x37,0x1b, + 0x53,0x3b,0x1b, 0x5b,0x43,0x1f, 0x63,0x4b,0x1f, 0x6b,0x53,0x1f, + 0x73,0x57,0x1f, 0x7b,0x5f,0x23, 0x83,0x67,0x23, 0x8f,0x6f,0x23, + 0x0b,0x0b,0x0f, 0x13,0x13,0x1b, 0x1b,0x1b,0x27, 0x27,0x27,0x33, + 0x2f,0x2f,0x3f, 0x37,0x37,0x4b, 0x3f,0x3f,0x57, 0x47,0x47,0x67, + 0x4f,0x4f,0x73, 0x5b,0x5b,0x7f, 0x63,0x63,0x8b, 0x6b,0x6b,0x97, + 0x73,0x73,0xa3, 0x7b,0x7b,0xaf, 0x83,0x83,0xbb, 0x8b,0x8b,0xcb, + 0x00,0x00,0x00, 0x07,0x07,0x00, 0x0b,0x0b,0x00, 0x13,0x13,0x00, + 0x1b,0x1b,0x00, 0x23,0x23,0x00, 0x2b,0x2b,0x07, 0x2f,0x2f,0x07, + 0x37,0x37,0x07, 0x3f,0x3f,0x07, 0x47,0x47,0x07, 0x4b,0x4b,0x0b, + 0x53,0x53,0x0b, 0x5b,0x5b,0x0b, 0x63,0x63,0x0b, 0x6b,0x6b,0x0f, + 0x07,0x00,0x00, 0x0f,0x00,0x00, 0x17,0x00,0x00, 0x1f,0x00,0x00, + 0x27,0x00,0x00, 0x2f,0x00,0x00, 0x37,0x00,0x00, 0x3f,0x00,0x00, + 0x47,0x00,0x00, 0x4f,0x00,0x00, 0x57,0x00,0x00, 0x5f,0x00,0x00, + 0x67,0x00,0x00, 0x6f,0x00,0x00, 0x77,0x00,0x00, 0x7f,0x00,0x00, + 0x13,0x13,0x00, 0x1b,0x1b,0x00, 0x23,0x23,0x00, 0x2f,0x2b,0x00, + 0x37,0x2f,0x00, 0x43,0x37,0x00, 0x4b,0x3b,0x07, 0x57,0x43,0x07, + 0x5f,0x47,0x07, 0x6b,0x4b,0x0b, 0x77,0x53,0x0f, 0x83,0x57,0x13, + 0x8b,0x5b,0x13, 0x97,0x5f,0x1b, 0xa3,0x63,0x1f, 0xaf,0x67,0x23, + 0x23,0x13,0x07, 0x2f,0x17,0x0b, 0x3b,0x1f,0x0f, 0x4b,0x23,0x13, + 0x57,0x2b,0x17, 0x63,0x2f,0x1f, 0x73,0x37,0x23, 0x7f,0x3b,0x2b, + 0x8f,0x43,0x33, 0x9f,0x4f,0x33, 0xaf,0x63,0x2f, 0xbf,0x77,0x2f, + 0xcf,0x8f,0x2b, 0xdf,0xab,0x27, 0xef,0xcb,0x1f, 0xff,0xf3,0x1b, + 0x0b,0x07,0x00, 0x1b,0x13,0x00, 0x2b,0x23,0x0f, 0x37,0x2b,0x13, + 0x47,0x33,0x1b, 0x53,0x37,0x23, 0x63,0x3f,0x2b, 0x6f,0x47,0x33, + 0x7f,0x53,0x3f, 0x8b,0x5f,0x47, 0x9b,0x6b,0x53, 0xa7,0x7b,0x5f, + 0xb7,0x87,0x6b, 0xc3,0x93,0x7b, 0xd3,0xa3,0x8b, 0xe3,0xb3,0x97, + 0xab,0x8b,0xa3, 0x9f,0x7f,0x97, 0x93,0x73,0x87, 0x8b,0x67,0x7b, + 0x7f,0x5b,0x6f, 0x77,0x53,0x63, 0x6b,0x4b,0x57, 0x5f,0x3f,0x4b, + 0x57,0x37,0x43, 0x4b,0x2f,0x37, 0x43,0x27,0x2f, 0x37,0x1f,0x23, + 0x2b,0x17,0x1b, 0x23,0x13,0x13, 0x17,0x0b,0x0b, 0x0f,0x07,0x07, + 0xbb,0x73,0x9f, 0xaf,0x6b,0x8f, 0xa3,0x5f,0x83, 0x97,0x57,0x77, + 0x8b,0x4f,0x6b, 0x7f,0x4b,0x5f, 0x73,0x43,0x53, 0x6b,0x3b,0x4b, + 0x5f,0x33,0x3f, 0x53,0x2b,0x37, 0x47,0x23,0x2b, 0x3b,0x1f,0x23, + 0x2f,0x17,0x1b, 0x23,0x13,0x13, 0x17,0x0b,0x0b, 0x0f,0x07,0x07, + 0xdb,0xc3,0xbb, 0xcb,0xb3,0xa7, 0xbf,0xa3,0x9b, 0xaf,0x97,0x8b, + 0xa3,0x87,0x7b, 0x97,0x7b,0x6f, 0x87,0x6f,0x5f, 0x7b,0x63,0x53, + 0x6b,0x57,0x47, 0x5f,0x4b,0x3b, 0x53,0x3f,0x33, 0x43,0x33,0x27, + 0x37,0x2b,0x1f, 0x27,0x1f,0x17, 0x1b,0x13,0x0f, 0x0f,0x0b,0x07, + 0x6f,0x83,0x7b, 0x67,0x7b,0x6f, 0x5f,0x73,0x67, 0x57,0x6b,0x5f, + 0x4f,0x63,0x57, 0x47,0x5b,0x4f, 0x3f,0x53,0x47, 0x37,0x4b,0x3f, + 0x2f,0x43,0x37, 0x2b,0x3b,0x2f, 0x23,0x33,0x27, 0x1f,0x2b,0x1f, + 0x17,0x23,0x17, 0x0f,0x1b,0x13, 0x0b,0x13,0x0b, 0x07,0x0b,0x07, + 0xff,0xf3,0x1b, 0xef,0xdf,0x17, 0xdb,0xcb,0x13, 0xcb,0xb7,0x0f, + 0xbb,0xa7,0x0f, 0xab,0x97,0x0b, 0x9b,0x83,0x07, 0x8b,0x73,0x07, + 0x7b,0x63,0x07, 0x6b,0x53,0x00, 0x5b,0x47,0x00, 0x4b,0x37,0x00, + 0x3b,0x2b,0x00, 0x2b,0x1f,0x00, 0x1b,0x0f,0x00, 0x0b,0x07,0x00, + 0x00,0x00,0xff, 0x0b,0x0b,0xef, 0x13,0x13,0xdf, 0x1b,0x1b,0xcf, + 0x23,0x23,0xbf, 0x2b,0x2b,0xaf, 0x2f,0x2f,0x9f, 0x2f,0x2f,0x8f, + 0x2f,0x2f,0x7f, 0x2f,0x2f,0x6f, 0x2f,0x2f,0x5f, 0x2b,0x2b,0x4f, + 0x23,0x23,0x3f, 0x1b,0x1b,0x2f, 0x13,0x13,0x1f, 0x0b,0x0b,0x0f, + 0x2b,0x00,0x00, 0x3b,0x00,0x00, 0x4b,0x07,0x00, 0x5f,0x07,0x00, + 0x6f,0x0f,0x00, 0x7f,0x17,0x07, 0x93,0x1f,0x07, 0xa3,0x27,0x0b, + 0xb7,0x33,0x0f, 0xc3,0x4b,0x1b, 0xcf,0x63,0x2b, 0xdb,0x7f,0x3b, + 0xe3,0x97,0x4f, 0xe7,0xab,0x5f, 0xef,0xbf,0x77, 0xf7,0xd3,0x8b, + 0xa7,0x7b,0x3b, 0xb7,0x9b,0x37, 0xc7,0xc3,0x37, 0xe7,0xe3,0x57, + 0x7f,0xbf,0xff, 0xab,0xe7,0xff, 0xd7,0xff,0xff, 0x67,0x00,0x00, + 0x8b,0x00,0x00, 0xb3,0x00,0x00, 0xd7,0x00,0x00, 0xff,0x00,0x00, + 0xff,0xf3,0x93, 0xff,0xf7,0xc7, 0xff,0xff,0xff, 0x9f,0x5b,0x53 +}; + +/* + ============= + LoadMIP + ============= + */ + +Image* LoadMIPBuff( byte* buffer ){ + byte *buf_p; + int palettelength; + int columns, rows, numPixels; + byte *pixbuf; + int i; + byte *loadedpalette; + const byte *palette; + + loadedpalette = 0; + + PointerInputStream inputStream( buffer ); + + inputStream.seek( MIP_NAME_LENGTH ); + columns = istream_read_int32_le( inputStream ); + rows = istream_read_int32_le( inputStream ); + int offset = istream_read_int32_le( inputStream ); + + if ( std::size_t( columns ) > 65536 && std::size_t( rows ) > 65536 ) { + return 0; + } + +// unsigned long mipdatasize = GET_MIP_DATA_SIZE( columns, rows ); + + palettelength = vfsLoadFile( "gfx/palette.lmp", (void **) &loadedpalette ); + if ( palettelength == 768 ) { + palette = loadedpalette; + } + else + { + loadedpalette = 0; + palette = quakepalette; + } + + buf_p = buffer + offset; + + numPixels = columns * rows; + + RGBAImage* image = new RGBAImage( columns, rows ); + + //Sys_Printf("lpMip->width = %i, lpMip->height = %i, lpMip->offsets[0] = %i, lpMip->offsets[1] = %i, lpMip->offsets[2] = %i, lpMip->offsets[3] = %i, numPixels = %i\n", lpMip->width, lpMip->height, lpMip->offsets[0], lpMip->offsets[1], lpMip->offsets[2], lpMip->offsets[3], numPixels); + //for (i = 0; i < sizeof(*lpMip); i++) + // Sys_Printf("%02x", (int) ((unsigned char *)lpMip)[i]); + + pixbuf = image->getRGBAPixels(); + + for ( i = 0; i < numPixels; i++ ) + { + int palIndex = *buf_p++; + *pixbuf++ = palette[palIndex * 3]; + *pixbuf++ = palette[palIndex * 3 + 1]; + *pixbuf++ = palette[palIndex * 3 + 2]; + *pixbuf++ = 0xff; + } + + if ( loadedpalette != 0 ) { + vfsFreeFile( loadedpalette ); + } + + return image; +} + +Image* LoadMIP( ArchiveFile& file ){ + ScopedArchiveBuffer buffer( file ); + return LoadMIPBuff( buffer.buffer ); +} diff --git a/plugins/imagehl/mip.h b/plugins/imagehl/mip.h new file mode 100644 index 0000000..ea11d8f --- /dev/null +++ b/plugins/imagehl/mip.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_MIP_H ) +#define INCLUDED_MIP_H + +class Image; +class ArchiveFile; + +Image* LoadMIP( ArchiveFile& file ); + +#endif diff --git a/plugins/imagehl/sprite.cpp b/plugins/imagehl/sprite.cpp new file mode 100644 index 0000000..195d952 --- /dev/null +++ b/plugins/imagehl/sprite.cpp @@ -0,0 +1,221 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// by Hydra - hydra@hydras-world.com + +#include "sprite.h" + +#include +#include +#include + +typedef unsigned char byte; + +#include "ifilesystem.h" + +#include "imagelib.h" + +/* + ============================================================================ + + IDSP IMAGE (.spr files) + + Some code copied straight from the Q1 source, also used the HalfLife SDK as + a reference. + + ============================================================================ + */ + +typedef enum {ST_SYNC = 0, ST_RAND } synctype_t; +typedef enum { SPR_SINGLE = 0, SPR_GROUP } spriteframetype_t; + +typedef struct dspriteheader_s { + int ident; + int version; +} dspriteheader_t; + +// Quake1 +typedef struct { + int type; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dspritev1_t; + +// Halflife +typedef struct { + int type; + int texFormat; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dspritev2_t; + +typedef struct { + int origin[2]; + int width; + int height; +} dspriteframe_t; + +typedef struct { + short type; +} dspriteframetype_t; + +/* + typedef struct { + byte rgb[256][3]; + } dpalette_t; + */ + +const int IDSPRITEHEADER = ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'D' << 8 ) + 'I' ); +// little-endian "IDSP" + +/* + ============= + LoadIDSP + ============= + */ + +Image* LoadIDSPBuff( byte *buffer ){ + byte *buf_p; + int columns, rows; + byte *pixbuf; + + int row, column; + byte *palette; + unsigned char red, green, blue, alphabyte; + + dspriteheader_t *header; + dspritev1_t *pinv1; + dspritev2_t *pinv2; + dspriteframetype_t *pframetype; + int version; + int numframes; + dspriteframe_t *spriteframe; + + header = (dspriteheader_t *)buffer; + + if ( header->ident != IDSPRITEHEADER ) { + globalErrorStream() << "WARNING: IDSP file has wrong header\n"; + return 0; + } + + version = header->version; + if ( version != 1 && version != 2 ) { + globalErrorStream() << "WARNING: IDSP file has wrong version number " + "(" << version << " should be 1 or 2)\n"; + return 0; + } + + // initialise variables depending on the sprite version. + switch ( version ) + { + case 1: + pinv1 = (dspritev1_t *)( header + 1 ); + numframes = pinv1->numframes; + columns = pinv1->width; + rows = pinv1->height; + pframetype = (dspriteframetype_t *)( pinv1 + 1 ); + break; + case 2: + pinv2 = (dspritev2_t *)( header + 1 ); + numframes = pinv2->numframes; + columns = pinv2->width; + rows = pinv2->height; + pframetype = (dspriteframetype_t *)( pinv2 + 1 ); + break; + default: + globalErrorStream() << "WARNING: IDSP file has unsupported version\n"; + return 0; + } + if ( numframes > 1 ) { + globalErrorStream() << "WARNING: IDSP file has multiple frames, only the first frame will be used.\n"; + } + + // palette = buffer+mipdatasize+2; + // buf_p = buffer+lpMip->offsets[0]; + + RGBAImage* image = new RGBAImage( columns, rows ); + +#ifdef DEBUG + frametype = spriteframetype_t( pframetype->type ); + if ( frametype == SPR_SINGLE ) { + globalOutputStream() << "Single Frame\n"; + } + else if ( frametype == SPR_GROUP ) { + globalOutputStream() << "Group of Frames\n"; + } + else + { + globalOutputStream() << "Bleh!\n"; // <-- we always get this, wtf! + } +#endif + + palette = (byte *)( pframetype + 1 ); + spriteframe = (dspriteframe_t *)( palette + ( 256 * 3 ) + 4 ); // what are those 4 extra bytes ? what's missing ? + buf_p = (byte *)( spriteframe + 1 ); + + for ( row = 0; row < rows; row++ ) + { + pixbuf = image->getRGBAPixels() + row * columns * 4; + + for ( column = 0; column < columns; column++ ) + { + int palIndex; + + palIndex = *buf_p++; + + red = *( palette + ( palIndex * 3 ) ); + green = *( palette + ( palIndex * 3 ) + 1 ); + blue = *( palette + ( palIndex * 3 ) + 2 ); + + // HalfLife engine makes pixels that are BLUE transparent. (RGB = 0x0000FF) + // So show them that way in the editor. + if ( blue == 0xff && red == 0x00 && green == 0x00 ) { + alphabyte = 0xff; //FIXME: backwards? (so sprite models to render correctly) + blue = 0x00; // don't set the resulting pixel to blue + } + else + { + alphabyte = 0x00; //FIXME: backwards? (so sprite models to render correctly) + } + + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + + *pixbuf++ = alphabyte; + } + } + + return image; +} + +Image* LoadIDSP( ArchiveFile& file ){ + ScopedArchiveBuffer buffer( file ); + return LoadIDSPBuff( buffer.buffer ); +} diff --git a/plugins/imagehl/sprite.h b/plugins/imagehl/sprite.h new file mode 100644 index 0000000..d6635d9 --- /dev/null +++ b/plugins/imagehl/sprite.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_SPRITE_H ) +#define INCLUDED_SPRITE_H + +class Image; +class ArchiveFile; + +Image* LoadIDSP( ArchiveFile& file ); + +#endif diff --git a/plugins/matsys/Makefile b/plugins/matsys/Makefile new file mode 100644 index 0000000..78083ed --- /dev/null +++ b/plugins/matsys/Makefile @@ -0,0 +1,26 @@ +# WorldSpawn Makefile + +GLIB_CFLAGS=$(shell pkg-config --cflags gtk+-2.0) -DGTK_TARGET=2 +GLIB_LDFLAGS=$(shell pkg-config --libs gtk+-2.0) + +PLUGIN_CFLAGS=$(CFLAGS) $(GLIB_CFLAGS) -I../../include -I../../libs -fPIC -fvisibility=hidden +PLUGIN_LDFLAGS=$(LDFLAGS) $(GLIB_LDFLAGS) -shared + +DO_CXX=$(CXX) $(PLUGIN_CFLAGS) -o $@ -c $< + +.cpp.o: + $(DO_CXX) + +WS_OBJS = \ + shaders.o plugin.o + +# binary target +../../build/plugins/libmatsys.so: $(WS_OBJS) + $(CXX) -o $@ $(WS_OBJS) $(PLUGIN_LDFLAGS) + +# object files +shaders.o: shaders.cpp shaders.h +plugin.o: plugin.cpp + +clean: + -rm -f *.o ../../build/plugins/libmatsys.so diff --git a/plugins/matsys/plugin.cpp b/plugins/matsys/plugin.cpp new file mode 100644 index 0000000..12fedab --- /dev/null +++ b/plugins/matsys/plugin.cpp @@ -0,0 +1,92 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "ishaders.h" +#include "ifilesystem.h" +#include "itextures.h" +#include "iscriplib.h" +#include "qerplugin.h" + +#include "string/string.h" +#include "modulesystem/singletonmodule.h" +#include "shaders.h" + +class ShadersDependencies : + public GlobalFileSystemModuleRef, + public GlobalTexturesModuleRef, + public GlobalScripLibModuleRef, + public GlobalRadiantModuleRef { +ImageModuleRef m_bitmapModule; +public: +ShadersDependencies() : + m_bitmapModule("tga") +{ +} + +ImageModuleRef &getBitmapModule() +{ + return m_bitmapModule; +} +}; + +class MaterialAPI { +ShaderSystem *m_shadersq3; +public: +typedef ShaderSystem Type; + +STRING_CONSTANT(Name, "mat"); + +MaterialAPI(ShadersDependencies &dependencies) +{ + g_shadersExtension = "mat"; + g_shadersDirectory = "textures/"; + g_enableDefaultShaders = false; + g_useShaderList = false; + g_bitmapModule = dependencies.getBitmapModule().getTable(); + Shaders_Construct(); + m_shadersq3 = &GetShaderSystem(); +} + +~MaterialAPI() +{ + Shaders_Destroy(); +} + +ShaderSystem *getTable() +{ + return m_shadersq3; +} +}; + +typedef SingletonModule > MaterialModule; +MaterialModule g_MaterialModule; + +extern "C" void +#ifdef _WIN32 +__declspec(dllexport) +#else +__attribute__((visibility("default"))) +#endif +Radiant_RegisterModules(ModuleServer &server) +{ + initialiseModule(server); + g_MaterialModule.selfRegister(); +} diff --git a/plugins/matsys/shaders.cpp b/plugins/matsys/shaders.cpp new file mode 100644 index 0000000..5b9d65c --- /dev/null +++ b/plugins/matsys/shaders.cpp @@ -0,0 +1,1578 @@ +/* + Copyright (c) 2001, Loki software, inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + 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. + + Neither the name of Loki software nor the names of its contributors may be used + to endorse or promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + */ + +// +// Shaders Manager Plugin +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "shaders.h" +#include "globaldefs.h" + +#include +#include +#include +#include + +#include "ifilesystem.h" +#include "ishaders.h" +#include "iscriplib.h" +#include "itextures.h" +#include "qerplugin.h" +#include "irender.h" + +#include + +#include "debugging/debugging.h" +#include "string/pooledstring.h" +#include "math/vector.h" +#include "generic/callback.h" +#include "generic/referencecounted.h" +#include "stream/memstream.h" +#include "stream/stringstream.h" +#include "stream/textfilestream.h" +#include "os/path.h" +#include "os/dir.h" +#include "os/file.h" +#include "stringio.h" +#include "shaderlib.h" +#include "texturelib.h" +#include "cmdlib.h" +#include "moduleobservers.h" +#include "archivelib.h" +#include "imagelib.h" + +const char *g_shadersExtension = ""; +const char *g_shadersDirectory = ""; +bool g_enableDefaultShaders = true; +bool g_useShaderList = true; +_QERPlugImageTable *g_bitmapModule = 0; +const char *g_texturePrefix = "textures/"; + +void ActiveShaders_IteratorBegin(); +bool ActiveShaders_IteratorAtEnd(); +IShader *ActiveShaders_IteratorCurrent(); +void ActiveShaders_IteratorIncrement(); +Callback g_ActiveShadersChangedNotify; +void FreeShaders(); +void LoadShaderFile(const char *filename); +qtexture_t *Texture_ForName(const char *filename); + +/*! + NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX: + SHADER_NOT_FOUND means we didn't find the raw texture or the shader for this + SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it + this was in the initial design of the shader code since early GtkRadiant alpha, and got sort of foxed in 1.2 and put back in + */ +Image *loadBitmap(void *environment, const char *name) +{ + DirectoryArchiveFile file(name, name); + if (!file.failed()) { + return g_bitmapModule->loadImage(file); + } + return 0; +} + +inline byte *getPixel(byte *pixels, int width, int height, int x, int y) +{ + return pixels + (((((y + height) % height) * width) + ((x + width) % width)) * 4); +} + +class KernelElement { +public: +int x, y; +float w; +}; + +Image &convertHeightmapToNormalmap(Image &heightmap, float scale) +{ + int w = heightmap.getWidth(); + int h = heightmap.getHeight(); + + Image &normalmap = *(new RGBAImage(heightmap.getWidth(), heightmap.getHeight())); + + byte *in = heightmap.getRGBAPixels(); + byte *out = normalmap.getRGBAPixels(); + + #if 1 + // no filtering + const int kernelSize = 2; + KernelElement kernel_du[kernelSize] = { + {-1, 0, -0.5f}, + {1, 0, 0.5f} + }; + KernelElement kernel_dv[kernelSize] = { + {0, 1, 0.5f}, + {0, -1, -0.5f} + }; + #else + // 3x3 Prewitt + const int kernelSize = 6; + KernelElement kernel_du[kernelSize] = { + {-1, 1,-1.0f }, + {-1, 0,-1.0f }, + {-1,-1,-1.0f }, + { 1, 1, 1.0f }, + { 1, 0, 1.0f }, + { 1,-1, 1.0f } + }; + KernelElement kernel_dv[kernelSize] = { + {-1, 1, 1.0f }, + { 0, 1, 1.0f }, + { 1, 1, 1.0f }, + {-1,-1,-1.0f }, + { 0,-1,-1.0f }, + { 1,-1,-1.0f } + }; + #endif + + int x, y = 0; + while (y < h) { + x = 0; + while (x < w) { + float du = 0; + for (KernelElement *i = kernel_du; i != kernel_du + kernelSize; ++i) { + du += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w; + } + float dv = 0; + for (KernelElement *i = kernel_dv; i != kernel_dv + kernelSize; ++i) { + dv += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w; + } + + float nx = -du * scale; + float ny = -dv * scale; + float nz = 1.0; + + // Normalize + float norm = 1.0 / sqrt(nx * nx + ny * ny + nz * nz); + out[0] = float_to_integer(((nx * norm) + 1) * 127.5); + out[1] = float_to_integer(((ny * norm) + 1) * 127.5); + out[2] = float_to_integer(((nz * norm) + 1) * 127.5); + out[3] = 255; + + x++; + out += 4; + } + + y++; + } + + return normalmap; +} + +Image *loadHeightmap(void *environment, const char *name) +{ + Image *heightmap = GlobalTexturesCache().loadImage(name); + if (heightmap != 0) { + Image &normalmap = convertHeightmapToNormalmap(*heightmap, *reinterpret_cast( environment )); + heightmap->release(); + return &normalmap; + } + return 0; +} + +Image *loadSpecial(void *environment, const char *name) +{ + if (*name == '_') { // special image + StringOutputStream bitmapName(256); + bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".tga"; + Image *image = loadBitmap(environment, bitmapName.c_str()); + if (image != 0) { + return image; + } + } + return GlobalTexturesCache().loadImage(name); +} + +class ShaderPoolContext { +}; + +typedef Static ShaderPool; +typedef PooledString ShaderString; +typedef ShaderString ShaderVariable; +typedef ShaderString ShaderValue; +typedef CopiedString TextureExpression; + +// clean a texture name to the qtexture_t name format we use internally +// NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case +// information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name, +// we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive. +//++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present +template +void parseTextureName(StringType &name, const char *token) +{ + StringOutputStream cleaned(256); + cleaned << PathCleaned(token); + name = CopiedString( + StringRange(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str()))).c_str(); // remove extension +} + +bool Tokeniser_parseTextureName(Tokeniser &tokeniser, TextureExpression &name) +{ + const char *token = tokeniser.getToken(); + if (token == 0) { + Tokeniser_unexpectedError(tokeniser, token, "#texture-name"); + return false; + } + parseTextureName(name, token); + return true; +} + +bool Tokeniser_parseShaderName(Tokeniser &tokeniser, CopiedString &name) +{ + const char *token = tokeniser.getToken(); + if (token == 0) { + Tokeniser_unexpectedError(tokeniser, token, "#shader-name"); + return false; + } + parseTextureName(name, token); + return true; +} + +bool Tokeniser_parseString(Tokeniser &tokeniser, ShaderString &string) +{ + const char *token = tokeniser.getToken(); + if (token == 0) { + Tokeniser_unexpectedError(tokeniser, token, "#string"); + return false; + } + string = token; + return true; +} + +typedef std::list ShaderParameters; +typedef std::list ShaderArguments; +typedef std::pair BlendFuncExpression; + +class ShaderTemplate { +std::size_t m_refcount; +CopiedString m_Name; +CopiedString m_WadName; +public: + +ShaderParameters m_params; +TextureExpression m_textureName; +TextureExpression m_diffuse; +TextureExpression m_bump; +ShaderValue m_heightmapScale; +TextureExpression m_specular; +TextureExpression m_lightFalloffImage; + +int m_nFlags; +float m_fTrans; +int m_iPolygonOffset; + +// alphafunc stuff +IShader::EAlphaFunc m_AlphaFunc; +float m_AlphaRef; +// cull stuff +IShader::ECull m_Cull; + +ShaderTemplate() : + m_refcount(0) +{ + m_nFlags = 0; + m_fTrans = 1.0f; +} + +void IncRef() +{ + ++m_refcount; +} + +void DecRef() +{ + ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero"); + if (--m_refcount == 0) { + delete this; + } +} + +std::size_t refcount() +{ + return m_refcount; +} + +const char *getName() const +{ + return m_Name.c_str(); +} + +void setName(const char *name) +{ + m_Name = name; +} +// ----------------------------------------- + +bool parseMaterial(Tokeniser &tokeniser); +bool parseTemplate(Tokeniser &tokeniser); + + +void CreateDefault(const char *name) +{ + /*if (g_enableDefaultShaders) { + m_textureName = name; + } else { + m_textureName = ""; + }*/ + setName(name); +} + +class MapLayerTemplate { +TextureExpression m_texture; +BlendFuncExpression m_blendFunc; +bool m_clampToBorder; +ShaderValue m_alphaTest; +public: +MapLayerTemplate(const TextureExpression &texture, const BlendFuncExpression &blendFunc, bool clampToBorder, + const ShaderValue &alphaTest) : + m_texture(texture), + m_blendFunc(blendFunc), + m_clampToBorder(false), + m_alphaTest(alphaTest) +{ +} + +const TextureExpression &texture() const +{ + return m_texture; +} + +const BlendFuncExpression &blendFunc() const +{ + return m_blendFunc; +} + +bool clampToBorder() const +{ + return m_clampToBorder; +} + +const ShaderValue &alphaTest() const +{ + return m_alphaTest; +} +}; + +typedef std::vector MapLayers; +MapLayers m_layers; +}; + +enum LayerTypeId { + LAYER_NONE, + LAYER_BLEND, + LAYER_DIFFUSEMAP, + LAYER_BUMPMAP, + LAYER_SPECULARMAP +}; + +class LayerTemplate { +public: +LayerTypeId m_type; +TextureExpression m_texture; +BlendFuncExpression m_blendFunc; +bool m_clampToBorder; +ShaderValue m_alphaTest; +ShaderValue m_heightmapScale; + +LayerTemplate() : m_type(LAYER_NONE), m_blendFunc("GL_ONE", "GL_ZERO"), m_clampToBorder(false), m_alphaTest("-1"), + m_heightmapScale("0") +{ +} +}; + +bool parseShaderParameters(Tokeniser &tokeniser, ShaderParameters ¶ms) +{ + Tokeniser_parseToken(tokeniser, "("); + for (;;) { + const char *param = tokeniser.getToken(); + if (string_equal(param, ")")) { + break; + } + params.push_back(param); + const char *comma = tokeniser.getToken(); + if (string_equal(comma, ")")) { + break; + } + if (!string_equal(comma, ",")) { + Tokeniser_unexpectedError(tokeniser, comma, ","); + return false; + } + } + return true; +} + +bool ShaderTemplate::parseTemplate(Tokeniser &tokeniser) +{ + m_Name = tokeniser.getToken(); + if (!parseShaderParameters(tokeniser, m_params)) { + globalErrorStream() << "shader template: " << makeQuoted(m_Name.c_str()) << ": parameter parse failed\n"; + return false; + } + + return parseMaterial(tokeniser); +} + +typedef SmartPointer ShaderTemplatePointer; +typedef std::map ShaderTemplateMap; + +ShaderTemplateMap g_shaders; +ShaderTemplateMap g_shaderTemplates; + +ShaderTemplate *findTemplate(const char *name) +{ + ShaderTemplateMap::iterator i = g_shaderTemplates.find(name); + if (i != g_shaderTemplates.end()) { + return (*i).second.get(); + } + return 0; +} + +class ShaderDefinition { +public: +ShaderDefinition(ShaderTemplate *shaderTemplate, const ShaderArguments &args, const char *filename) + : shaderTemplate(shaderTemplate), args(args), filename(filename) +{ +} + +ShaderTemplate *shaderTemplate; +ShaderArguments args; +const char *filename; +}; + +typedef std::map ShaderDefinitionMap; +ShaderDefinitionMap g_shaderDefinitions; + +bool parseTemplateInstance(Tokeniser &tokeniser, const char *filename) +{ + CopiedString name; + RETURN_FALSE_IF_FAIL(Tokeniser_parseShaderName(tokeniser, name)); + const char *templateName = tokeniser.getToken(); + ShaderTemplate *shaderTemplate = findTemplate(templateName); + if (shaderTemplate == 0) { + globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": shader template not found: " + << makeQuoted(templateName) << "\n"; + } + + ShaderArguments args; + if (!parseShaderParameters(tokeniser, args)) { + globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": argument parse failed\n"; + return false; + } + + if (shaderTemplate != 0) { + if (!g_shaderDefinitions.insert( + ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate, args, filename))).second) { + globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) + << ": already exists, second definition ignored\n"; + } + } + return true; +} + +const char *evaluateShaderValue(const char *value, const ShaderParameters ¶ms, const ShaderArguments &args) +{ + ShaderArguments::const_iterator j = args.begin(); + for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) { + const char *other = (*i).c_str(); + if (string_equal(value, other)) { + return (*j).c_str(); + } + } + return value; +} + +///\todo BlendFunc parsing +BlendFunc +evaluateBlendFunc(const BlendFuncExpression &blendFunc, const ShaderParameters ¶ms, const ShaderArguments &args) +{ + return BlendFunc(BLEND_ONE, BLEND_ZERO); +} + +qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){ + StringOutputStream result( 64 ); + const char* expression = texture.c_str(); + const char* end = expression + string_length( expression ); + if ( !string_empty( expression ) ) { + for (;; ) + { + const char* best = end; + const char* bestParam = 0; + const char* bestArg = 0; + ShaderArguments::const_iterator j = args.begin(); + for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j ) + { + const char* found = strstr( expression, ( *i ).c_str() ); + if ( found != 0 && found < best ) { + best = found; + bestParam = ( *i ).c_str(); + bestArg = ( *j ).c_str(); + } + } + if ( best != end ) { + result << StringRange( expression, best ); + result << PathCleaned( bestArg ); + expression = best + string_length( bestParam ); + } + else + { + break; + } + } + result << expression; + } + return GlobalTexturesCache().capture( loader, result.c_str() ); +} + +float evaluateFloat(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args) +{ + const char *result = evaluateShaderValue(value.c_str(), params, args); + float f = 0.0f; + if (!string_parse_float(result, f)) { + globalErrorStream() << "parsing float value failed: " << makeQuoted(result) << "\n"; + } + return f; +} + +BlendFactor evaluateBlendFactor(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args) +{ + const char *result = evaluateShaderValue(value.c_str(), params, args); + + if (string_equal_nocase(result, "gl_zero")) { + return BLEND_ZERO; + } + if (string_equal_nocase(result, "gl_one")) { + return BLEND_ONE; + } + if (string_equal_nocase(result, "gl_src_color")) { + return BLEND_SRC_COLOUR; + } + if (string_equal_nocase(result, "gl_one_minus_src_color")) { + return BLEND_ONE_MINUS_SRC_COLOUR; + } + if (string_equal_nocase(result, "gl_src_alpha")) { + return BLEND_SRC_ALPHA; + } + if (string_equal_nocase(result, "gl_one_minus_src_alpha")) { + return BLEND_ONE_MINUS_SRC_ALPHA; + } + if (string_equal_nocase(result, "gl_dst_color")) { + return BLEND_DST_COLOUR; + } + if (string_equal_nocase(result, "gl_one_minus_dst_color")) { + return BLEND_ONE_MINUS_DST_COLOUR; + } + if (string_equal_nocase(result, "gl_dst_alpha")) { + return BLEND_DST_ALPHA; + } + if (string_equal_nocase(result, "gl_one_minus_dst_alpha")) { + return BLEND_ONE_MINUS_DST_ALPHA; + } + if (string_equal_nocase(result, "gl_src_alpha_saturate")) { + return BLEND_SRC_ALPHA_SATURATE; + } + + globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted(result) << "\n"; + return BLEND_ZERO; +} + +class CShader : public IShader { +std::size_t m_refcount; +const ShaderTemplate &m_template; +const ShaderArguments &m_args; +const char *m_filename; +// name is shader-name, otherwise texture-name (if not a real shader) +CopiedString m_Name; +CopiedString m_WadName; +qtexture_t *m_pTexture; +qtexture_t *m_notfound; +qtexture_t *m_pDiffuse; +float m_heightmapScale; +qtexture_t *m_pBump; +qtexture_t *m_pSpecular; +qtexture_t *m_pLightFalloffImage; +BlendFunc m_blendFunc; +bool m_bInUse; + +public: +CShader(const ShaderDefinition &definition) : + m_refcount(0), + m_template(*definition.shaderTemplate), + m_args(definition.args), + m_filename(definition.filename), + m_blendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA), + m_bInUse(false) +{ + m_pTexture = 0; + m_pDiffuse = 0; + m_pBump = 0; + m_pSpecular = 0; + m_notfound = 0; + realise(); +} + +virtual ~CShader() +{ + unrealise(); + + ASSERT_MESSAGE(m_refcount == 0, "deleting active shader"); +} + +// IShaders implementation ----------------- +void IncRef() +{ + ++m_refcount; +} + +void DecRef() +{ + ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero"); + if (--m_refcount == 0) { + delete this; + } +} + +std::size_t refcount() +{ + return m_refcount; +} + +// get/set the qtexture_t* Radiant uses to represent this shader object +qtexture_t *getTexture() const +{ + return m_pTexture; +} + +qtexture_t *getDiffuse() const +{ + return m_pDiffuse; +} + +qtexture_t *getBump() const +{ + return m_pBump; +} + +qtexture_t *getSpecular() const +{ + return m_pSpecular; +} + +// get shader name +const char *getName() const +{ + return m_Name.c_str(); +} + +const char* getWadName() const +{ + return m_WadName.c_str(); +} + + +bool IsInUse() const +{ + return m_bInUse; +} + +void SetInUse(bool bInUse) +{ + m_bInUse = bInUse; + g_ActiveShadersChangedNotify(); +} + +// get the shader flags +int getFlags() const +{ + return m_template.m_nFlags; +} + +// get the transparency value +float getTrans() const +{ + return m_template.m_fTrans; +} + +int getPolygonOffset() const +{ + return m_template.m_iPolygonOffset; +} + +// test if it's a true shader, or a default shader created to wrap around a texture +bool IsDefault() const +{ + return string_empty(m_filename); +} + +// get the alphaFunc +void getAlphaFunc(EAlphaFunc *func, float *ref) +{ + *func = m_template.m_AlphaFunc; + *ref = m_template.m_AlphaRef; +}; + +BlendFunc getBlendFunc() const +{ + return m_blendFunc; +} + +// get the cull type +ECull getCull() +{ + return m_template.m_Cull; +}; + +// get shader file name (ie the file where this one is defined) +const char *getShaderFileName() const +{ + return m_filename; +} +// ----------------------------------------- + +void realise() +{ + m_pTexture = evaluateTexture(m_template.m_textureName, m_template.m_params, m_args); + + if (m_pTexture->texture_number == 0) { + m_notfound = m_pTexture; + { + StringOutputStream name(256); + name << GlobalRadiant().getAppPath() << "bitmaps/" << (IsDefault() ? "notex.tga" : "shadernotex.tga"); + m_pTexture = GlobalTexturesCache().capture(LoadImageCallback(0, loadBitmap), name.c_str()); + } + } +} + +void unrealise() +{ + GlobalTexturesCache().release(m_pTexture); + + if (m_notfound != 0) { + GlobalTexturesCache().release(m_notfound); + } +} + +// set shader name +void setName(const char *name) +{ + m_Name = name; +} + +void setWadName( const char* name ) +{ + m_WadName = name; +} + +class MapLayer : public ShaderLayer { +qtexture_t *m_texture; +BlendFunc m_blendFunc; +bool m_clampToBorder; +float m_alphaTest; +public: +MapLayer(qtexture_t *texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest) : + m_texture(texture), + m_blendFunc(blendFunc), + m_clampToBorder(false), + m_alphaTest(alphaTest) +{ +} + +qtexture_t *texture() const +{ + return m_texture; +} + +BlendFunc blendFunc() const +{ + return m_blendFunc; +} + +bool clampToBorder() const +{ + return m_clampToBorder; +} + +float alphaTest() const +{ + return m_alphaTest; +} +}; + +static MapLayer evaluateLayer(const ShaderTemplate::MapLayerTemplate &layerTemplate, const ShaderParameters ¶ms, + const ShaderArguments &args) +{ + return MapLayer( + evaluateTexture(layerTemplate.texture(), params, args), + evaluateBlendFunc(layerTemplate.blendFunc(), params, args), + layerTemplate.clampToBorder(), + evaluateFloat(layerTemplate.alphaTest(), params, args) + ); +} + +typedef std::vector MapLayers; +MapLayers m_layers; + +const ShaderLayer *firstLayer() const +{ + if (m_layers.empty()) { + return 0; + } + return &m_layers.front(); +} + +void forEachLayer(const ShaderLayerCallback &callback) const +{ + for (MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + callback(*i); + } +} + +qtexture_t *lightFalloffImage() const +{ + if (!string_empty(m_template.m_lightFalloffImage.c_str())) { + return m_pLightFalloffImage; + } + return 0; +} +}; + +typedef SmartPointer ShaderPointer; +typedef std::map shaders_t; +shaders_t g_ActiveShaders; +static shaders_t::iterator g_ActiveShadersIterator; + +void ActiveShaders_IteratorBegin() +{ + g_ActiveShadersIterator = g_ActiveShaders.begin(); +} + +bool ActiveShaders_IteratorAtEnd() +{ + return g_ActiveShadersIterator == g_ActiveShaders.end(); +} + +IShader *ActiveShaders_IteratorCurrent() +{ + return static_cast( g_ActiveShadersIterator->second ); +} + +void ActiveShaders_IteratorIncrement() +{ + ++g_ActiveShadersIterator; +} + +void debug_check_shaders(shaders_t &shaders) +{ + for (shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i) { + ASSERT_MESSAGE(i->second->refcount() == 1, "orphan shader still referenced"); + } +} + +// will free all GL binded qtextures and shaders +// NOTE: doesn't make much sense out of Radiant exit or called during a reload +void FreeShaders() +{ + // reload shaders + // empty the actives shaders list + debug_check_shaders(g_ActiveShaders); + g_ActiveShaders.clear(); + g_shaders.clear(); + g_shaderTemplates.clear(); + g_shaderDefinitions.clear(); + g_ActiveShadersChangedNotify(); +} + +bool ShaderTemplate::parseMaterial(Tokeniser &tokeniser) +{ + tokeniser.nextLine(); + + // we need to read until we hit a balanced } + int depth = 0; + for (;;) { + tokeniser.nextLine(); + const char *token = tokeniser.getToken(); + + if (token == 0) { + return false; + } + + if (string_equal(token, "{")) { + ++depth; + continue; + } else if (string_equal(token, "}")) { + --depth; + if (depth < 0) { // underflow + return false; + } + if (depth == 0) { // end of shader + break; + } + + continue; + } + + if (depth == 1) { + if (string_equal_nocase(token, "qer_nocarve")) { + m_nFlags |= QER_NOCARVE; + } else if (string_equal_nocase(token, "polygonoffset")) { + if (Tokeniser_getInteger(tokeniser, m_iPolygonOffset) == false) { + m_iPolygonOffset = 1; + } + m_nFlags |= QER_POLYOFS; + } else if (string_equal_nocase(token, "qer_trans")) { + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_fTrans)); + m_nFlags |= QER_TRANS; + } else if (string_equal_nocase(token, "qer_editorimage") || string_equal_nocase(token, "diffusemap")) { + RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName)); + } else if (string_equal_nocase(token, "qer_alphafunc")) { + const char *alphafunc = tokeniser.getToken(); + + if (alphafunc == 0) { + Tokeniser_unexpectedError(tokeniser, alphafunc, "#alphafunc"); + return false; + } + + if (string_equal_nocase(alphafunc, "equal")) { + m_AlphaFunc = IShader::eEqual; + } else if (string_equal_nocase(alphafunc, "greater")) { + m_AlphaFunc = IShader::eGreater; + } else if (string_equal_nocase(alphafunc, "less")) { + m_AlphaFunc = IShader::eLess; + } else if (string_equal_nocase(alphafunc, "gequal")) { + m_AlphaFunc = IShader::eGEqual; + } else if (string_equal_nocase(alphafunc, "lequal")) { + m_AlphaFunc = IShader::eLEqual; + } else { + m_AlphaFunc = IShader::eAlways; + } + + m_nFlags |= QER_ALPHATEST; + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_AlphaRef)); + } else if (string_equal_nocase(token, "cull")) { + const char *cull = tokeniser.getToken(); + + if (cull == 0) { + Tokeniser_unexpectedError(tokeniser, cull, "#cull"); + return false; + } + + if (string_equal_nocase(cull, "none") + || string_equal_nocase(cull, "twosided") + || string_equal_nocase(cull, "disable")) { + m_Cull = IShader::eCullNone; + } else if (string_equal_nocase(cull, "back") + || string_equal_nocase(cull, "backside") + || string_equal_nocase(cull, "backsided")) { + m_Cull = IShader::eCullBack; + } else { + m_Cull = IShader::eCullBack; + } + + m_nFlags |= QER_CULL; + } else if (string_equal_nocase(token, "surfaceparm")) { + const char *surfaceparm = tokeniser.getToken(); + + if (surfaceparm == 0) { + Tokeniser_unexpectedError(tokeniser, surfaceparm, "#surfaceparm"); + return false; + } + + if (string_equal_nocase(surfaceparm, "fog")) { + m_nFlags |= QER_FOG; + if (m_fTrans == 1.0f) { // has not been explicitly set by qer_trans + m_fTrans = 0.35f; + } + } else if (string_equal_nocase(surfaceparm, "nodraw")) { + m_nFlags |= QER_NODRAW; + } else if (string_equal_nocase(surfaceparm, "nonsolid")) { + m_nFlags |= QER_NONSOLID; + } else if (string_equal_nocase(surfaceparm, "water")) { + m_nFlags |= QER_WATER; + } else if (string_equal_nocase(surfaceparm, "lava")) { + m_nFlags |= QER_LAVA; + } else if (string_equal_nocase(surfaceparm, "areaportal")) { + m_nFlags |= QER_AREAPORTAL; + } else if (string_equal_nocase(surfaceparm, "playerclip")) { + m_nFlags |= QER_CLIP; + } else if (string_equal_nocase(surfaceparm, "botclip")) { + m_nFlags |= QER_BOTCLIP; + } + } + } + } + + return true; +} + +class Layer { +public: +LayerTypeId m_type; +TextureExpression m_texture; +BlendFunc m_blendFunc; +bool m_clampToBorder; +float m_alphaTest; +float m_heightmapScale; + +Layer() : m_type(LAYER_NONE), m_blendFunc(BLEND_ONE, BLEND_ZERO), m_clampToBorder(false), m_alphaTest(-1), + m_heightmapScale(0) +{ +} +}; + +std::list g_shaderFilenames; + +#if defined(WIN64) || defined(WIN32) +char * +strndup (const char *s, size_t n) +{ + char *result; + size_t len = strlen (s); + + if (n < len) + len = n; + + result = (char *) malloc (len + 1); + + if (!result) + return 0; + + result[len] = '\0'; + return (char *) memcpy (result, s, len); +} +#endif + +char* m_substring(const char* str, size_t begin, size_t len) +{ + if (str == 0 || strlen(str) == 0 || strlen(str) < begin || strlen(str) < (begin+len)) { + return 0; + } + + return strndup(str + begin, len); +} + +void ParseShaderFile(Tokeniser &tokeniser, const char *filename) +{ + g_shaderFilenames.push_back(filename); + filename = g_shaderFilenames.back().c_str(); + tokeniser.nextLine(); + for (;;) { + const char *token = tokeniser.getToken(); + + if (token == 0) { + break; + } + + if (string_equal(token, "table")) { + if (tokeniser.getToken() == 0) { + Tokeniser_unexpectedError(tokeniser, 0, "#table-name"); + return; + } + if (!Tokeniser_parseToken(tokeniser, "{")) { + return; + } + for (;;) { + const char *option = tokeniser.getToken(); + if (string_equal(option, "{")) { + for (;;) { + const char *value = tokeniser.getToken(); + if (string_equal(value, "}")) { + break; + } + } + + if (!Tokeniser_parseToken(tokeniser, "}")) { + return; + } + break; + } + } + } else { + if (string_equal(token, "guide")) { + parseTemplateInstance(tokeniser, filename); + } else { + if (!string_equal(token, "material") + && !string_equal(token, "particle") + && !string_equal(token, "skin")) { + tokeniser.ungetToken(); + } + // first token should be the path + name.. (from base) + ShaderTemplatePointer shaderTemplate(new ShaderTemplate()); + shaderTemplate->setName( m_substring( filename, 0, strlen( filename) - 4 ) ); // EUKARA + + g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)); + + bool result = shaderTemplate->parseMaterial(tokeniser); + if (result) { + // do we already have this shader? + if (!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(), + ShaderDefinition( + shaderTemplate.get(), + ShaderArguments(), + filename))).second) { + #if GDEF_DEBUG + globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() + << " is already in memory, definition in " << filename << " ignored.\n"; + #endif + } + } else { + globalErrorStream() << "Error parsing material " << shaderTemplate->getName() << "\n"; + return; + } + } + } + } +} + +void parseGuideFile(Tokeniser &tokeniser, const char *filename) +{ + tokeniser.nextLine(); + for (;;) { + const char *token = tokeniser.getToken(); + + if (token == 0) { + break; + } + + if (string_equal(token, "guide")) { + // first token should be the path + name.. (from base) + ShaderTemplatePointer shaderTemplate(new ShaderTemplate); + shaderTemplate->parseTemplate(tokeniser); + if (!g_shaderTemplates.insert( + ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)).second) { + globalErrorStream() << "guide " << makeQuoted(shaderTemplate->getName()) + << ": already defined, second definition ignored\n"; + } + } else if (string_equal(token, "inlineGuide")) { + // skip entire inlineGuide definition + std::size_t depth = 0; + for (;;) { + tokeniser.nextLine(); + token = tokeniser.getToken(); + if (string_equal(token, "{")) { + ++depth; + } else if (string_equal(token, "}")) { + if (--depth == 0) { + break; + } + } + } + } + } +} + +void LoadShaderFile(const char *filename) +{ + ArchiveTextFile *file = GlobalFileSystem().openTextFile(filename); + + if (file != 0) { + // we probably only want to output when errors happen + //globalOutputStream() << "Parsing material " << filename << "\n"; + Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream()); + ParseShaderFile(tokeniser, filename); + tokeniser.release(); + file->release(); + } else { + globalOutputStream() << "Unable to read material " << filename << "\n"; + } +} + +void loadGuideFile(const char *filename) +{ + StringOutputStream fullname(256); + fullname << "guides/" << filename; + ArchiveTextFile *file = GlobalFileSystem().openTextFile(fullname.c_str()); + + if (file != 0) { + globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n"; + Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream()); + parseGuideFile(tokeniser, fullname.c_str()); + tokeniser.release(); + file->release(); + } else { + globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n"; + } +} + +CShader* Try_Shader_ForName( const char* name ){ + { + shaders_t::iterator i = g_ActiveShaders.find( name ); + if ( i != g_ActiveShaders.end() ) { + return ( *i ).second; + } + } + // active shader was not found + + // find matching shader definition + ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name ); + if ( i == g_shaderDefinitions.end() ) { + // shader definition was not found + + // create new shader definition from default shader template + ShaderTemplatePointer shaderTemplate( new ShaderTemplate() ); + shaderTemplate->CreateDefault( name ); + g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ); + + i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first; + } + + // create shader from existing definition + ShaderPointer pShader( new CShader( ( *i ).second ) ); + pShader->setName( name ); + g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) ); + g_ActiveShadersChangedNotify(); + return pShader; +} + +IShader *Shader_ForName(const char *name) +{ + ASSERT_NOTNULL(name); + + IShader *pShader = Try_Shader_ForName(name); + pShader->IncRef(); + return pShader; +} + + +// the list of scripts/*.shader files we need to work with +// those are listed in shaderlist file +GSList *l_shaderfiles = 0; + +GSList *Shaders_getShaderFileList() +{ + return l_shaderfiles; +} + +/* + ================== + DumpUnreferencedShaders + usefull function: dumps the list of .shader files that are not referenced to the console + ================== + */ +void IfFound_dumpUnreferencedShader(bool &bFound, const char *filename) +{ + bool listed = false; + + for (GSList *sh = l_shaderfiles; sh != 0; sh = g_slist_next(sh)) { + if (!strcmp((char *) sh->data, filename)) { + listed = true; + break; + } + } + + if (!listed) { + if (!bFound) { + bFound = true; + globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n"; + } + globalOutputStream() << "\t" << filename << "\n"; + } +} + +typedef ReferenceCaller IfFoundDumpUnreferencedShaderCaller; + +void DumpUnreferencedShaders() +{ + bool bFound = false; + GlobalFileSystem().forEachFile(g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller(bFound)); +} + +void ShaderList_addShaderFile(const char *dirstring) +{ + bool found = false; + + for (GSList *tmp = l_shaderfiles; tmp != 0; tmp = tmp->next) { + if (string_equal_nocase(dirstring, (char *) tmp->data)) { + found = true; + globalOutputStream() << "duplicate entry \"" << (char *) tmp->data << "\" in shaderlist.txt\n"; + break; + } + } + + if (!found) { + l_shaderfiles = g_slist_append(l_shaderfiles, strdup(dirstring)); + } +} + +/* + ================== + BuildShaderList + build a CStringList of shader names + ================== + */ +void BuildShaderList(TextInputStream &shaderlist) +{ + Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(shaderlist); + tokeniser.nextLine(); + const char *token = tokeniser.getToken(); + StringOutputStream shaderFile(64); + while (token != 0) { + // each token should be a shader filename + shaderFile << token << "." << g_shadersExtension; + ShaderList_addShaderFile(shaderFile.c_str()); + tokeniser.nextLine(); + token = tokeniser.getToken(); + shaderFile.clear(); + } + tokeniser.release(); +} + +void FreeShaderList() +{ + while (l_shaderfiles != 0) { + free(l_shaderfiles->data); + l_shaderfiles = g_slist_remove(l_shaderfiles, l_shaderfiles->data); + } +} + +void ShaderList_addFromArchive(const char *archivename) +{ + const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath"); + if (string_empty(shaderpath)) { + return; + } + + StringOutputStream shaderlist(256); + shaderlist << DirectoryCleaned(shaderpath) << "shaderlist.txt"; + + Archive *archive = GlobalFileSystem().getArchive(archivename, false); + if (archive) { + ArchiveTextFile *file = archive->openTextFile(shaderlist.c_str()); + if (file) { + globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n"; + BuildShaderList(file->getInputStream()); + file->release(); + } + } +} + +#include "stream/filestream.h" + +bool +shaderlist_findOrInstall(const char *enginePath, const char *toolsPath, const char *shaderPath, const char *gamename) +{ + StringOutputStream absShaderList(256); + absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt"; + if (file_exists(absShaderList.c_str())) { + return true; + } + { + StringOutputStream directory(256); + directory << enginePath << gamename << '/' << shaderPath; + if (!file_exists(directory.c_str()) && !Q_mkdir(directory.c_str())) { + return false; + } + } + { + StringOutputStream defaultShaderList(256); + defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt"; + if (file_exists(defaultShaderList.c_str())) { + return file_copy(defaultShaderList.c_str(), absShaderList.c_str()); + } + } + return false; +} + +void Shaders_Load() +{ + /*if (QUAKE4) { + GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0); + }*/ + + const char *shaderPath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath"); + if (!string_empty(shaderPath)) { + StringOutputStream path(256); + path << DirectoryCleaned(shaderPath); + + if (g_useShaderList) { + // preload shader files that have been listed in shaderlist.txt + const char *basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame"); + const char *gamename = GlobalRadiant().getGameName(); + const char *enginePath = GlobalRadiant().getEnginePath(); + const char *toolsPath = GlobalRadiant().getGameToolsPath(); + bool isMod = !string_equal(basegame, gamename); + + if (!isMod || !shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename)) { + gamename = basegame; + shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename); + } + + GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true); + DumpUnreferencedShaders(); + } else { + GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 0); + } + + GSList *lst = l_shaderfiles; + StringOutputStream shadername(256); + while (lst) { + shadername << path.c_str() << reinterpret_cast( lst->data ); + LoadShaderFile(shadername.c_str()); + shadername.clear(); + lst = lst->next; + } + } + + //StringPool_analyse(ShaderPool::instance()); +} + +void Shaders_Free() +{ + FreeShaders(); + FreeShaderList(); + g_shaderFilenames.clear(); +} + +ModuleObservers g_observers; + +// wait until filesystem and is realised before loading anything +std::size_t g_shaders_unrealised = 1; +bool Shaders_realised() +{ + return g_shaders_unrealised == 0; +} + +void Shaders_Realise() +{ + if (--g_shaders_unrealised == 0) { + Shaders_Load(); + g_observers.realise(); + } +} + +void Shaders_Unrealise() +{ + if (++g_shaders_unrealised == 1) { + g_observers.unrealise(); + Shaders_Free(); + } +} + +void Shaders_Refresh() +{ + Shaders_Unrealise(); + Shaders_Realise(); +} + +class MaterialSystem : public ShaderSystem, public ModuleObserver { +public: +void realise() +{ + Shaders_Realise(); +} + +void unrealise() +{ + Shaders_Unrealise(); +} + +void refresh() +{ + Shaders_Refresh(); +} + +IShader *getShaderForName(const char *name) +{ + return Shader_ForName(name); +} + +void foreachShaderName(const ShaderNameCallback &callback) +{ + for (ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i) { + callback((*i).first.c_str()); + } +} + +void beginActiveShadersIterator() +{ + ActiveShaders_IteratorBegin(); +} + +bool endActiveShadersIterator() +{ + return ActiveShaders_IteratorAtEnd(); +} + +IShader *dereferenceActiveShadersIterator() +{ + return ActiveShaders_IteratorCurrent(); +} + +void incrementActiveShadersIterator() +{ + ActiveShaders_IteratorIncrement(); +} + +void setActiveShadersChangedNotify(const Callback ¬ify) +{ + g_ActiveShadersChangedNotify = notify; +} + +void attach(ModuleObserver &observer) +{ + g_observers.attach(observer); +} + +void detach(ModuleObserver &observer) +{ + g_observers.detach(observer); +} + +void setLightingEnabled(bool enabled) +{ + +} + +const char *getTexturePrefix() const +{ + return g_texturePrefix; +} +}; + +MaterialSystem g_MaterialSystem; + +ShaderSystem &GetShaderSystem() +{ + return g_MaterialSystem; +} + +void Shaders_Construct() +{ + GlobalFileSystem().attach(g_MaterialSystem); +} + +void Shaders_Destroy() +{ + GlobalFileSystem().detach(g_MaterialSystem); + + if (Shaders_realised()) { + Shaders_Free(); + } +} diff --git a/plugins/matsys/shaders.h b/plugins/matsys/shaders.h new file mode 100644 index 0000000..4c9859a --- /dev/null +++ b/plugins/matsys/shaders.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2001, Loki software, inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + 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. + + Neither the name of Loki software nor the names of its contributors may be used + to endorse or promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + */ + +#if !defined( INCLUDED_SHADERS_H ) +#define INCLUDED_SHADERS_H +void Shaders_Construct(); +void Shaders_Destroy(); +class ShaderSystem; +ShaderSystem &GetShaderSystem(); +extern const char *g_shadersExtension; +extern const char *g_shadersDirectory; +extern bool g_enableDefaultShaders; +extern bool g_useShaderList; +struct _QERPlugImageTable; +extern _QERPlugImageTable *g_bitmapModule; +#endif diff --git a/plugins/shaders/plugin.cpp b/plugins/shaders/plugin.cpp index 12fedab..8e5a453 100644 --- a/plugins/shaders/plugin.cpp +++ b/plugins/shaders/plugin.cpp @@ -27,66 +27,117 @@ #include "string/string.h" #include "modulesystem/singletonmodule.h" + #include "shaders.h" class ShadersDependencies : public GlobalFileSystemModuleRef, public GlobalTexturesModuleRef, public GlobalScripLibModuleRef, - public GlobalRadiantModuleRef { + public GlobalRadiantModuleRef +{ ImageModuleRef m_bitmapModule; public: ShadersDependencies() : - m_bitmapModule("tga") -{ + m_bitmapModule( "tga" ){ } - -ImageModuleRef &getBitmapModule() -{ +ImageModuleRef& getBitmapModule(){ return m_bitmapModule; } }; -class MaterialAPI { -ShaderSystem *m_shadersq3; +class ShadersQ3API +{ +ShaderSystem* m_shadersq3; public: typedef ShaderSystem Type; +STRING_CONSTANT( Name, "quake3" ); -STRING_CONSTANT(Name, "mat"); - -MaterialAPI(ShadersDependencies &dependencies) -{ - g_shadersExtension = "mat"; - g_shadersDirectory = "textures/"; - g_enableDefaultShaders = false; - g_useShaderList = false; +ShadersQ3API( ShadersDependencies& dependencies ){ + g_shadersExtension = "shader"; + g_shadersDirectory = "scripts/"; g_bitmapModule = dependencies.getBitmapModule().getTable(); Shaders_Construct(); m_shadersq3 = &GetShaderSystem(); } - -~MaterialAPI() -{ +~ShadersQ3API(){ Shaders_Destroy(); } - -ShaderSystem *getTable() -{ +ShaderSystem* getTable(){ return m_shadersq3; } }; -typedef SingletonModule > MaterialModule; -MaterialModule g_MaterialModule; +typedef SingletonModule > ShadersQ3Module; -extern "C" void -#ifdef _WIN32 -__declspec(dllexport) -#else -__attribute__((visibility("default"))) -#endif -Radiant_RegisterModules(ModuleServer &server) +ShadersQ3Module g_ShadersQ3Module; + + +class ShadersDoom3API { - initialiseModule(server); - g_MaterialModule.selfRegister(); +ShaderSystem* m_shadersdoom3; +public: +typedef ShaderSystem Type; +STRING_CONSTANT( Name, "doom3" ); + +ShadersDoom3API( ShadersDependencies& dependencies ){ + g_shadersExtension = "mtr"; + g_shadersDirectory = "materials/"; + g_enableDefaultShaders = false; + g_shaderLanguage = SHADERLANGUAGE_DOOM3; + g_useShaderList = false; + g_bitmapModule = dependencies.getBitmapModule().getTable(); + Shaders_Construct(); + m_shadersdoom3 = &GetShaderSystem(); +} +~ShadersDoom3API(){ + Shaders_Destroy(); +} +ShaderSystem* getTable(){ + return m_shadersdoom3; +} +}; + +typedef SingletonModule > ShadersDoom3Module; + +ShadersDoom3Module g_ShadersDoom3Module; + + +class ShadersQuake4API +{ +ShaderSystem* m_shadersquake4; +public: +typedef ShaderSystem Type; +STRING_CONSTANT( Name, "quake4" ); + +ShadersQuake4API( ShadersDependencies& dependencies ){ + g_shadersExtension = "mtr"; + g_shadersDirectory = "materials/"; + g_enableDefaultShaders = false; + g_shaderLanguage = SHADERLANGUAGE_QUAKE4; + g_useShaderList = false; + g_bitmapModule = dependencies.getBitmapModule().getTable(); + Shaders_Construct(); + m_shadersquake4 = &GetShaderSystem(); +} +~ShadersQuake4API(){ + Shaders_Destroy(); +} +ShaderSystem* getTable(){ + return m_shadersquake4; +} +}; + +typedef SingletonModule > ShadersQuake4Module; + +ShadersQuake4Module g_ShadersQuake4Module; + + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){ + initialiseModule( server ); + + g_ShadersQ3Module.selfRegister(); + g_ShadersDoom3Module.selfRegister(); + g_ShadersQuake4Module.selfRegister(); } diff --git a/plugins/shaders/shaders.cpp b/plugins/shaders/shaders.cpp index 4932543..3846942 100644 --- a/plugins/shaders/shaders.cpp +++ b/plugins/shaders/shaders.cpp @@ -31,9 +31,10 @@ // // Shaders Manager Plugin // -// Leonardo Zide (leo@lokigames.com) +// Leonardo Zide ( leo@lokigames.com ) // +#include "defaults.h" #include "shaders.h" #include "globaldefs.h" @@ -70,21 +71,27 @@ #include "archivelib.h" #include "imagelib.h" -const char *g_shadersExtension = ""; -const char *g_shadersDirectory = ""; +const char* g_shadersExtension = ""; +const char* g_shadersDirectory = ""; bool g_enableDefaultShaders = true; +ShaderLanguage g_shaderLanguage = SHADERLANGUAGE_QUAKE3; bool g_useShaderList = true; -_QERPlugImageTable *g_bitmapModule = 0; -const char *g_texturePrefix = "textures/"; +_QERPlugImageTable* g_bitmapModule = 0; +const char* g_texturePrefix = DEFAULT_TEXTURE_DIRNAME; void ActiveShaders_IteratorBegin(); + bool ActiveShaders_IteratorAtEnd(); + IShader *ActiveShaders_IteratorCurrent(); + void ActiveShaders_IteratorIncrement(); + Callback g_ActiveShadersChangedNotify; + void FreeShaders(); -void LoadShaderFile(const char *filename); -qtexture_t *Texture_ForName(const char *filename); + +void LoadShaderFile( const char *filename ); /*! NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX: @@ -92,48 +99,47 @@ qtexture_t *Texture_ForName(const char *filename); SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it this was in the initial design of the shader code since early GtkRadiant alpha, and got sort of foxed in 1.2 and put back in */ -Image *loadBitmap(void *environment, const char *name) -{ - DirectoryArchiveFile file(name, name); - if (!file.failed()) { - return g_bitmapModule->loadImage(file); + +Image* loadBitmap( void* environment, const char* name ){ + DirectoryArchiveFile file( name, name ); + if ( !file.failed() ) { + return g_bitmapModule->loadImage( file ); } return 0; } -inline byte *getPixel(byte *pixels, int width, int height, int x, int y) -{ - return pixels + (((((y + height) % height) * width) + ((x + width) % width)) * 4); +inline byte* getPixel( byte* pixels, int width, int height, int x, int y ){ + return pixels + ( ( ( ( ( y + height ) % height ) * width ) + ( ( x + width ) % width ) ) * 4 ); } -class KernelElement { +class KernelElement +{ public: int x, y; float w; }; -Image &convertHeightmapToNormalmap(Image &heightmap, float scale) -{ +Image& convertHeightmapToNormalmap( Image& heightmap, float scale ){ int w = heightmap.getWidth(); int h = heightmap.getHeight(); - Image &normalmap = *(new RGBAImage(heightmap.getWidth(), heightmap.getHeight())); + Image& normalmap = *( new RGBAImage( heightmap.getWidth(), heightmap.getHeight() ) ); - byte *in = heightmap.getRGBAPixels(); - byte *out = normalmap.getRGBAPixels(); + byte* in = heightmap.getRGBAPixels(); + byte* out = normalmap.getRGBAPixels(); - #if 1 +#if 1 // no filtering const int kernelSize = 2; KernelElement kernel_du[kernelSize] = { - {-1, 0, -0.5f}, - {1, 0, 0.5f} + {-1, 0,-0.5f }, + { 1, 0, 0.5f } }; KernelElement kernel_dv[kernelSize] = { - {0, 1, 0.5f}, - {0, -1, -0.5f} + { 0, 1, 0.5f }, + { 0,-1,-0.5f } }; - #else +#else // 3x3 Prewitt const int kernelSize = 6; KernelElement kernel_du[kernelSize] = { @@ -152,19 +158,23 @@ Image &convertHeightmapToNormalmap(Image &heightmap, float scale) { 0,-1,-1.0f }, { 1,-1,-1.0f } }; - #endif +#endif int x, y = 0; - while (y < h) { + while ( y < h ) + { x = 0; - while (x < w) { + while ( x < w ) + { float du = 0; - for (KernelElement *i = kernel_du; i != kernel_du + kernelSize; ++i) { - du += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w; + for ( KernelElement* i = kernel_du; i != kernel_du + kernelSize; ++i ) + { + du += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w; } float dv = 0; - for (KernelElement *i = kernel_dv; i != kernel_dv + kernelSize; ++i) { - dv += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w; + for ( KernelElement* i = kernel_dv; i != kernel_dv + kernelSize; ++i ) + { + dv += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w; } float nx = -du * scale; @@ -172,10 +182,10 @@ Image &convertHeightmapToNormalmap(Image &heightmap, float scale) float nz = 1.0; // Normalize - float norm = 1.0 / sqrt(nx * nx + ny * ny + nz * nz); - out[0] = float_to_integer(((nx * norm) + 1) * 127.5); - out[1] = float_to_integer(((ny * norm) + 1) * 127.5); - out[2] = float_to_integer(((nz * norm) + 1) * 127.5); + float norm = 1.0 / sqrt( nx * nx + ny * ny + nz * nz ); + out[0] = float_to_integer( ( ( nx * norm ) + 1 ) * 127.5 ); + out[1] = float_to_integer( ( ( ny * norm ) + 1 ) * 127.5 ); + out[2] = float_to_integer( ( ( nz * norm ) + 1 ) * 127.5 ); out[3] = 255; x++; @@ -188,31 +198,18 @@ Image &convertHeightmapToNormalmap(Image &heightmap, float scale) return normalmap; } -Image *loadHeightmap(void *environment, const char *name) -{ - Image *heightmap = GlobalTexturesCache().loadImage(name); - if (heightmap != 0) { - Image &normalmap = convertHeightmapToNormalmap(*heightmap, *reinterpret_cast( environment )); +Image* loadHeightmap( void* environment, const char* name ){ + Image* heightmap = GlobalTexturesCache().loadImage( name ); + if ( heightmap != 0 ) { + Image& normalmap = convertHeightmapToNormalmap( *heightmap, *reinterpret_cast( environment ) ); heightmap->release(); return &normalmap; } return 0; } -Image *loadSpecial(void *environment, const char *name) +class ShaderPoolContext { - if (*name == '_') { // special image - StringOutputStream bitmapName(256); - bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".tga"; - Image *image = loadBitmap(environment, bitmapName.c_str()); - if (image != 0) { - return image; - } - } - return GlobalTexturesCache().loadImage(name); -} - -class ShaderPoolContext { }; typedef Static ShaderPool; @@ -227,57 +224,57 @@ typedef CopiedString TextureExpression; // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive. //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present template -void parseTextureName(StringType &name, const char *token) -{ - StringOutputStream cleaned(256); - cleaned << PathCleaned(token); - name = CopiedString( - StringRange(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str()))).c_str(); // remove extension +void parseTextureName( StringType& name, const char* token ){ + StringOutputStream cleaned( 256 ); + cleaned << PathCleaned( token ); + name = CopiedString( StringRange( cleaned.c_str(), path_get_filename_base_end( cleaned.c_str() ) ) ).c_str(); // remove extension } -bool Tokeniser_parseTextureName(Tokeniser &tokeniser, TextureExpression &name) -{ - const char *token = tokeniser.getToken(); - if (token == 0) { - Tokeniser_unexpectedError(tokeniser, token, "#texture-name"); +bool Tokeniser_parseTextureName( Tokeniser& tokeniser, TextureExpression& name ){ + const char* token = tokeniser.getToken(); + if ( token == 0 ) { + Tokeniser_unexpectedError( tokeniser, token, "#texture-name" ); return false; } - parseTextureName(name, token); + parseTextureName( name, token ); return true; } -bool Tokeniser_parseShaderName(Tokeniser &tokeniser, CopiedString &name) -{ - const char *token = tokeniser.getToken(); - if (token == 0) { - Tokeniser_unexpectedError(tokeniser, token, "#shader-name"); +bool Tokeniser_parseShaderName( Tokeniser& tokeniser, CopiedString& name ){ + const char* token = tokeniser.getToken(); + if ( token == 0 ) { + Tokeniser_unexpectedError( tokeniser, token, "#shader-name" ); return false; } - parseTextureName(name, token); + parseTextureName( name, token ); return true; } -bool Tokeniser_parseString(Tokeniser &tokeniser, ShaderString &string) -{ - const char *token = tokeniser.getToken(); - if (token == 0) { - Tokeniser_unexpectedError(tokeniser, token, "#string"); +bool Tokeniser_parseString( Tokeniser& tokeniser, ShaderString& string ){ + const char* token = tokeniser.getToken(); + if ( token == 0 ) { + Tokeniser_unexpectedError( tokeniser, token, "#string" ); return false; } string = token; return true; } + typedef std::list ShaderParameters; typedef std::list ShaderArguments; + typedef std::pair BlendFuncExpression; -class ShaderTemplate { +class ShaderTemplate +{ std::size_t m_refcount; CopiedString m_Name; +CopiedString m_WadName; public: ShaderParameters m_params; + TextureExpression m_textureName; TextureExpression m_diffuse; TextureExpression m_bump; @@ -287,7 +284,6 @@ TextureExpression m_lightFalloffImage; int m_nFlags; float m_fTrans; -int m_iPolygonOffset; // alphafunc stuff IShader::EAlphaFunc m_AlphaFunc; @@ -296,88 +292,82 @@ float m_AlphaRef; IShader::ECull m_Cull; ShaderTemplate() : - m_refcount(0) -{ + m_refcount( 0 ){ m_nFlags = 0; m_fTrans = 1.0f; } -void IncRef() -{ +void IncRef(){ ++m_refcount; } -void DecRef() -{ - ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero"); - if (--m_refcount == 0) { +void DecRef(){ + ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" ); + if ( --m_refcount == 0 ) { delete this; } } -std::size_t refcount() -{ +std::size_t refcount(){ return m_refcount; } -const char *getName() const -{ +const char* getName() const { return m_Name.c_str(); } -void setName(const char *name) -{ +void setName( const char* name ){ m_Name = name; } // ----------------------------------------- -bool parseMaterial(Tokeniser &tokeniser); -bool parseTemplate(Tokeniser &tokeniser); +bool parseDoom3( Tokeniser& tokeniser ); + +bool parseQuake3( Tokeniser& tokeniser ); + +bool parseTemplate( Tokeniser& tokeniser ); -void CreateDefault(const char *name) -{ - /*if (g_enableDefaultShaders) { - m_textureName = name; - } else { - m_textureName = ""; - }*/ - setName(name); +void CreateDefault( const char *name ){ + if ( g_enableDefaultShaders ) { + m_textureName = name; + } + else + { + m_textureName = ""; + } + setName( name ); } -class MapLayerTemplate { + +class MapLayerTemplate +{ TextureExpression m_texture; BlendFuncExpression m_blendFunc; bool m_clampToBorder; ShaderValue m_alphaTest; public: -MapLayerTemplate(const TextureExpression &texture, const BlendFuncExpression &blendFunc, bool clampToBorder, - const ShaderValue &alphaTest) : - m_texture(texture), - m_blendFunc(blendFunc), - m_clampToBorder(false), - m_alphaTest(alphaTest) -{ +MapLayerTemplate( const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest ) : + m_texture( texture ), + m_blendFunc( blendFunc ), + m_clampToBorder( false ), + m_alphaTest( alphaTest ){ } -const TextureExpression &texture() const -{ +const TextureExpression& texture() const { return m_texture; } -const BlendFuncExpression &blendFunc() const -{ +const BlendFuncExpression& blendFunc() const { return m_blendFunc; } -bool clampToBorder() const -{ +bool clampToBorder() const { return m_clampToBorder; } -const ShaderValue &alphaTest() const -{ +const ShaderValue& alphaTest() const { return m_alphaTest; } }; @@ -386,7 +376,49 @@ typedef std::vector MapLayers; MapLayers m_layers; }; -enum LayerTypeId { + +bool Doom3Shader_parseHeightmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){ + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) ); + RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) ); + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) ); + RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, heightmapScale ) ); + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) ); + return true; +} + +bool Doom3Shader_parseAddnormals( Tokeniser& tokeniser, TextureExpression& bump ){ + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) ); + RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) ); + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) ); + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "heightmap" ) ); + TextureExpression heightmapName; + ShaderValue heightmapScale; + RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, heightmapName, heightmapScale ) ); + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) ); + return true; +} + +bool Doom3Shader_parseBumpmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){ + const char* token = tokeniser.getToken(); + if ( token == 0 ) { + Tokeniser_unexpectedError( tokeniser, token, "#bumpmap" ); + return false; + } + if ( string_equal( token, "heightmap" ) ) { + RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, bump, heightmapScale ) ); + } + else if ( string_equal( token, "addnormals" ) ) { + RETURN_FALSE_IF_FAIL( Doom3Shader_parseAddnormals( tokeniser, bump ) ); + } + else + { + parseTextureName( bump, token ); + } + return true; +} + +enum LayerTypeId +{ LAYER_NONE, LAYER_BLEND, LAYER_DIFFUSEMAP, @@ -394,7 +426,8 @@ enum LayerTypeId { LAYER_SPECULARMAP }; -class LayerTemplate { +class LayerTemplate +{ public: LayerTypeId m_type; TextureExpression m_texture; @@ -403,42 +436,245 @@ bool m_clampToBorder; ShaderValue m_alphaTest; ShaderValue m_heightmapScale; -LayerTemplate() : m_type(LAYER_NONE), m_blendFunc("GL_ONE", "GL_ZERO"), m_clampToBorder(false), m_alphaTest("-1"), - m_heightmapScale("0") -{ +LayerTemplate() : m_type( LAYER_NONE ), m_blendFunc( "GL_ONE", "GL_ZERO" ), m_clampToBorder( false ), m_alphaTest( "-1" ), m_heightmapScale( "0" ){ } }; -bool parseShaderParameters(Tokeniser &tokeniser, ShaderParameters ¶ms) -{ - Tokeniser_parseToken(tokeniser, "("); - for (;;) { - const char *param = tokeniser.getToken(); - if (string_equal(param, ")")) { +bool parseShaderParameters( Tokeniser& tokeniser, ShaderParameters& params ){ + Tokeniser_parseToken( tokeniser, "(" ); + for (;; ) + { + const char* param = tokeniser.getToken(); + if ( string_equal( param, ")" ) ) { break; } - params.push_back(param); - const char *comma = tokeniser.getToken(); - if (string_equal(comma, ")")) { + params.push_back( param ); + const char* comma = tokeniser.getToken(); + if ( string_equal( comma, ")" ) ) { break; } - if (!string_equal(comma, ",")) { - Tokeniser_unexpectedError(tokeniser, comma, ","); + if ( !string_equal( comma, "," ) ) { + Tokeniser_unexpectedError( tokeniser, comma, "," ); return false; } } return true; } -bool ShaderTemplate::parseTemplate(Tokeniser &tokeniser) -{ +bool ShaderTemplate::parseTemplate( Tokeniser& tokeniser ){ m_Name = tokeniser.getToken(); - if (!parseShaderParameters(tokeniser, m_params)) { - globalErrorStream() << "shader template: " << makeQuoted(m_Name.c_str()) << ": parameter parse failed\n"; + if ( !parseShaderParameters( tokeniser, m_params ) ) { + globalErrorStream() << "shader template: " << makeQuoted( m_Name.c_str() ) << ": parameter parse failed\n"; return false; } - return parseMaterial(tokeniser); + return parseDoom3( tokeniser ); +} + +bool ShaderTemplate::parseDoom3( Tokeniser& tokeniser ){ + LayerTemplate currentLayer; + bool isFog = false; + + // we need to read until we hit a balanced } + int depth = 0; + for (;; ) + { + tokeniser.nextLine(); + const char* token = tokeniser.getToken(); + + if ( token == 0 ) { + return false; + } + + if ( string_equal( token, "{" ) ) { + ++depth; + continue; + } + else if ( string_equal( token, "}" ) ) { + --depth; + if ( depth < 0 ) { // error + return false; + } + if ( depth == 0 ) { // end of shader + break; + } + if ( depth == 1 ) { // end of layer + if ( currentLayer.m_type == LAYER_DIFFUSEMAP ) { + m_diffuse = currentLayer.m_texture; + } + else if ( currentLayer.m_type == LAYER_BUMPMAP ) { + m_bump = currentLayer.m_texture; + } + else if ( currentLayer.m_type == LAYER_SPECULARMAP ) { + m_specular = currentLayer.m_texture; + } + else if ( !string_empty( currentLayer.m_texture.c_str() ) ) { + m_layers.push_back( MapLayerTemplate( + currentLayer.m_texture.c_str(), + currentLayer.m_blendFunc, + currentLayer.m_clampToBorder, + currentLayer.m_alphaTest + ) ); + } + currentLayer.m_type = LAYER_NONE; + currentLayer.m_texture = ""; + } + continue; + } + + if ( depth == 2 ) { // in layer + if ( string_equal_nocase( token, "blend" ) ) { + const char* blend = tokeniser.getToken(); + + if ( blend == 0 ) { + Tokeniser_unexpectedError( tokeniser, blend, "#blend" ); + return false; + } + + if ( string_equal_nocase( blend, "diffusemap" ) ) { + currentLayer.m_type = LAYER_DIFFUSEMAP; + } + else if ( string_equal_nocase( blend, "bumpmap" ) ) { + currentLayer.m_type = LAYER_BUMPMAP; + } + else if ( string_equal_nocase( blend, "specularmap" ) ) { + currentLayer.m_type = LAYER_SPECULARMAP; + } + else + { + currentLayer.m_blendFunc.first = blend; + + const char* comma = tokeniser.getToken(); + + if ( comma == 0 ) { + Tokeniser_unexpectedError( tokeniser, comma, "#comma" ); + return false; + } + + if ( string_equal( comma, "," ) ) { + RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, currentLayer.m_blendFunc.second ) ); + } + else + { + currentLayer.m_blendFunc.second = ""; + tokeniser.ungetToken(); + } + } + } + else if ( string_equal_nocase( token, "map" ) ) { + if ( currentLayer.m_type == LAYER_BUMPMAP ) { + RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale ) ); + } + else + { + const char* map = tokeniser.getToken(); + + if ( map == 0 ) { + Tokeniser_unexpectedError( tokeniser, map, "#map" ); + return false; + } + + if ( string_equal( map, "makealpha" ) ) { + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) ); + const char* texture = tokeniser.getToken(); + if ( texture == 0 ) { + Tokeniser_unexpectedError( tokeniser, texture, "#texture" ); + return false; + } + currentLayer.m_texture = texture; + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) ); + } + else + { + parseTextureName( currentLayer.m_texture, map ); + } + } + } + else if ( string_equal_nocase( token, "zeroclamp" ) ) { + currentLayer.m_clampToBorder = true; + } +#if 0 + else if ( string_equal_nocase( token, "alphaTest" ) ) { + Tokeniser_getFloat( tokeniser, currentLayer.m_alphaTest ); + } +#endif + } + else if ( depth == 1 ) { + if ( string_equal_nocase( token, "qer_editorimage" ) ) { + RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) ); + } + else if ( string_equal_nocase( token, "qer_trans" ) ) { + m_fTrans = string_read_float( tokeniser.getToken() ); + m_nFlags |= QER_TRANS; + } + else if ( string_equal_nocase( token, "translucent" ) ) { + m_fTrans = 1; + m_nFlags |= QER_TRANS; + } + else if ( string_equal( token, "DECAL_MACRO" ) ) { + m_fTrans = 1; + m_nFlags |= QER_TRANS; + } + else if ( string_equal_nocase( token, "bumpmap" ) ) { + RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, m_bump, m_heightmapScale ) ); + } + else if ( string_equal_nocase( token, "diffusemap" ) ) { + RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_diffuse ) ); + } + else if ( string_equal_nocase( token, "specularmap" ) ) { + RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_specular ) ); + } + else if ( string_equal_nocase( token, "twosided" ) ) { + m_Cull = IShader::eCullNone; + m_nFlags |= QER_CULL; + } + else if ( string_equal_nocase( token, "nodraw" ) ) { + m_nFlags |= QER_NODRAW; + } + else if ( string_equal_nocase( token, "nonsolid" ) ) { + m_nFlags |= QER_NONSOLID; + } + else if ( string_equal_nocase( token, "liquid" ) ) { + m_nFlags |= QER_WATER; + } + else if ( string_equal_nocase( token, "areaportal" ) ) { + m_nFlags |= QER_AREAPORTAL; + } + else if ( string_equal_nocase( token, "playerclip" ) + || string_equal_nocase( token, "monsterclip" ) + || string_equal_nocase( token, "ikclip" ) + || string_equal_nocase( token, "moveableclip" ) ) { + m_nFlags |= QER_CLIP; + } + if ( string_equal_nocase( token, "fogLight" ) ) { + isFog = true; + } + else if ( !isFog && string_equal_nocase( token, "lightFalloffImage" ) ) { + const char* lightFalloffImage = tokeniser.getToken(); + if ( lightFalloffImage == 0 ) { + Tokeniser_unexpectedError( tokeniser, lightFalloffImage, "#lightFalloffImage" ); + return false; + } + if ( string_equal_nocase( lightFalloffImage, "makeintensity" ) ) { + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) ); + TextureExpression name; + RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, name ) ); + m_lightFalloffImage = name; + RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) ); + } + else + { + m_lightFalloffImage = lightFalloffImage; + } + } + } + } + + if ( string_empty( m_textureName.c_str() ) ) { + m_textureName = m_diffuse; + } + + return true; } typedef SmartPointer ShaderTemplatePointer; @@ -447,304 +683,289 @@ typedef std::map ShaderTemplateMap; ShaderTemplateMap g_shaders; ShaderTemplateMap g_shaderTemplates; -ShaderTemplate *findTemplate(const char *name) -{ - ShaderTemplateMap::iterator i = g_shaderTemplates.find(name); - if (i != g_shaderTemplates.end()) { - return (*i).second.get(); +ShaderTemplate* findTemplate( const char* name ){ + ShaderTemplateMap::iterator i = g_shaderTemplates.find( name ); + if ( i != g_shaderTemplates.end() ) { + return ( *i ).second.get(); } return 0; } -class ShaderDefinition { -public: -ShaderDefinition(ShaderTemplate *shaderTemplate, const ShaderArguments &args, const char *filename) - : shaderTemplate(shaderTemplate), args(args), filename(filename) +class ShaderDefinition { +public: +ShaderDefinition( ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename ) + : shaderTemplate( shaderTemplate ), args( args ), filename( filename ){ } -ShaderTemplate *shaderTemplate; +ShaderTemplate* shaderTemplate; ShaderArguments args; -const char *filename; +const char* filename; }; typedef std::map ShaderDefinitionMap; + ShaderDefinitionMap g_shaderDefinitions; -bool parseTemplateInstance(Tokeniser &tokeniser, const char *filename) -{ +bool parseTemplateInstance( Tokeniser& tokeniser, const char* filename ){ CopiedString name; - RETURN_FALSE_IF_FAIL(Tokeniser_parseShaderName(tokeniser, name)); - const char *templateName = tokeniser.getToken(); - ShaderTemplate *shaderTemplate = findTemplate(templateName); - if (shaderTemplate == 0) { - globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": shader template not found: " - << makeQuoted(templateName) << "\n"; + RETURN_FALSE_IF_FAIL( Tokeniser_parseShaderName( tokeniser, name ) ); + const char* templateName = tokeniser.getToken(); + ShaderTemplate* shaderTemplate = findTemplate( templateName ); + if ( shaderTemplate == 0 ) { + globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": shader template not found: " << makeQuoted( templateName ) << "\n"; } ShaderArguments args; - if (!parseShaderParameters(tokeniser, args)) { - globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": argument parse failed\n"; + if ( !parseShaderParameters( tokeniser, args ) ) { + globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": argument parse failed\n"; return false; } - if (shaderTemplate != 0) { - if (!g_shaderDefinitions.insert( - ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate, args, filename))).second) { - globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) - << ": already exists, second definition ignored\n"; + if ( shaderTemplate != 0 ) { + if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate, args, filename ) ) ).second ) { + globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": already exists, second definition ignored\n"; } } return true; } -const char *evaluateShaderValue(const char *value, const ShaderParameters ¶ms, const ShaderArguments &args) -{ + +const char* evaluateShaderValue( const char* value, const ShaderParameters& params, const ShaderArguments& args ){ ShaderArguments::const_iterator j = args.begin(); - for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) { - const char *other = (*i).c_str(); - if (string_equal(value, other)) { - return (*j).c_str(); + for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j ) + { + const char* other = ( *i ).c_str(); + if ( string_equal( value, other ) ) { + return ( *j ).c_str(); } } return value; } ///\todo BlendFunc parsing -BlendFunc -evaluateBlendFunc(const BlendFuncExpression &blendFunc, const ShaderParameters ¶ms, const ShaderArguments &args) -{ - return BlendFunc(BLEND_ONE, BLEND_ZERO); +BlendFunc evaluateBlendFunc( const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args ){ + return BlendFunc( BLEND_ONE, BLEND_ZERO ); } -qtexture_t * -evaluateTexture(const TextureExpression &texture, const ShaderParameters ¶ms, const ShaderArguments &args, - const LoadImageCallback &loader = GlobalTexturesCache().defaultLoader()) -{ - StringOutputStream result(64); - const char *expression = texture.c_str(); - const char *end = expression + string_length(expression); - if (!string_empty(expression)) { - for (;;) { - const char *best = end; - const char *bestParam = 0; - const char *bestArg = 0; +qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){ + StringOutputStream result( 64 ); + const char* expression = texture.c_str(); + const char* end = expression + string_length( expression ); + if ( !string_empty( expression ) ) { + for (;; ) + { + const char* best = end; + const char* bestParam = 0; + const char* bestArg = 0; ShaderArguments::const_iterator j = args.begin(); - for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) { - const char *found = strstr(expression, (*i).c_str()); - if (found != 0 && found < best) { + for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j ) + { + const char* found = strstr( expression, ( *i ).c_str() ); + if ( found != 0 && found < best ) { best = found; - bestParam = (*i).c_str(); - bestArg = (*j).c_str(); + bestParam = ( *i ).c_str(); + bestArg = ( *j ).c_str(); } } - if (best != end) { - result << StringRange(expression, best); - result << PathCleaned(bestArg); - expression = best + string_length(bestParam); - } else { + if ( best != end ) { + result << StringRange( expression, best ); + result << PathCleaned( bestArg ); + expression = best + string_length( bestParam ); + } + else + { break; } } result << expression; } - return GlobalTexturesCache().capture(loader, result.c_str()); + return GlobalTexturesCache().capture( loader, result.c_str() ); } -float evaluateFloat(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args) -{ - const char *result = evaluateShaderValue(value.c_str(), params, args); - float f = 0.0f; - if (!string_parse_float(result, f)) { - globalErrorStream() << "parsing float value failed: " << makeQuoted(result) << "\n"; +float evaluateFloat( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){ + const char* result = evaluateShaderValue( value.c_str(), params, args ); + float f; + if ( !string_parse_float( result, f ) ) { + globalErrorStream() << "parsing float value failed: " << makeQuoted( result ) << "\n"; } return f; } -BlendFactor evaluateBlendFactor(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args) -{ - const char *result = evaluateShaderValue(value.c_str(), params, args); +BlendFactor evaluateBlendFactor( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){ + const char* result = evaluateShaderValue( value.c_str(), params, args ); - if (string_equal_nocase(result, "gl_zero")) { + if ( string_equal_nocase( result, "gl_zero" ) ) { return BLEND_ZERO; } - if (string_equal_nocase(result, "gl_one")) { + if ( string_equal_nocase( result, "gl_one" ) ) { return BLEND_ONE; } - if (string_equal_nocase(result, "gl_src_color")) { + if ( string_equal_nocase( result, "gl_src_color" ) ) { return BLEND_SRC_COLOUR; } - if (string_equal_nocase(result, "gl_one_minus_src_color")) { + if ( string_equal_nocase( result, "gl_one_minus_src_color" ) ) { return BLEND_ONE_MINUS_SRC_COLOUR; } - if (string_equal_nocase(result, "gl_src_alpha")) { + if ( string_equal_nocase( result, "gl_src_alpha" ) ) { return BLEND_SRC_ALPHA; } - if (string_equal_nocase(result, "gl_one_minus_src_alpha")) { + if ( string_equal_nocase( result, "gl_one_minus_src_alpha" ) ) { return BLEND_ONE_MINUS_SRC_ALPHA; } - if (string_equal_nocase(result, "gl_dst_color")) { + if ( string_equal_nocase( result, "gl_dst_color" ) ) { return BLEND_DST_COLOUR; } - if (string_equal_nocase(result, "gl_one_minus_dst_color")) { + if ( string_equal_nocase( result, "gl_one_minus_dst_color" ) ) { return BLEND_ONE_MINUS_DST_COLOUR; } - if (string_equal_nocase(result, "gl_dst_alpha")) { + if ( string_equal_nocase( result, "gl_dst_alpha" ) ) { return BLEND_DST_ALPHA; } - if (string_equal_nocase(result, "gl_one_minus_dst_alpha")) { + if ( string_equal_nocase( result, "gl_one_minus_dst_alpha" ) ) { return BLEND_ONE_MINUS_DST_ALPHA; } - if (string_equal_nocase(result, "gl_src_alpha_saturate")) { + if ( string_equal_nocase( result, "gl_src_alpha_saturate" ) ) { return BLEND_SRC_ALPHA_SATURATE; } - globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted(result) << "\n"; + globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted( result ) << "\n"; return BLEND_ZERO; } -class CShader : public IShader { +class CShader : public IShader +{ std::size_t m_refcount; -const ShaderTemplate &m_template; -const ShaderArguments &m_args; -const char *m_filename; -// name is shader-name, otherwise texture-name (if not a real shader) + +const ShaderTemplate& m_template; +const ShaderArguments& m_args; +const char* m_filename; +// name is shader-name, otherwise texture-name ( if not a real shader ) CopiedString m_Name; -qtexture_t *m_pTexture; -qtexture_t *m_notfound; -qtexture_t *m_pDiffuse; +CopiedString m_WadName; + +qtexture_t* m_pTexture; +qtexture_t* m_notfound; +qtexture_t* m_pDiffuse; float m_heightmapScale; -qtexture_t *m_pBump; -qtexture_t *m_pSpecular; -qtexture_t *m_pLightFalloffImage; +qtexture_t* m_pBump; +qtexture_t* m_pSpecular; +qtexture_t* m_pLightFalloffImage; BlendFunc m_blendFunc; + bool m_bInUse; + public: -CShader(const ShaderDefinition &definition) : - m_refcount(0), - m_template(*definition.shaderTemplate), - m_args(definition.args), - m_filename(definition.filename), - m_blendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA), - m_bInUse(false) -{ +static bool m_lightingEnabled; + +CShader( const ShaderDefinition& definition ) : + m_refcount( 0 ), + m_template( *definition.shaderTemplate ), + m_args( definition.args ), + m_filename( definition.filename ), + m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ), + m_bInUse( false ){ m_pTexture = 0; m_pDiffuse = 0; m_pBump = 0; m_pSpecular = 0; + m_notfound = 0; + realise(); } -virtual ~CShader() -{ +virtual ~CShader(){ unrealise(); - ASSERT_MESSAGE(m_refcount == 0, "deleting active shader"); + ASSERT_MESSAGE( m_refcount == 0, "deleting active shader" ); } // IShaders implementation ----------------- -void IncRef() -{ +void IncRef(){ ++m_refcount; } -void DecRef() -{ - ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero"); - if (--m_refcount == 0) { +void DecRef(){ + ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" ); + if ( --m_refcount == 0 ) { delete this; } } -std::size_t refcount() -{ +std::size_t refcount(){ return m_refcount; } // get/set the qtexture_t* Radiant uses to represent this shader object -qtexture_t *getTexture() const -{ +qtexture_t* getTexture() const { return m_pTexture; } -qtexture_t *getDiffuse() const -{ +qtexture_t* getDiffuse() const { return m_pDiffuse; } -qtexture_t *getBump() const -{ +qtexture_t* getBump() const { return m_pBump; } -qtexture_t *getSpecular() const -{ +qtexture_t* getSpecular() const { return m_pSpecular; } // get shader name -const char *getName() const -{ +const char* getName() const { return m_Name.c_str(); } -bool IsInUse() const -{ +const char* getWadName() const { + return m_WadName.c_str(); +} + +bool IsInUse() const { return m_bInUse; } -void SetInUse(bool bInUse) -{ +void SetInUse( bool bInUse ){ m_bInUse = bInUse; g_ActiveShadersChangedNotify(); } // get the shader flags -int getFlags() const -{ +int getFlags() const { return m_template.m_nFlags; } // get the transparency value -float getTrans() const -{ +float getTrans() const { return m_template.m_fTrans; } int getPolygonOffset() const { - return m_template.m_iPolygonOffset; + return 0; } // test if it's a true shader, or a default shader created to wrap around a texture -bool IsDefault() const -{ - return string_empty(m_filename); +bool IsDefault() const { + return string_empty( m_filename ); } // get the alphaFunc -void getAlphaFunc(EAlphaFunc *func, float *ref) -{ - *func = m_template.m_AlphaFunc; - *ref = m_template.m_AlphaRef; -}; - -BlendFunc getBlendFunc() const -{ +void getAlphaFunc( EAlphaFunc *func, float *ref ) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; }; +BlendFunc getBlendFunc() const { return m_blendFunc; } // get the cull type -ECull getCull() -{ +ECull getCull(){ return m_template.m_Cull; }; -// get shader file name (ie the file where this one is defined) -const char *getShaderFileName() const -{ +// get shader file name ( ie the file where this one is defined ) +const char* getShaderFileName() const { return m_filename; } // ----------------------------------------- @@ -763,73 +984,135 @@ void realise() } } -void unrealise() -{ - GlobalTexturesCache().release(m_pTexture); +void unrealise(){ + GlobalTexturesCache().release( m_pTexture ); - if (m_notfound != 0) { - GlobalTexturesCache().release(m_notfound); + if ( m_notfound != 0 ) { + GlobalTexturesCache().release( m_notfound ); + } + + unrealiseLighting(); +} + +void realiseLighting(){ + if ( m_lightingEnabled ) { + LoadImageCallback loader = GlobalTexturesCache().defaultLoader(); + if ( !string_empty( m_template.m_heightmapScale.c_str() ) ) { + m_heightmapScale = evaluateFloat( m_template.m_heightmapScale, m_template.m_params, m_args ); + loader = LoadImageCallback( &m_heightmapScale, loadHeightmap ); + } + m_pDiffuse = evaluateTexture( m_template.m_diffuse, m_template.m_params, m_args ); + m_pBump = evaluateTexture( m_template.m_bump, m_template.m_params, m_args, loader ); + m_pSpecular = evaluateTexture( m_template.m_specular, m_template.m_params, m_args ); + m_pLightFalloffImage = evaluateTexture( m_template.m_lightFalloffImage, m_template.m_params, m_args ); + + for ( ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i ) + { + m_layers.push_back( evaluateLayer( *i, m_template.m_params, m_args ) ); + } + + if ( m_layers.size() == 1 ) { + const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc(); + if ( !string_empty( blendFunc.second.c_str() ) ) { + m_blendFunc = BlendFunc( + evaluateBlendFactor( blendFunc.first.c_str(), m_template.m_params, m_args ), + evaluateBlendFactor( blendFunc.second.c_str(), m_template.m_params, m_args ) + ); + } + else + { + const char* blend = evaluateShaderValue( blendFunc.first.c_str(), m_template.m_params, m_args ); + + if ( string_equal_nocase( blend, "add" ) ) { + m_blendFunc = BlendFunc( BLEND_ONE, BLEND_ONE ); + } + else if ( string_equal_nocase( blend, "filter" ) ) { + m_blendFunc = BlendFunc( BLEND_DST_COLOUR, BLEND_ZERO ); + } + else if ( string_equal_nocase( blend, "blend" ) ) { + m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ); + } + else + { + globalErrorStream() << "parsing blend value failed: " << makeQuoted( blend ) << "\n"; + } + } + } + } +} + +void unrealiseLighting(){ + if ( m_lightingEnabled ) { + GlobalTexturesCache().release( m_pDiffuse ); + GlobalTexturesCache().release( m_pBump ); + GlobalTexturesCache().release( m_pSpecular ); + + GlobalTexturesCache().release( m_pLightFalloffImage ); + + for ( MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i ) + { + GlobalTexturesCache().release( ( *i ).texture() ); + } + m_layers.clear(); + + m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ); } } // set shader name -void setName(const char *name) -{ +void setName( const char* name ){ m_Name = name; } -class MapLayer : public ShaderLayer { -qtexture_t *m_texture; +void setWadName( const char* name ){ + m_WadName = name; +} + +class MapLayer : public ShaderLayer +{ +qtexture_t* m_texture; BlendFunc m_blendFunc; bool m_clampToBorder; float m_alphaTest; public: -MapLayer(qtexture_t *texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest) : - m_texture(texture), - m_blendFunc(blendFunc), - m_clampToBorder(false), - m_alphaTest(alphaTest) -{ +MapLayer( qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest ) : + m_texture( texture ), + m_blendFunc( blendFunc ), + m_clampToBorder( false ), + m_alphaTest( alphaTest ){ } -qtexture_t *texture() const -{ +qtexture_t* texture() const { return m_texture; } -BlendFunc blendFunc() const -{ +BlendFunc blendFunc() const { return m_blendFunc; } -bool clampToBorder() const -{ +bool clampToBorder() const { return m_clampToBorder; } -float alphaTest() const -{ +float alphaTest() const { return m_alphaTest; } }; -static MapLayer evaluateLayer(const ShaderTemplate::MapLayerTemplate &layerTemplate, const ShaderParameters ¶ms, - const ShaderArguments &args) -{ +static MapLayer evaluateLayer( const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args ){ return MapLayer( - evaluateTexture(layerTemplate.texture(), params, args), - evaluateBlendFunc(layerTemplate.blendFunc(), params, args), - layerTemplate.clampToBorder(), - evaluateFloat(layerTemplate.alphaTest(), params, args) - ); + evaluateTexture( layerTemplate.texture(), params, args ), + evaluateBlendFunc( layerTemplate.blendFunc(), params, args ), + layerTemplate.clampToBorder(), + evaluateFloat( layerTemplate.alphaTest(), params, args ) + ); } typedef std::vector MapLayers; MapLayers m_layers; -const ShaderLayer *firstLayer() const -{ - if (m_layers.empty()) { +const ShaderLayer* firstLayer() const { + if ( m_layers.empty() ) { return 0; } return &m_layers.front(); @@ -842,54 +1125,52 @@ void forEachLayer(const ShaderLayerCallback &callback) const } } -qtexture_t *lightFalloffImage() const -{ - if (!string_empty(m_template.m_lightFalloffImage.c_str())) { +qtexture_t* lightFalloffImage() const { + if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) { return m_pLightFalloffImage; } return 0; } }; +bool CShader::m_lightingEnabled = false; + typedef SmartPointer ShaderPointer; typedef std::map shaders_t; + shaders_t g_ActiveShaders; + static shaders_t::iterator g_ActiveShadersIterator; -void ActiveShaders_IteratorBegin() -{ +void ActiveShaders_IteratorBegin(){ g_ActiveShadersIterator = g_ActiveShaders.begin(); } -bool ActiveShaders_IteratorAtEnd() -{ +bool ActiveShaders_IteratorAtEnd(){ return g_ActiveShadersIterator == g_ActiveShaders.end(); } -IShader *ActiveShaders_IteratorCurrent() -{ - return static_cast( g_ActiveShadersIterator->second ); +IShader *ActiveShaders_IteratorCurrent(){ + return static_cast( g_ActiveShadersIterator->second ); } -void ActiveShaders_IteratorIncrement() -{ +void ActiveShaders_IteratorIncrement(){ ++g_ActiveShadersIterator; } -void debug_check_shaders(shaders_t &shaders) -{ - for (shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i) { - ASSERT_MESSAGE(i->second->refcount() == 1, "orphan shader still referenced"); +void debug_check_shaders( shaders_t& shaders ){ + for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i ) + { + ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" ); } } // will free all GL binded qtextures and shaders // NOTE: doesn't make much sense out of Radiant exit or called during a reload -void FreeShaders() -{ +void FreeShaders(){ // reload shaders // empty the actives shaders list - debug_check_shaders(g_ActiveShaders); + debug_check_shaders( g_ActiveShaders ); g_ActiveShaders.clear(); g_shaders.clear(); g_shaderTemplates.clear(); @@ -897,119 +1178,140 @@ void FreeShaders() g_ActiveShadersChangedNotify(); } -bool ShaderTemplate::parseMaterial(Tokeniser &tokeniser) -{ +bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){ + // name of the qtexture_t we'll use to represent this shader ( this one has the "textures\" before ) + m_textureName = m_Name.c_str(); + tokeniser.nextLine(); // we need to read until we hit a balanced } int depth = 0; - for (;;) { + for (;; ) + { tokeniser.nextLine(); - const char *token = tokeniser.getToken(); + const char* token = tokeniser.getToken(); - if (token == 0) { + if ( token == 0 ) { return false; } - if (string_equal(token, "{")) { + if ( string_equal( token, "{" ) ) { ++depth; continue; - } else if (string_equal(token, "}")) { + } + else if ( string_equal( token, "}" ) ) { --depth; - if (depth < 0) { // underflow + if ( depth < 0 ) { // underflow return false; } - if (depth == 0) { // end of shader + if ( depth == 0 ) { // end of shader break; } continue; } - if (depth == 1) { - if (string_equal_nocase(token, "qer_nocarve")) { + if ( depth == 1 ) { + if ( string_equal_nocase( token, "qer_nocarve" ) ) { m_nFlags |= QER_NOCARVE; - } else if (string_equal_nocase(token, "polygonoffset")) { - if (Tokeniser_getInteger(tokeniser, m_iPolygonOffset) == false) { - m_iPolygonOffset = 1; - } - m_nFlags |= QER_POLYOFS; - } else if (string_equal_nocase(token, "qer_trans")) { - RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_fTrans)); + } + else if ( string_equal_nocase( token, "qer_trans" ) ) { + RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) ); m_nFlags |= QER_TRANS; - } else if (string_equal_nocase(token, "qer_editorimage") || string_equal_nocase(token, "diffusemap")) { - RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName)); - } else if (string_equal_nocase(token, "qer_alphafunc")) { - const char *alphafunc = tokeniser.getToken(); + } + else if ( string_equal_nocase( token, "qer_editorimage" ) ) { + RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) ); + } + else if ( string_equal_nocase( token, "qer_alphafunc" ) ) { + const char* alphafunc = tokeniser.getToken(); - if (alphafunc == 0) { - Tokeniser_unexpectedError(tokeniser, alphafunc, "#alphafunc"); + if ( alphafunc == 0 ) { + Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" ); return false; } - if (string_equal_nocase(alphafunc, "equal")) { + if ( string_equal_nocase( alphafunc, "equal" ) ) { m_AlphaFunc = IShader::eEqual; - } else if (string_equal_nocase(alphafunc, "greater")) { + } + else if ( string_equal_nocase( alphafunc, "greater" ) ) { m_AlphaFunc = IShader::eGreater; - } else if (string_equal_nocase(alphafunc, "less")) { + } + else if ( string_equal_nocase( alphafunc, "less" ) ) { m_AlphaFunc = IShader::eLess; - } else if (string_equal_nocase(alphafunc, "gequal")) { + } + else if ( string_equal_nocase( alphafunc, "gequal" ) ) { m_AlphaFunc = IShader::eGEqual; - } else if (string_equal_nocase(alphafunc, "lequal")) { + } + else if ( string_equal_nocase( alphafunc, "lequal" ) ) { m_AlphaFunc = IShader::eLEqual; - } else { + } + else + { m_AlphaFunc = IShader::eAlways; } m_nFlags |= QER_ALPHATEST; - RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_AlphaRef)); - } else if (string_equal_nocase(token, "cull")) { - const char *cull = tokeniser.getToken(); - if (cull == 0) { - Tokeniser_unexpectedError(tokeniser, cull, "#cull"); + RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) ); + } + else if ( string_equal_nocase( token, "cull" ) ) { + const char* cull = tokeniser.getToken(); + + if ( cull == 0 ) { + Tokeniser_unexpectedError( tokeniser, cull, "#cull" ); return false; } - if (string_equal_nocase(cull, "none") - || string_equal_nocase(cull, "twosided") - || string_equal_nocase(cull, "disable")) { + if ( string_equal_nocase( cull, "none" ) + || string_equal_nocase( cull, "twosided" ) + || string_equal_nocase( cull, "disable" ) ) { m_Cull = IShader::eCullNone; - } else if (string_equal_nocase(cull, "back") - || string_equal_nocase(cull, "backside") - || string_equal_nocase(cull, "backsided")) { + } + else if ( string_equal_nocase( cull, "back" ) + || string_equal_nocase( cull, "backside" ) + || string_equal_nocase( cull, "backsided" ) ) { m_Cull = IShader::eCullBack; - } else { + } + else + { m_Cull = IShader::eCullBack; } m_nFlags |= QER_CULL; - } else if (string_equal_nocase(token, "surfaceparm")) { - const char *surfaceparm = tokeniser.getToken(); + } + else if ( string_equal_nocase( token, "surfaceparm" ) ) { + const char* surfaceparm = tokeniser.getToken(); - if (surfaceparm == 0) { - Tokeniser_unexpectedError(tokeniser, surfaceparm, "#surfaceparm"); + if ( surfaceparm == 0 ) { + Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" ); return false; } - if (string_equal_nocase(surfaceparm, "fog")) { + if ( string_equal_nocase( surfaceparm, "fog" ) ) { m_nFlags |= QER_FOG; - if (m_fTrans == 1.0f) { // has not been explicitly set by qer_trans + if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans m_fTrans = 0.35f; } - } else if (string_equal_nocase(surfaceparm, "nodraw")) { + } + else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) { m_nFlags |= QER_NODRAW; - } else if (string_equal_nocase(surfaceparm, "nonsolid")) { + } + else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) { m_nFlags |= QER_NONSOLID; - } else if (string_equal_nocase(surfaceparm, "water")) { + } + else if ( string_equal_nocase( surfaceparm, "water" ) ) { m_nFlags |= QER_WATER; - } else if (string_equal_nocase(surfaceparm, "lava")) { + } + else if ( string_equal_nocase( surfaceparm, "lava" ) ) { m_nFlags |= QER_LAVA; - } else if (string_equal_nocase(surfaceparm, "areaportal")) { + } + else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) { m_nFlags |= QER_AREAPORTAL; - } else if (string_equal_nocase(surfaceparm, "playerclip")) { + } + else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) { m_nFlags |= QER_CLIP; - } else if (string_equal_nocase(surfaceparm, "botclip")) { + } + else if ( string_equal_nocase( surfaceparm, "botclip" ) ) { m_nFlags |= QER_BOTCLIP; } } @@ -1019,7 +1321,8 @@ bool ShaderTemplate::parseMaterial(Tokeniser &tokeniser) return true; } -class Layer { +class Layer +{ public: LayerTypeId m_type; TextureExpression m_texture; @@ -1028,109 +1331,86 @@ bool m_clampToBorder; float m_alphaTest; float m_heightmapScale; -Layer() : m_type(LAYER_NONE), m_blendFunc(BLEND_ONE, BLEND_ZERO), m_clampToBorder(false), m_alphaTest(-1), - m_heightmapScale(0) -{ +Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){ } }; std::list g_shaderFilenames; -#if defined(WIN64) || defined(WIN32) -char * -strndup (const char *s, size_t n) -{ - char *result; - size_t len = strlen (s); - - if (n < len) - len = n; - - result = (char *) malloc (len + 1); - - if (!result) - return 0; - - result[len] = '\0'; - return (char *) memcpy (result, s, len); -} -#endif - -char* m_substring(const char* str, size_t begin, size_t len) -{ - if (str == 0 || strlen(str) == 0 || strlen(str) < begin || strlen(str) < (begin+len)) { - return 0; - } - - return strndup(str + begin, len); -} - -void ParseShaderFile(Tokeniser &tokeniser, const char *filename) -{ - g_shaderFilenames.push_back(filename); +void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){ + g_shaderFilenames.push_back( filename ); filename = g_shaderFilenames.back().c_str(); tokeniser.nextLine(); - for (;;) { - const char *token = tokeniser.getToken(); + for (;; ) + { + const char* token = tokeniser.getToken(); - if (token == 0) { + if ( token == 0 ) { break; } - if (string_equal(token, "table")) { - if (tokeniser.getToken() == 0) { - Tokeniser_unexpectedError(tokeniser, 0, "#table-name"); + if ( string_equal( token, "table" ) ) { + if ( tokeniser.getToken() == 0 ) { + Tokeniser_unexpectedError( tokeniser, 0, "#table-name" ); return; } - if (!Tokeniser_parseToken(tokeniser, "{")) { + if ( !Tokeniser_parseToken( tokeniser, "{" ) ) { return; } - for (;;) { - const char *option = tokeniser.getToken(); - if (string_equal(option, "{")) { - for (;;) { - const char *value = tokeniser.getToken(); - if (string_equal(value, "}")) { + for (;; ) + { + const char* option = tokeniser.getToken(); + if ( string_equal( option, "{" ) ) { + for (;; ) + { + const char* value = tokeniser.getToken(); + if ( string_equal( value, "}" ) ) { break; } } - if (!Tokeniser_parseToken(tokeniser, "}")) { + if ( !Tokeniser_parseToken( tokeniser, "}" ) ) { return; } break; } } - } else { - if (string_equal(token, "guide")) { - parseTemplateInstance(tokeniser, filename); - } else { - if (!string_equal(token, "material") - && !string_equal(token, "particle") - && !string_equal(token, "skin")) { + } + else + { + if ( string_equal( token, "guide" ) ) { + parseTemplateInstance( tokeniser, filename ); + } + else + { + if ( !string_equal( token, "material" ) + && !string_equal( token, "particle" ) + && !string_equal( token, "skin" ) ) { tokeniser.ungetToken(); } - // first token should be the path + name.. (from base) - ShaderTemplatePointer shaderTemplate(new ShaderTemplate()); - shaderTemplate->setName( m_substring( filename, 0, strlen( filename) - 4 ) ); // EUKARA + // first token should be the path + name.. ( from base ) + CopiedString name; + if ( !Tokeniser_parseShaderName( tokeniser, name ) ) { + } + ShaderTemplatePointer shaderTemplate( new ShaderTemplate() ); + shaderTemplate->setName( name.c_str() ); - g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)); + g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ); - bool result = shaderTemplate->parseMaterial(tokeniser); - if (result) { + bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 ) + ? shaderTemplate->parseQuake3( tokeniser ) + : shaderTemplate->parseDoom3( tokeniser ); + if ( result ) { // do we already have this shader? - if (!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(), - ShaderDefinition( - shaderTemplate.get(), - ShaderArguments(), - filename))).second) { - #if GDEF_DEBUG - globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() - << " is already in memory, definition in " << filename << " ignored.\n"; - #endif + if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) { +#if GDEF_DEBUG + globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n"; +#endif } - } else { - globalErrorStream() << "Error parsing material " << shaderTemplate->getName() << "\n"; + } + else + { + globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n"; return; } } @@ -1138,35 +1418,36 @@ void ParseShaderFile(Tokeniser &tokeniser, const char *filename) } } -void parseGuideFile(Tokeniser &tokeniser, const char *filename) -{ +void parseGuideFile( Tokeniser& tokeniser, const char* filename ){ tokeniser.nextLine(); - for (;;) { - const char *token = tokeniser.getToken(); + for (;; ) + { + const char* token = tokeniser.getToken(); - if (token == 0) { + if ( token == 0 ) { break; } - if (string_equal(token, "guide")) { - // first token should be the path + name.. (from base) - ShaderTemplatePointer shaderTemplate(new ShaderTemplate); - shaderTemplate->parseTemplate(tokeniser); - if (!g_shaderTemplates.insert( - ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)).second) { - globalErrorStream() << "guide " << makeQuoted(shaderTemplate->getName()) - << ": already defined, second definition ignored\n"; + if ( string_equal( token, "guide" ) ) { + // first token should be the path + name.. ( from base ) + ShaderTemplatePointer shaderTemplate( new ShaderTemplate ); + shaderTemplate->parseTemplate( tokeniser ); + if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) { + globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n"; } - } else if (string_equal(token, "inlineGuide")) { + } + else if ( string_equal( token, "inlineGuide" ) ) { // skip entire inlineGuide definition std::size_t depth = 0; - for (;;) { + for (;; ) + { tokeniser.nextLine(); token = tokeniser.getToken(); - if (string_equal(token, "{")) { + if ( string_equal( token, "{" ) ) { ++depth; - } else if (string_equal(token, "}")) { - if (--depth == 0) { + } + else if ( string_equal( token, "}" ) ) { + if ( --depth == 0 ) { break; } } @@ -1175,76 +1456,80 @@ void parseGuideFile(Tokeniser &tokeniser, const char *filename) } } -void LoadShaderFile(const char *filename) -{ - ArchiveTextFile *file = GlobalFileSystem().openTextFile(filename); +void LoadShaderFile( const char* filename ){ + ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename ); + + if ( file != 0 ) { + globalOutputStream() << "Parsing shaderfile " << filename << "\n"; + + Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() ); + + ParseShaderFile( tokeniser, filename ); - if (file != 0) { - // we probably only want to output when errors happen - //globalOutputStream() << "Parsing material " << filename << "\n"; - Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream()); - ParseShaderFile(tokeniser, filename); tokeniser.release(); file->release(); - } else { - globalOutputStream() << "Unable to read material " << filename << "\n"; + } + else + { + globalOutputStream() << "Unable to read shaderfile " << filename << "\n"; } } -void loadGuideFile(const char *filename) -{ - StringOutputStream fullname(256); +void loadGuideFile( const char* filename ){ + StringOutputStream fullname( 256 ); fullname << "guides/" << filename; - ArchiveTextFile *file = GlobalFileSystem().openTextFile(fullname.c_str()); + ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() ); - if (file != 0) { + if ( file != 0 ) { globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n"; - Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream()); - parseGuideFile(tokeniser, fullname.c_str()); + + Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() ); + + parseGuideFile( tokeniser, fullname.c_str() ); + tokeniser.release(); file->release(); - } else { + } + else + { globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n"; } } -CShader *Try_Shader_ForName(const char *name) -{ +CShader* Try_Shader_ForName( const char* name ){ { - shaders_t::iterator i = g_ActiveShaders.find(name); - if (i != g_ActiveShaders.end()) { - return (*i).second; + shaders_t::iterator i = g_ActiveShaders.find( name ); + if ( i != g_ActiveShaders.end() ) { + return ( *i ).second; } } // active shader was not found // find matching shader definition - ShaderDefinitionMap::iterator i = g_shaderDefinitions.find(name); - if (i == g_shaderDefinitions.end()) { + ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name ); + if ( i == g_shaderDefinitions.end() ) { // shader definition was not found - // create new shader definition from default shader template - ShaderTemplatePointer shaderTemplate(new ShaderTemplate()); - shaderTemplate->CreateDefault(name); - g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)); - i = g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate.get(), - ShaderArguments(), - ""))).first; + // create new shader definition from default shader template + ShaderTemplatePointer shaderTemplate( new ShaderTemplate() ); + shaderTemplate->CreateDefault( name ); + g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ); + + i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first; } // create shader from existing definition - ShaderPointer pShader(new CShader((*i).second)); - pShader->setName(name); - g_ActiveShaders.insert(shaders_t::value_type(name, pShader)); + ShaderPointer pShader( new CShader( ( *i ).second ) ); + pShader->setName( name ); + g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) ); g_ActiveShadersChangedNotify(); return pShader; } -IShader *Shader_ForName(const char *name) -{ - ASSERT_NOTNULL(name); +IShader *Shader_ForName( const char *name ){ + ASSERT_NOTNULL( name ); - IShader *pShader = Try_Shader_ForName(name); + IShader *pShader = Try_Shader_ForName( name ); pShader->IncRef(); return pShader; } @@ -1254,8 +1539,7 @@ IShader *Shader_ForName(const char *name) // those are listed in shaderlist file GSList *l_shaderfiles = 0; -GSList *Shaders_getShaderFileList() -{ +GSList* Shaders_getShaderFileList(){ return l_shaderfiles; } @@ -1265,19 +1549,19 @@ GSList *Shaders_getShaderFileList() usefull function: dumps the list of .shader files that are not referenced to the console ================== */ -void IfFound_dumpUnreferencedShader(bool &bFound, const char *filename) -{ +void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){ bool listed = false; - for (GSList *sh = l_shaderfiles; sh != 0; sh = g_slist_next(sh)) { - if (!strcmp((char *) sh->data, filename)) { + for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) ) + { + if ( !strcmp( (char*)sh->data, filename ) ) { listed = true; break; } } - if (!listed) { - if (!bFound) { + if ( !listed ) { + if ( !bFound ) { bFound = true; globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n"; } @@ -1285,28 +1569,27 @@ void IfFound_dumpUnreferencedShader(bool &bFound, const char *filename) } } -typedef ReferenceCaller IfFoundDumpUnreferencedShaderCaller; +typedef ReferenceCaller IfFoundDumpUnreferencedShaderCaller; -void DumpUnreferencedShaders() -{ +void DumpUnreferencedShaders(){ bool bFound = false; - GlobalFileSystem().forEachFile(g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller(bFound)); + GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) ); } -void ShaderList_addShaderFile(const char *dirstring) -{ +void ShaderList_addShaderFile( const char* dirstring ){ bool found = false; - for (GSList *tmp = l_shaderfiles; tmp != 0; tmp = tmp->next) { - if (string_equal_nocase(dirstring, (char *) tmp->data)) { + for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next ) + { + if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) { found = true; - globalOutputStream() << "duplicate entry \"" << (char *) tmp->data << "\" in shaderlist.txt\n"; + globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n"; break; } } - if (!found) { - l_shaderfiles = g_slist_append(l_shaderfiles, strdup(dirstring)); + if ( !found ) { + l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) ); } } @@ -1316,47 +1599,49 @@ void ShaderList_addShaderFile(const char *dirstring) build a CStringList of shader names ================== */ -void BuildShaderList(TextInputStream &shaderlist) -{ - Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(shaderlist); +void BuildShaderList( TextInputStream& shaderlist ){ + Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist ); tokeniser.nextLine(); - const char *token = tokeniser.getToken(); - StringOutputStream shaderFile(64); - while (token != 0) { + const char* token = tokeniser.getToken(); + StringOutputStream shaderFile( 64 ); + while ( token != 0 ) + { // each token should be a shader filename shaderFile << token << "." << g_shadersExtension; - ShaderList_addShaderFile(shaderFile.c_str()); + + ShaderList_addShaderFile( shaderFile.c_str() ); + tokeniser.nextLine(); token = tokeniser.getToken(); + shaderFile.clear(); } tokeniser.release(); } -void FreeShaderList() -{ - while (l_shaderfiles != 0) { - free(l_shaderfiles->data); - l_shaderfiles = g_slist_remove(l_shaderfiles, l_shaderfiles->data); +void FreeShaderList(){ + while ( l_shaderfiles != 0 ) + { + free( l_shaderfiles->data ); + l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data ); } } -void ShaderList_addFromArchive(const char *archivename) -{ - const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath"); - if (string_empty(shaderpath)) { +void ShaderList_addFromArchive( const char *archivename ){ + const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" ); + if ( string_empty( shaderpath ) ) { return; } - StringOutputStream shaderlist(256); - shaderlist << DirectoryCleaned(shaderpath) << "shaderlist.txt"; + StringOutputStream shaderlist( 256 ); + shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt"; - Archive *archive = GlobalFileSystem().getArchive(archivename, false); - if (archive) { - ArchiveTextFile *file = archive->openTextFile(shaderlist.c_str()); - if (file) { + Archive *archive = GlobalFileSystem().getArchive( archivename, false ); + if ( archive ) { + ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() ); + if ( file ) { globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n"; - BuildShaderList(file->getInputStream()); + BuildShaderList( file->getInputStream() ); file->release(); } } @@ -1364,76 +1649,76 @@ void ShaderList_addFromArchive(const char *archivename) #include "stream/filestream.h" -bool -shaderlist_findOrInstall(const char *enginePath, const char *toolsPath, const char *shaderPath, const char *gamename) -{ - StringOutputStream absShaderList(256); +bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){ + StringOutputStream absShaderList( 256 ); absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt"; - if (file_exists(absShaderList.c_str())) { + if ( file_exists( absShaderList.c_str() ) ) { return true; } { - StringOutputStream directory(256); + StringOutputStream directory( 256 ); directory << enginePath << gamename << '/' << shaderPath; - if (!file_exists(directory.c_str()) && !Q_mkdir(directory.c_str())) { + if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) { return false; } } { - StringOutputStream defaultShaderList(256); + StringOutputStream defaultShaderList( 256 ); defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt"; - if (file_exists(defaultShaderList.c_str())) { - return file_copy(defaultShaderList.c_str(), absShaderList.c_str()); + if ( file_exists( defaultShaderList.c_str() ) ) { + return file_copy( defaultShaderList.c_str(), absShaderList.c_str() ); } } return false; } -void Shaders_Load() -{ - /*if (QUAKE4) { - GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0); - }*/ +void Shaders_Load(){ + if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) { + GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0); + } - const char *shaderPath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath"); - if (!string_empty(shaderPath)) { - StringOutputStream path(256); - path << DirectoryCleaned(shaderPath); + const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" ); + if ( !string_empty( shaderPath ) ) { + StringOutputStream path( 256 ); + path << DirectoryCleaned( shaderPath ); - if (g_useShaderList) { + if ( g_useShaderList ) { // preload shader files that have been listed in shaderlist.txt - const char *basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame"); - const char *gamename = GlobalRadiant().getGameName(); - const char *enginePath = GlobalRadiant().getEnginePath(); - const char *toolsPath = GlobalRadiant().getGameToolsPath(); - bool isMod = !string_equal(basegame, gamename); + const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" ); + const char* gamename = GlobalRadiant().getGameName(); + const char* enginePath = GlobalRadiant().getEnginePath(); + const char* toolsPath = GlobalRadiant().getGameToolsPath(); - if (!isMod || !shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename)) { + bool isMod = !string_equal( basegame, gamename ); + + if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) { gamename = basegame; - shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename); + shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ); } GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true); DumpUnreferencedShaders(); - } else { + } + else + { GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 0); } GSList *lst = l_shaderfiles; - StringOutputStream shadername(256); - while (lst) { - shadername << path.c_str() << reinterpret_cast( lst->data ); - LoadShaderFile(shadername.c_str()); + StringOutputStream shadername( 256 ); + while ( lst ) + { + shadername << path.c_str() << reinterpret_cast( lst->data ); + LoadShaderFile( shadername.c_str() ); shadername.clear(); lst = lst->next; } } - //StringPool_analyse(ShaderPool::instance()); + //StringPool_analyse( ShaderPool::instance() ); } -void Shaders_Free() -{ +void Shaders_Free(){ FreeShaders(); FreeShaderList(); g_shaderFilenames.clear(); @@ -1441,127 +1726,117 @@ void Shaders_Free() ModuleObservers g_observers; -// wait until filesystem and is realised before loading anything -std::size_t g_shaders_unrealised = 1; -bool Shaders_realised() -{ +std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything +bool Shaders_realised(){ return g_shaders_unrealised == 0; } -void Shaders_Realise() -{ - if (--g_shaders_unrealised == 0) { +void Shaders_Realise(){ + if ( --g_shaders_unrealised == 0 ) { Shaders_Load(); g_observers.realise(); } } -void Shaders_Unrealise() -{ - if (++g_shaders_unrealised == 1) { +void Shaders_Unrealise(){ + if ( ++g_shaders_unrealised == 1 ) { g_observers.unrealise(); Shaders_Free(); } } -void Shaders_Refresh() -{ +void Shaders_Refresh(){ Shaders_Unrealise(); Shaders_Realise(); } -class MaterialSystem : public ShaderSystem, public ModuleObserver { +class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver +{ public: -void realise() -{ +void realise(){ Shaders_Realise(); } -void unrealise() -{ +void unrealise(){ Shaders_Unrealise(); } -void refresh() -{ +void refresh(){ Shaders_Refresh(); } -IShader *getShaderForName(const char *name) -{ - return Shader_ForName(name); +IShader* getShaderForName( const char* name ){ + return Shader_ForName( name ); } -void foreachShaderName(const ShaderNameCallback &callback) -{ - for (ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i) { - callback((*i).first.c_str()); +void foreachShaderName( const ShaderNameCallback& callback ){ + for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i ) + { + callback( ( *i ).first.c_str() ); } } -void beginActiveShadersIterator() -{ +void beginActiveShadersIterator(){ ActiveShaders_IteratorBegin(); } -bool endActiveShadersIterator() -{ +bool endActiveShadersIterator(){ return ActiveShaders_IteratorAtEnd(); } -IShader *dereferenceActiveShadersIterator() -{ +IShader* dereferenceActiveShadersIterator(){ return ActiveShaders_IteratorCurrent(); } -void incrementActiveShadersIterator() -{ +void incrementActiveShadersIterator(){ ActiveShaders_IteratorIncrement(); } -void setActiveShadersChangedNotify(const Callback ¬ify) -{ +void setActiveShadersChangedNotify( const Callback& notify ){ g_ActiveShadersChangedNotify = notify; } -void attach(ModuleObserver &observer) -{ - g_observers.attach(observer); +void attach( ModuleObserver& observer ){ + g_observers.attach( observer ); } -void detach(ModuleObserver &observer) -{ - g_observers.detach(observer); +void detach( ModuleObserver& observer ){ + g_observers.detach( observer ); } -void setLightingEnabled(bool enabled) -{ - +void setLightingEnabled( bool enabled ){ + if ( CShader::m_lightingEnabled != enabled ) { + for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i ) + { + ( *i ).second->unrealiseLighting(); + } + CShader::m_lightingEnabled = enabled; + for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i ) + { + ( *i ).second->realiseLighting(); + } + } } -const char *getTexturePrefix() const -{ +const char* getTexturePrefix() const { return g_texturePrefix; } }; -MaterialSystem g_MaterialSystem; +Quake3ShaderSystem g_Quake3ShaderSystem; -ShaderSystem &GetShaderSystem() -{ - return g_MaterialSystem; +ShaderSystem& GetShaderSystem(){ + return g_Quake3ShaderSystem; } -void Shaders_Construct() -{ - GlobalFileSystem().attach(g_MaterialSystem); +void Shaders_Construct(){ + GlobalFileSystem().attach( g_Quake3ShaderSystem ); } -void Shaders_Destroy() -{ - GlobalFileSystem().detach(g_MaterialSystem); +void Shaders_Destroy(){ + GlobalFileSystem().detach( g_Quake3ShaderSystem ); - if (Shaders_realised()) { + if ( Shaders_realised() ) { Shaders_Free(); } } diff --git a/plugins/shaders/shaders.h b/plugins/shaders/shaders.h index 4c9859a..88f193f 100644 --- a/plugins/shaders/shaders.h +++ b/plugins/shaders/shaders.h @@ -30,14 +30,26 @@ #if !defined( INCLUDED_SHADERS_H ) #define INCLUDED_SHADERS_H + void Shaders_Construct(); void Shaders_Destroy(); class ShaderSystem; -ShaderSystem &GetShaderSystem(); -extern const char *g_shadersExtension; -extern const char *g_shadersDirectory; +ShaderSystem& GetShaderSystem(); + +enum ShaderLanguage +{ + SHADERLANGUAGE_QUAKE3, + SHADERLANGUAGE_DOOM3, + SHADERLANGUAGE_QUAKE4 +}; + +extern const char* g_shadersExtension; +extern const char* g_shadersDirectory; +extern ShaderLanguage g_shaderLanguage; extern bool g_enableDefaultShaders; extern bool g_useShaderList; struct _QERPlugImageTable; -extern _QERPlugImageTable *g_bitmapModule; +extern _QERPlugImageTable* g_bitmapModule; + + #endif diff --git a/plugins/vfspk3/vfs.cpp b/plugins/vfspk3/vfs.cpp index 47a4ea4..df7673b 100644 --- a/plugins/vfspk3/vfs.cpp +++ b/plugins/vfspk3/vfs.cpp @@ -148,7 +148,26 @@ static Archive *InitPakFile(ArchiveModules &archiveModules, const char *filename entry.archive = table->m_pfnOpenArchive(filename); entry.is_pakfile = true; g_archives.push_back(entry); - globalOutputStream() << " pak file: " << filename << "\n"; + globalOutputStream() << " " << path_get_extension(filename) << " file: " << filename << "\n"; + + return entry.archive; + } + + return 0; +} + +static Archive *InitWadFile(ArchiveModules &archiveModules, const char *filename) +{ + const _QERArchiveTable *table = GetArchiveTable(archiveModules, path_get_extension(filename)); + + if (table != 0) { + archive_entry_t entry; + entry.name = filename; + + entry.archive = table->m_pfnOpenArchive(filename); + entry.is_pakfile = false; + g_archives.push_back(entry); + globalOutputStream() << " wad file: " << filename << "\n"; return entry.archive; } @@ -457,194 +476,155 @@ static void LoadDpkPakWithDeps(const char *pakname) // Global functions // reads all pak files from a dir -void InitDirectory(const char *directory, ArchiveModules &archiveModules) -{ +void InitDirectory( const char* directory, ArchiveModules& archiveModules ){ int j; g_numForbiddenDirs = 0; - StringTokeniser st(GlobalRadiant().getGameDescriptionKeyValue("forbidden_paths"), " "); - for (j = 0; j < VFS_MAXDIRS; ++j) { + StringTokeniser st( GlobalRadiant().getGameDescriptionKeyValue( "forbidden_paths" ), " " ); + for ( j = 0; j < VFS_MAXDIRS; ++j ) + { const char *t = st.getToken(); - if (string_empty(t)) { + if ( string_empty( t ) ) { break; } - strncpy(g_strForbiddenDirs[g_numForbiddenDirs], t, PATH_MAX); + strncpy( g_strForbiddenDirs[g_numForbiddenDirs], t, PATH_MAX ); g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = '\0'; ++g_numForbiddenDirs; } - for (j = 0; j < g_numForbiddenDirs; ++j) { - char *dbuf = g_strdup(directory); - if (*dbuf && dbuf[strlen(dbuf) - 1] == '/') { - dbuf[strlen(dbuf) - 1] = 0; + for ( j = 0; j < g_numForbiddenDirs; ++j ) + { + char* dbuf = g_strdup( directory ); + if ( *dbuf && dbuf[strlen( dbuf ) - 1] == '/' ) { + dbuf[strlen( dbuf ) - 1] = 0; } - const char *p = strrchr(dbuf, '/'); - p = (p ? (p + 1) : dbuf); - if (matchpattern(p, g_strForbiddenDirs[j], TRUE)) { - g_free(dbuf); + const char *p = strrchr( dbuf, '/' ); + p = ( p ? ( p + 1 ) : dbuf ); + if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) { + g_free( dbuf ); break; } - g_free(dbuf); + g_free( dbuf ); } - if (j < g_numForbiddenDirs) { - printf("Directory %s matched by forbidden dirs, removed\n", directory); + if ( j < g_numForbiddenDirs ) { + printf( "Directory %s matched by forbidden dirs, removed\n", directory ); return; } - if (g_numDirs == VFS_MAXDIRS) { + if ( g_numDirs == VFS_MAXDIRS ) { return; } - strncpy(g_strDirs[g_numDirs], directory, PATH_MAX); + strncpy( g_strDirs[g_numDirs], directory, PATH_MAX ); g_strDirs[g_numDirs][PATH_MAX] = '\0'; - FixDOSName(g_strDirs[g_numDirs]); - AddSlash(g_strDirs[g_numDirs]); + FixDOSName( g_strDirs[g_numDirs] ); + AddSlash( g_strDirs[g_numDirs] ); - const char *path = g_strDirs[g_numDirs]; + const char* path = g_strDirs[g_numDirs]; g_numDirs++; { - archive_entry_t entry; - entry.name = path; - entry.archive = OpenArchive(path); - entry.is_pakfile = false; - g_archives.push_back(entry); + g_archives.push_back( archive_entry_t{ path, OpenArchive( path ), false } ); } - if (g_bUsePak) { + if ( g_bUsePak ) { + GDir* dir = g_dir_open( path, 0, 0 ); - GDir *dir = g_dir_open(path, 0, 0); - - if (dir != 0) { + if ( dir != 0 ) { globalOutputStream() << "vfs directory: " << path << "\n"; - Archives archives; - Archives archivesOverride; - const char *ignore_prefix = ""; - const char *override_prefix = ""; - bool is_pk3_vfs, is_pk4_vfs, is_dpk_vfs; + const char* ignore_prefix = ""; + const char* override_prefix = ""; - is_pk3_vfs = GetArchiveTable(archiveModules, "pk3"); - is_pk4_vfs = GetArchiveTable(archiveModules, "pk4"); - is_dpk_vfs = GetArchiveTable(archiveModules, "dpk"); - - if (!is_dpk_vfs) { + { // See if we are in "sp" or "mp" mapping mode - const char *gamemode = gamemode_get(); + const char* gamemode = gamemode_get(); - if (strcmp(gamemode, "sp") == 0) { + if ( strcmp( gamemode, "sp" ) == 0 ) { ignore_prefix = "mp_"; override_prefix = "sp_"; - } else if (strcmp(gamemode, "mp") == 0) { + } + else if ( strcmp( gamemode, "mp" ) == 0 ) { ignore_prefix = "sp_"; override_prefix = "mp_"; } } - for (;;) { - const char *name = g_dir_read_name(dir); - if (name == 0) { + Archives archives; + Archives archivesOverride; + for (;; ) + { + const char* name = g_dir_read_name( dir ); + if ( name == 0 ) { break; } - for (j = 0; j < g_numForbiddenDirs; ++j) { - const char *p = strrchr(name, '/'); - p = (p ? (p + 1) : name); - if (matchpattern(p, g_strForbiddenDirs[j], TRUE)) { + for ( j = 0; j < g_numForbiddenDirs; ++j ) + { + const char *p = strrchr( name, '/' ); + p = ( p ? ( p + 1 ) : name ); + if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) { break; } } - if (j < g_numForbiddenDirs) { + if ( j < g_numForbiddenDirs ) { continue; } - const char *ext = strrchr(name, '.'); - char tmppath[PATH_MAX]; + const char *ext = strrchr( name, '.' ); - if (is_dpk_vfs) { - if (!!ext && !string_compare_nocase_upper(ext, ".dpkdir")) { - snprintf(tmppath, PATH_MAX, "%s%s/", path, name); - tmppath[PATH_MAX] = '\0'; - FixDOSName(tmppath); - AddSlash(tmppath); - AddDpkPak(CopiedString(StringRange(name, ext)).c_str(), tmppath, false); + if ( ext && !string_compare_nocase_upper( ext, ".pk3dir" ) ) { + if ( g_numDirs == VFS_MAXDIRS ) { + continue; + } + snprintf( g_strDirs[g_numDirs], PATH_MAX, "%s%s/", path, name ); + g_strDirs[g_numDirs][PATH_MAX] = '\0'; + FixDOSName( g_strDirs[g_numDirs] ); + AddSlash( g_strDirs[g_numDirs] ); + g_numDirs++; + + { + g_archives.push_back( archive_entry_t{ g_strDirs[g_numDirs - 1], OpenArchive( g_strDirs[g_numDirs - 1] ), false } ); } } - if (is_pk3_vfs || is_pk4_vfs) { - if (!!ext && (!string_compare_nocase_upper(ext, ".pk3dir") - || !string_compare_nocase_upper(ext, ".pk4dir"))) { - snprintf(tmppath, PATH_MAX, "%s%s/", path, name); - tmppath[PATH_MAX] = '\0'; - FixDOSName(tmppath); - AddSlash(tmppath); - AddPk3Dir(tmppath); - } - } - - // GetArchiveTable() needs "pk3" if ext is ".pk3" - if ((ext == 0) || *(ext + 1) == '\0' || GetArchiveTable(archiveModules, ext + 1) == 0) { + if ( ( ext == 0 ) || *( ++ext ) == '\0' || GetArchiveTable( archiveModules, ext ) == 0 ) { continue; } // using the same kludge as in engine to ensure consistency - if (!string_empty(ignore_prefix) && strncmp(name, ignore_prefix, strlen(ignore_prefix)) == 0) { + if ( !string_empty( ignore_prefix ) && strncmp( name, ignore_prefix, strlen( ignore_prefix ) ) == 0 ) { continue; } - if (!string_empty(override_prefix) && strncmp(name, override_prefix, strlen(override_prefix)) == 0) { - if (!string_compare_nocase_upper(ext, ".dpk")) { - if (is_dpk_vfs) { - archives.insert(name); - } - } else { - archivesOverride.insert(name); - } + if ( !string_empty( override_prefix ) && strncmp( name, override_prefix, strlen( override_prefix ) ) == 0 ) { + archivesOverride.insert( name ); continue; } - archives.insert(name); + archives.insert( name ); } - g_dir_close(dir); + g_dir_close( dir ); // add the entries to the vfs - char *fullpath; - if (is_dpk_vfs) { - for (Archives::iterator i = archives.begin(); i != archives.end(); ++i) { - const char *name = i->c_str(); - const char *ext = strrchr(name, '.'); - if (!string_compare_nocase_upper(ext, ".dpk")) { - CopiedString name_final = CopiedString(StringRange(name, ext)); - fullpath = string_new_concat(path, name); - AddDpkPak(name_final.c_str(), fullpath, true); - string_release(fullpath, string_length(fullpath)); - } - } + for ( Archives::iterator i = archivesOverride.begin(); i != archivesOverride.end(); ++i ) + { + char filename[PATH_MAX]; + strcpy( filename, path ); + strcat( filename, ( *i ).c_str() ); + InitPakFile( archiveModules, filename ); } - if (is_pk3_vfs || is_pk4_vfs) { - for (Archives::iterator i = archivesOverride.begin(); i != archivesOverride.end(); ++i) { - const char *name = i->c_str(); - const char *ext = strrchr(name, '.'); - if (!string_compare_nocase_upper(ext, ".pk3") - || !string_compare_nocase_upper(ext, ".pk4")) { - fullpath = string_new_concat(path, i->c_str()); - InitPakFile(archiveModules, fullpath); - string_release(fullpath, string_length(fullpath)); - } - } - for (Archives::iterator i = archives.begin(); i != archives.end(); ++i) { - const char *name = i->c_str(); - const char *ext = strrchr(name, '.'); - if (!string_compare_nocase_upper(ext, ".pk3") - || !string_compare_nocase_upper(ext, ".pk4")) { - fullpath = string_new_concat(path, i->c_str()); - InitPakFile(archiveModules, fullpath); - string_release(fullpath, string_length(fullpath)); - } - } + for ( Archives::iterator i = archives.begin(); i != archives.end(); ++i ) + { + char filename[PATH_MAX]; + strcpy( filename, path ); + strcat( filename, ( *i ).c_str() ); + InitPakFile( archiveModules, filename ); } - } else { + } + else + { globalErrorStream() << "vfs directory not found: " << path << "\n"; } } @@ -812,7 +792,7 @@ void initialise() void load() { ArchiveModules &archiveModules = FileSystemQ3API_getArchiveModules(); - bool is_dpk_vfs = GetArchiveTable(archiveModules, "dpk"); + bool is_dpk_vfs = 1; if (is_dpk_vfs) { const char *pakname; @@ -947,7 +927,6 @@ Archive *getArchive(const char *archiveName, bool pakonly) if (pakonly && !(*i).is_pakfile) { continue; } - if (path_equal((*i).name.c_str(), archiveName)) { return (*i).archive; } diff --git a/resources/Makefile b/resources/Makefile index 5f913b6..60ba82a 100644 --- a/resources/Makefile +++ b/resources/Makefile @@ -3,9 +3,12 @@ all: mkdir -p ../build/games mkdir -p ../build/gl mkdir -p ../build/platform.game + mkdir -p ../build/goldsrc.game cp -vf ./defaultkeys.ini ../build/defaultkeys.ini cp -vf ./platform.game/default_build_menu.xml ../build/platform.game/default_build_menu.xml + cp -vf ./goldsrc.game/default_build_menu.xml ../build/goldsrc.game/default_build_menu.xml cp -vf ./games/platform.game ../build/games/platform.game + cp -vf ./games/goldsrc.game ../build/games/goldsrc.game cp -vf ./gl/lighting_DBS_omni_fp.glp ../build/gl/lighting_DBS_omni_fp.glp cp -vf ./gl/lighting_DBS_omni_fp.glsl ../build/gl/lighting_DBS_omni_fp.glsl cp -vf ./gl/lighting_DBS_omni_vp.glp ../build/gl/lighting_DBS_omni_vp.glp diff --git a/resources/games/goldsrc.game b/resources/games/goldsrc.game new file mode 100644 index 0000000..22a3a2d --- /dev/null +++ b/resources/games/goldsrc.game @@ -0,0 +1,28 @@ + + diff --git a/resources/goldsrc.game/default_build_menu.xml b/resources/goldsrc.game/default_build_menu.xml new file mode 100644 index 0000000..3c1637e --- /dev/null +++ b/resources/goldsrc.game/default_build_menu.xml @@ -0,0 +1,26 @@ + + + +"[RadiantPath]vmap" -v -connect [MonitorAddress] -game platform -fs_basepath "[EnginePath]" -fs_game [GameName] + + + +[vmap] -custinfoparms -threads 4 -samplesize 8 "[MapFile]" +[vmap] -vis -v -fast "[MapFile]" + + + +[vmap] -custinfoparms -threads 4 -samplesize 8 "[MapFile]" +[vmap] -vis -v -fast "[MapFile]" +[vmap] -light -custinfoparms -v -samplesize 8 -fast -threads 4 -samples 4 -shade -shadeangle 60 -patchshadows "[MapFile]" + + + +[vmap] -custinfoparms -threads 4 -samplesize 8 "[MapFile]" +[vmap] -vis "[MapFile]" +[vmap] -light -custinfoparms -samplesize 8 -fast -threads 4 -samples 4 -shade -shadeangle 60 -patchshadows "[MapFile]" + + + + + diff --git a/src/mainframe.cpp b/src/mainframe.cpp index dfdd6b7..efea26f 100644 --- a/src/mainframe.cpp +++ b/src/mainframe.cpp @@ -156,6 +156,8 @@ void VFS_Refresh() RefreshReferences(); // also refresh texture browser TextureBrowser_RefreshShaders(); + // also show textures (all or common) + TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() ); } void VFS_Restart()