diff --git a/README.md b/README.md index 4b44723e8..ba57c214f 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,10 @@ This repository contains our fork of HLSDK and restored source code for some of * Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs` +#### PSP +* Build pspsdk(GCC 9.3) from https://github.com/pspdev +* Clone this repository: `git clone --recursive https://github.com/Crow-bar/xash3d-fwgs`. + ### Building #### Windows (Visual Studio) 0) Open command line @@ -90,3 +94,20 @@ If compiling 32-bit on amd64, you may need to supply `export PKG_CONFIG_PATH=/us (You need to pass `-8` to compile 64-bit engine on 64-bit x86 processor) 2) Compile: `./waf build` 3) Install(optional): `./waf install --destdir=/path/to/any/output/directory` + +#### PSP +0) Navigate to `xash3d-fwgs` directory. +1) Examine which build options are available: `./waf --help` +2) Configure build: + Normal: `./waf configure -T fast --psp=prx,660,HW --prefix=/path/to/any/output/directory` + Profiling: `./waf configure -T debug --psp=elf,660,HW --enable-profiling --prefix=/path/to/any/output/directory` +3) Compile: `./waf build` +4) Install(optional): `./waf install` + + +## Running +0) Copy libraries and main executable somewhere, if you're skipped installation stage. +1) Copy game files to same directory +2) Run `xash3d.exe`/`xash3d.sh`/`xash3d` depending on which platform you're using. + +For additional info, run Xash3D with `-help` command line key. diff --git a/common/backends.h b/common/backends.h index 6fd51f19f..8478a2076 100644 --- a/common/backends.h +++ b/common/backends.h @@ -20,16 +20,19 @@ GNU General Public License for more details. #define VIDEO_SDL 1 #define VIDEO_FBDEV 3 #define VIDEO_DOS 4 +#define VIDEO_PSP 5 // audio backends (XASH_SOUND) #define SOUND_NULL 0 #define SOUND_SDL 1 #define SOUND_ALSA 3 +#define SOUND_PSP 4 // input (XASH_INPUT) #define INPUT_NULL 0 #define INPUT_SDL 1 #define INPUT_EVDEV 3 +#define INPUT_PSP 4 // timer (XASH_TIMER) #define TIMER_NULL 0 // not used @@ -37,17 +40,20 @@ GNU General Public License for more details. #define TIMER_POSIX 2 #define TIMER_WIN32 3 #define TIMER_DOS 4 +#define TIMER_PSP 5 // messageboxes (XASH_MESSAGEBOX) #define MSGBOX_STDERR 0 #define MSGBOX_SDL 1 #define MSGBOX_WIN32 3 #define MSGBOX_NSWITCH 4 +#define MSGBOX_PSP 5 // library loading (XASH_LIB) -#define LIB_NULL 0 -#define LIB_POSIX 1 -#define LIB_WIN32 2 +#define LIB_NULL 0 +#define LIB_POSIX 1 +#define LIB_WIN32 2 #define LIB_STATIC 3 +#define LIB_PSP 4 #endif /* BACKENDS_H */ diff --git a/common/com_image.h b/common/com_image.h index 098a0d12a..8b7c85123 100644 --- a/common/com_image.h +++ b/common/com_image.h @@ -23,6 +23,7 @@ NOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 == || type == PF_BC7_UNORM \ || type == PF_BC7_SRGB \ || type == PF_KTX2_RAW ) +#define ImageIND( type ) (type == PF_INDEXED_32 || type == PF_INDEXED_24) typedef enum { @@ -33,6 +34,10 @@ typedef enum PF_BGRA_32, // big endian RGBA (MacOS) PF_RGB_24, // uncompressed dds or another 24-bit image PF_BGR_24, // big-endian RGB (MacOS) + PF_RGB_332, // 8-bit R3 G3 B2 + PF_RGB_5650, // 16-bit R5 G6 B5 + PF_RGBA_5551, // 16-bit R5 G5 B5 A1 + PF_RGBA_4444, // 16-bit R4 G4 B4 A4 PF_LUMINANCE, PF_DXT1, // s3tc DXT1/BC1 format PF_DXT3, // s3tc DXT3/BC2 format diff --git a/common/com_model.h b/common/com_model.h index 22d522437..44aa56102 100644 --- a/common/com_model.h +++ b/common/com_model.h @@ -127,6 +127,21 @@ typedef struct int flags; // sky or slime, no lightmap or 256 subdivision } mtexinfo_t; +#if XASH_PSP +typedef struct +{ + float uv[2]; + float xyz[3]; +}gu_vert_t; +typedef struct glpoly_s +{ + struct glpoly_s *next; + struct glpoly_s *chain; + int numverts; + int flags; // for SURF_UNDERWATER + gu_vert_t verts[1]; // variable sized (xyz s1t1 + lm(xyz s2t2)) +} glpoly_t; +#else typedef struct glpoly_s { struct glpoly_s *next; @@ -135,7 +150,7 @@ typedef struct glpoly_s int flags; // for SURF_UNDERWATER float verts[4][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) } glpoly_t; - +#endif typedef struct mnode_s { // common with leaf diff --git a/common/defaults.h b/common/defaults.h index 3267a950c..dc7aac89e 100644 --- a/common/defaults.h +++ b/common/defaults.h @@ -77,6 +77,30 @@ SETUP BACKENDS DEFINITIONS // usually only 10-20 fds availiable #define XASH_REDUCE_FD + #elif XASH_PSP + #ifndef XASH_VIDEO + #define XASH_VIDEO VIDEO_PSP + #endif + + #ifndef XASH_TIMER + #define XASH_TIMER TIMER_PSP + #endif + + #ifndef XASH_INPUT + #define XASH_INPUT INPUT_PSP + #endif + + #ifndef XASH_SOUND + #define XASH_SOUND SOUND_PSP + #endif // XASH_SOUND + + #ifndef XASH_MESSAGEBOX + #define XASH_MESSAGEBOX MSGBOX_PSP + #endif // XASH_MESSAGEBOX + + #define XASH_REDUCE_FD + #define XASH_NO_TOUCH + #define XASH_NO_ZIP #endif #endif // XASH_DEDICATED @@ -105,7 +129,7 @@ SETUP BACKENDS DEFINITIONS #endif // !XASH_WIN32 #endif -#ifdef XASH_STATIC_LIBS +#if defined(XASH_STATIC_LIBS) && !XASH_PSP #define XASH_LIB LIB_STATIC #define XASH_INTERNAL_GAMELIBS #define XASH_ALLOW_SAVERESTORE_OFFSETS @@ -113,6 +137,8 @@ SETUP BACKENDS DEFINITIONS #define XASH_LIB LIB_WIN32 #elif XASH_POSIX #define XASH_LIB LIB_POSIX +#elif XASH_PSP +#define XASH_LIB LIB_PSP #endif // @@ -193,4 +219,20 @@ Default build-depended cvar and constant values #define DEFAULT_MAX_EDICTS 1200 // was 900 before HL25 #endif // DEFAULT_MAX_EDICTS +#ifndef DEFAULT_ACCELERATED_RENDERER + #ifdef XASH_PSP + #define DEFAULT_ACCELERATED_RENDERER "gu" + #else + #if XASH_MOBILE_PLATFORM + #define DEFAULT_ACCELERATED_RENDERER "gles1" + #else // !XASH_MOBILE_PLATFORM + #define DEFAULT_ACCELERATED_RENDERER "gl" + #endif // !XASH_MOBILE_PLATFORM + #endif +#endif // DEFAULT_ACCELERATED_RENDERER + +#ifndef DEFAULT_SOFTWARE_RENDERER + #define DEFAULT_SOFTWARE_RENDERER "soft" // mittorn's ref_soft +#endif // DEFAULT_SOFTWARE_RENDERER + #endif // DEFAULTS_H diff --git a/common/port.h b/common/port.h index 761898473..edcb1188e 100644 --- a/common/port.h +++ b/common/port.h @@ -24,7 +24,9 @@ GNU General Public License for more details. #include #define OS_LIB_EXT "dylib" #define OPEN_COMMAND "open" - #else + #elif XASH_PSP + #define OS_LIB_EXT "prx" + #else #define OS_LIB_EXT "so" #define OPEN_COMMAND "xdg-open" #endif @@ -55,6 +57,16 @@ GNU General Public License for more details. #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) #endif + #if XASH_PSP + #include + #include + + #define O_BINARY 0 + #define O_TEXT 0 + + #define _mkdir( x ) sceIoMkdir( x, FIO_S_IRWXU | FIO_S_IRWXG | FIO_S_IROTH | FIO_S_IXOTH ) + #endif + typedef void* HANDLE; typedef void* HINSTANCE; diff --git a/common/render_api.h b/common/render_api.h index 40129c1de..c2ecc8bf3 100644 --- a/common/render_api.h +++ b/common/render_api.h @@ -101,8 +101,13 @@ typedef enum TF_TEXTURE_3D = (1<<20), // this is GL_TEXTURE_3D TF_ATLAS_PAGE = (1<<21), // bit who indicate lightmap page or deluxemap page TF_ALPHACONTRAST = (1<<22), // special texture mode for A2C +#if XASH_PSP + TF_IMG_SWIZZLED = (1<<23), + TF_IMG_INVRAM = (1<<24), +#else // reserved // reserved +#endif TF_IMG_UPLOADED = (1<<25), // this is set for first time when called glTexImage, otherwise it will be call glTexSubImage TF_ARB_FLOAT = (1<<26), // float textures TF_NOCOMPARE = (1<<27), // disable comparing for depth textures diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 7eec99ba7..8f058873d 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -21,12 +21,22 @@ typedef int sound_t; typedef float vec_t; typedef vec_t vec2_t[2]; typedef vec_t vec3_t[3]; +#if XASH_PSP +typedef vec_t vec4_t[4] __attribute__( ( aligned( 16 ) ) ); +typedef vec_t quat_t[4] __attribute__( ( aligned( 16 ) ) ); +#else typedef vec_t vec4_t[4]; typedef vec_t quat_t[4]; +#endif typedef byte rgba_t[4]; // unsigned byte colorpack typedef byte rgb_t[3]; // unsigned byte colorpack +#if XASH_PSP +typedef vec_t matrix3x4[3][4] __attribute__( ( aligned( 16 ) ) ); +typedef vec_t matrix4x4[4][4] __attribute__( ( aligned( 16 ) ) ); +#else typedef vec_t matrix3x4[3][4]; typedef vec_t matrix4x4[4][4]; +#endif #if XASH_64BIT typedef uint32_t poolhandle_t; @@ -194,6 +204,9 @@ typedef void *(*pfnCreateInterface_t)( const char *, int * ); // config strings are a general means of communication from // the server to all connected clients. // each config string can be at most CS_SIZE characters. +#if XASH_PSP +#define MAX_QPATH 48 +#else #if XASH_LOW_MEMORY == 0 #define MAX_QPATH 64 // max length of a game pathname #elif XASH_LOW_MEMORY == 2 @@ -201,6 +214,7 @@ typedef void *(*pfnCreateInterface_t)( const char *, int * ); #elif XASH_LOW_MEMORY == 1 #define MAX_QPATH 48 #endif +#endif #define MAX_OSPATH 260 // max length of a filesystem pathname #define CS_SIZE 64 // size of one config string #define CS_TIME 16 // size of time string diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index eb024315a..cd66bc429 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -26,18 +26,6 @@ GNU General Public License for more details. // #define STUDIO_INTERPOLATION_FIX -/* -================== -CL_IsPlayerIndex - -detect player entity -================== -*/ -qboolean CL_IsPlayerIndex( int idx ) -{ - return ( idx >= 1 && idx <= cl.maxclients ); -} - /* ========================================================================= diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index f8413b5db..518d96971 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -96,41 +96,6 @@ static dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and higher static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ); -/* -==================== -CL_GetEntityByIndex - -Render callback for studio models -==================== -*/ -cl_entity_t *CL_GetEntityByIndex( int index ) -{ - if( !clgame.entities ) // not in game yet - return NULL; - - if( index < 0 || index >= clgame.maxEntities ) - return NULL; - - if( index == 0 ) - return clgame.entities; - - return CL_EDICT_NUM( index ); -} - -/* -================ -CL_ModelHandle - -get model handle by index -================ -*/ -model_t *CL_ModelHandle( int modelindex ) -{ - if( modelindex < 0 || modelindex >= MAX_MODELS ) - return NULL; - return cl.models[modelindex]; -} - /* ==================== CL_IsThirdPerson @@ -939,7 +904,7 @@ void CL_DrawCrosshair( void ) VectorAdd( refState.viewangles, cl.crosshairangle, angles ); AngleVectors( angles, forward, NULL, NULL ); VectorAdd( refState.vieworg, forward, point ); - ref.dllFuncs.WorldToScreen( point, screen ); + gTriApi.WorldToScreen( point, screen ); x += ( clgame.viewport[2] >> 1 ) * screen[0] + 0.5f; y += ( clgame.viewport[3] >> 1 ) * screen[1] + 0.5f; @@ -1084,7 +1049,6 @@ void CL_ClearWorld( void ) world.max_recursion = 0; - clgame.ds.cullMode = TRI_FRONT; clgame.numStatics = 0; } diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index f2c1943d7..2e954c869 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -81,12 +81,12 @@ NetGraph_FillRGBA shortcut */ static void NetGraph_DrawRect( wrect_t *rect, byte colors[4] ) { - ref.dllFuncs.Color4ub( colors[0], colors[1], colors[2], colors[3] ); // color for this quad + gTriApi.Color4ub( colors[0], colors[1], colors[2], colors[3] ); // color for this quad - ref.dllFuncs.Vertex3f( rect->left, rect->top, 0 ); - ref.dllFuncs.Vertex3f( rect->left + rect->right, rect->top, 0 ); - ref.dllFuncs.Vertex3f( rect->left + rect->right, rect->top + rect->bottom, 0 ); - ref.dllFuncs.Vertex3f( rect->left, rect->top + rect->bottom, 0 ); + gTriApi.Vertex3f( rect->left, rect->top, 0 ); + gTriApi.Vertex3f( rect->left + rect->right, rect->top, 0 ); + gTriApi.Vertex3f( rect->left + rect->right, rect->top + rect->bottom, 0 ); + gTriApi.Vertex3f( rect->left, rect->top + rect->bottom, 0 ); } /* @@ -691,7 +691,7 @@ void SCR_DrawNetGraph( void ) ref.dllFuncs.End(); ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); - ref.dllFuncs.GL_SetRenderMode( kRenderNormal ); + ref.dllFuncs.RenderMode( kRenderNormal ); } } diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index fd37d6d3c..e5f1446ae 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -393,7 +393,7 @@ int CL_TempEntAddEntity( cl_entity_t *pEntity ) VectorAdd( pEntity->origin, pEntity->model->maxs, maxs ); // g-cont. just use PVS from previous frame - if( TriBoxInPVS( mins, maxs )) + if( Mod_BoxVisible( mins, maxs, ref.dllFuncs.Mod_GetCurrentVis( ))) { VectorCopy( pEntity->angles, pEntity->curstate.angles ); VectorCopy( pEntity->origin, pEntity->curstate.origin ); @@ -1023,8 +1023,12 @@ void GAME_EXPORT R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t count = (size[0] * size[1] + size[1] * size[2] + size[2] * size[0]) / (3 * SHARD_VOLUME * SHARD_VOLUME); } +#if XASH_PSP + if( count > 15 ) count = 15; +#else // limit to 100 pieces if( count > 100 ) count = 100; +#endif for( i = 0; i < count; i++ ) { diff --git a/engine/client/cl_tent.h b/engine/client/cl_tent.h index f2cc7e99a..44a2c937e 100644 --- a/engine/client/cl_tent.h +++ b/engine/client/cl_tent.h @@ -101,19 +101,6 @@ struct beam_s *R_BeamRing( int startEnt, int endEnt, int modelIndex, float life, struct beam_s *R_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ); void R_BeamKill( int deadEntity ); - -// TriAPI -void TriRenderMode( int mode ); -void TriColor4f( float r, float g, float b, float a ); -void TriColor4ub( byte r, byte g, byte b, byte a ); -void TriBrightness( float brightness ); -void TriCullFace( TRICULLSTYLE mode ); -int TriWorldToScreen( const float *world, float *screen ); -int TriBoxInPVS( float *mins, float *maxs ); -void TriLightAtPoint( float *pos, float *value ); -void TriColor4fRendermode( float r, float g, float b, float a, int rendermode ); -int TriSpriteTexture( model_t *pSpriteModel, int frame ); - extern model_t *cl_sprite_dot; extern model_t *cl_sprite_shell; diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c index 79f0bf7ff..e2b509fde 100644 --- a/engine/client/cl_view.c +++ b/engine/client/cl_view.c @@ -417,10 +417,10 @@ void R_DrawLeafNode( float x, float y, float scale ) void R_DrawNodeConnection( float x, float y, float x2, float y2 ) { - ref.dllFuncs.Begin( TRI_LINES ); - ref.dllFuncs.Vertex3f( x, y, 0 ); - ref.dllFuncs.Vertex3f( x2, y2, 0 ); - ref.dllFuncs.End( ); + gTriApi.Begin( TRI_LINES ); + gTriApi.Vertex3f( x, y, 0 ); + gTriApi.Vertex3f( x2, y2, 0 ); + gTriApi.End( ); } void R_ShowTree_r( mnode_t *node, float x, float y, float scale, int shownodes, mleaf_t *viewleaf ) @@ -443,10 +443,10 @@ void R_ShowTree_r( mnode_t *node, float x, float y, float scale, int shownodes, if( shownodes == 1 ) { if( cl.worldmodel->leafs == leaf ) - ref.dllFuncs.Color4f( 1.0f, 1.0f, 1.0f, 1.0f ); + gTriApi.Color4f( 1.0f, 1.0f, 1.0f, 1.0f ); else if( viewleaf && viewleaf == leaf ) - ref.dllFuncs.Color4f( 1.0f, 0.0f, 0.0f, 1.0f ); - else ref.dllFuncs.Color4f( 0.0f, 1.0f, 0.0f, 1.0f ); + gTriApi.Color4f( 1.0f, 0.0f, 0.0f, 1.0f ); + else gTriApi.Color4f( 0.0f, 1.0f, 0.0f, 1.0f ); R_DrawLeafNode( x, y, scale ); } world.recursion_level--; @@ -455,7 +455,7 @@ void R_ShowTree_r( mnode_t *node, float x, float y, float scale, int shownodes, if( shownodes == 1 ) { - ref.dllFuncs.Color4f( 0.0f, 0.0f, 1.0f, 1.0f ); + gTriApi.Color4f( 0.0f, 0.0f, 1.0f, 1.0f ); R_DrawLeafNode( x, y, scale ); } else if( shownodes == 2 ) @@ -487,7 +487,7 @@ void R_ShowTree( void ) //pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); //pglLineWidth( 2.0f ); - ref.dllFuncs.Color4f( 1, 0.7f, 0, 1.0f ); + gTriApi.Color4f( 1, 0.7f, 0, 1.0f ); //pglDisable( GL_TEXTURE_2D ); R_ShowTree_r( cl.worldmodel->nodes, x, y, world.max_recursion * 3.5f, 2, viewleaf ); //pglEnable( GL_TEXTURE_2D ); diff --git a/engine/client/client.h b/engine/client/client.h index cbf16e46f..5177c97c1 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -370,7 +370,6 @@ typedef struct // holds text color rgba_t textColor; rgba_t spriteColor; - vec4_t triRGBA; // crosshair members const model_t *pCrosshair; @@ -839,7 +838,6 @@ void CL_ClearSpriteTextures( void ); void CL_CenterPrint( const char *text, float y ); void CL_TextMessageParse( byte *pMemFile, int fileSize ); client_textmessage_t *CL_TextMessageGet( const char *pName ); -model_t *CL_ModelHandle( int modelindex ); void NetAPI_CancelAllRequests( void ); cl_entity_t *CL_GetLocalPlayer( void ); model_t *CL_LoadClientSprite( const char *filename ); @@ -861,19 +859,45 @@ void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int he void CL_DisableScissor( scissor_state_t *scissor ); qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ); -_inline cl_entity_t *CL_EDICT_NUM( int n ) +static inline cl_entity_t *CL_EDICT_NUM( int n ) { - if( !clgame.entities ) + if( unlikely( !clgame.entities )) // not in game yet { Host_Error( "CL_EDICT_NUM: clgame.entities is NULL\n"); return NULL; } - if(( n >= 0 ) && ( n < clgame.maxEntities )) - return clgame.entities + n; + if( unlikely( n < 0 || n >= clgame.maxEntities )) + { + Host_Error( "CL_EDICT_NUM: bad number %i\n", n ); + return NULL; + } + + return &clgame.entities[n]; +} + +static inline cl_entity_t *CL_GetEntityByIndex( int n ) +{ + if( unlikely( !clgame.entities )) // not in game yet + return NULL; + + if( unlikely( n < 0 || n >= clgame.maxEntities )) + return NULL; + + return &clgame.entities[n]; +} - Host_Error( "CL_EDICT_NUM: bad number %i\n", n ); - return NULL; +static inline model_t *CL_ModelHandle( int modelindex ) +{ + if( unlikely( modelindex < 0 || modelindex >= MAX_MODELS )) + return NULL; + + return cl.models[modelindex]; +} + +static inline qboolean CL_IsPlayerIndex( int idx ) +{ + return ( idx >= 1 && idx <= cl.maxclients ); } // @@ -995,7 +1019,6 @@ void CL_ProcessPlayerState( int playerindex, entity_state_t *state ); void CL_ComputePlayerOrigin( cl_entity_t *clent ); void CL_ProcessPacket( frame_t *frame ); void CL_MoveThirdpersonCamera( void ); -qboolean CL_IsPlayerIndex( int idx ); void CL_SetIdealPitch( void ); void CL_EmitEntities( void ); @@ -1189,5 +1212,6 @@ int Key_ToUpper( int key ); void OSK_Draw( void ); extern rgba_t g_color_table[8]; +extern triangleapi_t gTriApi; #endif//CLIENT_H diff --git a/engine/client/console.c b/engine/client/console.c index 90cd78d69..41bb85e26 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -2195,7 +2195,7 @@ void Con_VidInit( void ) Con_LoadConchars(); Con_CheckResize(); -#if XASH_LOW_MEMORY +#if XASH_LOW_MEMORY && !XASH_PSP con.background = R_GetBuiltinTexture( REF_BLACK_TEXTURE ); #else // loading console image diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index a2ed8163e..9db564bd2 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -19,6 +19,24 @@ GNU General Public License for more details. #include "vgui_draw.h" #include "mobility_int.h" +#ifdef XASH_NO_TOUCH + +void Touch_WriteConfig( void ) {} +void Touch_SetClientOnly( qboolean state ) {} +void Touch_RemoveButton( const char *name ) {} +void Touch_HideButtons( const char *name, byte hide ) {} +void Touch_AddClientButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags ) {} +void Touch_AddDefaultButton( const char *name, const char *texturefile, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags ) {} +void Touch_ResetDefaultButtons( void ){} +void Touch_Init( void ) {} +void Touch_Draw( void ) {} +int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ) { return 0; } +void Touch_GetMove( float *forward, float *side, float *yaw, float *pitch ) {} +void Touch_KeyEvent( int key, int down ) {} +void Touch_Shutdown( void ) {} + +#else /* XASH_NO_TOUCH */ + typedef enum { touch_command, // just tap a button @@ -1529,7 +1547,6 @@ void Touch_Draw( void ) ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( touch.move_start_x + touch.side * width - GRID_X * touch_move_indicator.value ), TO_SCRN_Y( touch.move_start_y - touch.forward * height - GRID_Y * touch_move_indicator.value ), TO_SCRN_X( GRID_X * 2 * touch_move_indicator.value ), TO_SCRN_Y( GRID_Y * 2 * touch_move_indicator.value ), 0, 0, 1, 1, touch.joytexture ); - } } @@ -2191,3 +2208,5 @@ void Touch_Shutdown( void ) touch.initialized = false; Mem_FreePool( &touch.mempool ); } + +#endif /* XASH_NO_TOUCH */ diff --git a/engine/client/input.c b/engine/client/input.c index f1e13dc67..5ba4dddf3 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -60,8 +60,10 @@ uint IN_CollectInputDevices( void ) if( !m_ignore.value ) // no way to check is mouse connected, so use cvar only ret |= INPUT_DEVICE_MOUSE; +#ifndef XASH_NO_TOUCH if( touch_enable.value ) ret |= INPUT_DEVICE_TOUCH; +#endif if( Joy_IsActive() ) // connected or enabled ret |= INPUT_DEVICE_JOYSTICK; @@ -91,13 +93,17 @@ void IN_LockInputDevices( qboolean lock ) { SetBits( m_ignore.flags, FCVAR_READ_ONLY ); SetBits( joy_enable.flags, FCVAR_READ_ONLY ); +#ifndef XASH_NO_TOUCH SetBits( touch_enable.flags, FCVAR_READ_ONLY ); +#endif } else { ClearBits( m_ignore.flags, FCVAR_READ_ONLY ); ClearBits( joy_enable.flags, FCVAR_READ_ONLY ); +#ifndef XASH_NO_TOUCH ClearBits( touch_enable.flags, FCVAR_READ_ONLY ); +#endif } } @@ -351,11 +357,14 @@ void IN_MouseEvent( int key, int down ) else ClearBits( in_mstate, BIT( key )); // touch emulation overrides all input +#if XASH_NO_TOUCH if( touch_emulate.value ) { Touch_KeyEvent( K_MOUSE1 + key, down ); } - else if( cls.key_dest == key_game ) + else +#endif + if( cls.key_dest == key_game ) { // perform button actions VGui_MouseEvent( K_MOUSE1 + key, down ); @@ -364,8 +373,7 @@ void IN_MouseEvent( int key, int down ) // client may override IN_MouseEvent // but by default it calls back to Key_Event anyway if( in_mouseactive ) - clgame.dllFuncs.IN_MouseEvent( in_mstate ); - } + clgame.dllFuncs.IN_MouseEvent( in_mstate ); } else { // perform button actions diff --git a/engine/client/keys.c b/engine/client/keys.c index fcd69afb4..1f3da72f6 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -515,7 +515,6 @@ void Key_Init( void ) Cvar_RegisterVariable( &osk_enable ); Cvar_RegisterVariable( &key_rotate ); - } /* @@ -703,7 +702,11 @@ void GAME_EXPORT Key_Event( int key, int down ) VGui_KeyEvent( key, down ); // console key is hardcoded, so the user can never unbind it +#if XASH_PSP + if( key == '`' || key == '~' || key == K_MODE_BUTTON ) +#else if( key == '`' || key == '~' ) +#endif { // we are in typing mode, so don't switch to console if( cls.key_dest == key_message || !down ) @@ -714,7 +717,11 @@ void GAME_EXPORT Key_Event( int key, int down ) } // escape is always handled special +#if XASH_PSP + if( ( key == K_ESCAPE || key == K_START_BUTTON ) && down ) +#else if( key == K_ESCAPE && down ) +#endif { switch( cls.key_dest ) { @@ -881,7 +888,11 @@ Normal keyboard characters, already shifted / capslocked / etc void CL_CharEvent( int key ) { // the console key should never be used as a char +#if XASH_PSP + if( key == '`' || key == '~' || key == K_MODE_BUTTON ) return; +#else if( key == '`' || key == '~' ) return; +#endif if( cls.key_dest == key_console && !Con_Visible( )) { @@ -1006,7 +1017,11 @@ static qboolean OSK_KeyEvent( int key, int down ) if( osk.curbutton.val == 0 ) { +#if XASH_PSP + if( key == K_ENTER || key == K_A_BUTTON ) +#else if( key == K_ENTER ) +#endif { osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x]; return true; @@ -1017,6 +1032,9 @@ static qboolean OSK_KeyEvent( int key, int down ) switch ( key ) { +#if XASH_PSP + case K_A_BUTTON: +#endif case K_ENTER: switch( osk.curbutton.val ) { diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index 89f1211be..f735d3a37 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -323,6 +323,10 @@ static ref_api_t gEngfuncs = &clgame.drawFuncs, &g_fsapi, +#if XASH_PSP + P5Ram_Alloc, + P5Ram_Free, +#endif }; static void R_UnloadProgs( void ) @@ -344,33 +348,8 @@ static void R_UnloadProgs( void ) memset( &ref.dllFuncs, 0, sizeof( ref.dllFuncs )); } -static void CL_FillTriAPIFromRef( triangleapi_t *dst, const ref_interface_t *src ) -{ - dst->version = TRI_API_VERSION; - dst->Begin = src->Begin; - dst->RenderMode = TriRenderMode; - dst->End = src->End; - dst->Color4f = TriColor4f; - dst->Color4ub = TriColor4ub; - dst->TexCoord2f = src->TexCoord2f; - dst->Vertex3f = src->Vertex3f; - dst->Vertex3fv = src->Vertex3fv; - dst->Brightness = TriBrightness; - dst->CullFace = TriCullFace; - dst->SpriteTexture = TriSpriteTexture; - dst->WorldToScreen = TriWorldToScreen; - dst->Fog = src->Fog; - dst->ScreenToWorld = src->ScreenToWorld; - dst->GetMatrix = src->GetMatrix; - dst->BoxInPVS = TriBoxInPVS; - dst->LightAtPoint = TriLightAtPoint; - dst->Color4fRendermode = TriColor4fRendermode; - dst->FogParams = src->FogParams; -} - static qboolean R_LoadProgs( const char *name ) { - extern triangleapi_t gTriApi; static ref_api_t gpEngfuncs; REFAPI GetRefAPI; // single export @@ -415,12 +394,18 @@ static qboolean R_LoadProgs( const char *name ) return false; } + // initialize TriAPI callbacks + if( !ref.dllFuncs.getTriAPI( TRI_API_VERSION, &gTriApi )) + { + COM_FreeLibrary( ref.hInstance ); + Con_Reportf( "R_LoadProgs: can't init TriAPI Interface: wrong version, %i must be %i\n", gTriApi.version, TRI_API_VERSION ); + ref.hInstance = NULL; + return false; + } + Cvar_FullSet( "host_refloaded", "1", FCVAR_READ_ONLY ); ref.initialized = true; - // initialize TriAPI callbacks - CL_FillTriAPIFromRef( &gTriApi, &ref.dllFuncs ); - return true; } diff --git a/engine/client/s_load.c b/engine/client/s_load.c index 17f1dd4ef..696672c34 100644 --- a/engine/client/s_load.c +++ b/engine/client/s_load.c @@ -21,7 +21,11 @@ GNU General Public License for more details. // than could actually be referenced during gameplay, // because we don't want to free anything until we are // sure we won't need it. +#if XASH_PSP +#define MAX_SFX 2048 +#else #define MAX_SFX 8192 +#endif #define MAX_SFX_HASH (MAX_SFX/4) static int s_numSfx = 0; diff --git a/engine/client/sound.h b/engine/client/sound.h index 7e581cbce..84c659936 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -201,9 +201,15 @@ typedef struct //==================================================================== +#if XASH_PSP +#define MAX_DYNAMIC_CHANNELS (20 + NUM_AMBIENTS) +#define MAX_CHANNELS (128 + MAX_DYNAMIC_CHANNELS) +#define MAX_RAW_CHANNELS 16 +#else #define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) #define MAX_CHANNELS (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp #define MAX_RAW_CHANNELS 48 +#endif #define MAX_RAW_SAMPLES 8192 extern sound_t ambient_sfx[NUM_AMBIENTS]; diff --git a/engine/common/build.c b/engine/common/build.c new file mode 100644 index 000000000..d9bcca829 --- /dev/null +++ b/engine/common/build.c @@ -0,0 +1,175 @@ +/* +build.c - returns a engine build number +Copyright (C) 2010 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "common.h" + +static char *date = __DATE__ ; +static char *mon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +static char mond[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +// returns days since Apr 1 2015 +int Q_buildnum( void ) +{ + int m = 0, d = 0, y = 0; + static int b = 0; + + if( b != 0 ) return b; + + for( m = 0; m < 11; m++ ) + { + if( !Q_strnicmp( &date[0], mon[m], 3 )) + break; + d += mond[m]; + } + + d += Q_atoi( &date[4] ) - 1; + y = Q_atoi( &date[7] ) - 1900; + b = d + (int)((y - 1) * 365.25f ); + + if((( y % 4 ) == 0 ) && m > 1 ) + { + b += 1; + } + b -= 41728; // Apr 1 2015 + + return b; +} + +/* +============= +Q_buildnum_compat + +Returns a Xash3D build number. This is left for compability with original Xash3D. +IMPORTANT: this value must be changed ONLY after updating to newer Xash3D +IMPORTANT: this value must be acquired through "build" cvar. +============= +*/ +int Q_buildnum_compat( void ) +{ + // do not touch this! Only author of Xash3D can increase buildnumbers! + return 4529; +} + +/* +============ +Q_buildos + +Returns current name of operating system. Without any spaces. +============ +*/ +const char *Q_buildos( void ) +{ + const char *osname; + +#if XASH_MINGW + osname = "win32-mingw"; +#elif XASH_WIN32 + osname = "win32"; +#elif XASH_ANDROID + osname = "android"; +#elif XASH_LINUX + osname = "linux"; +#elif XASH_APPLE + osname = "apple"; +#elif XASH_FREEBSD + osname = "freebsd"; +#elif XASH_NETBSD + osname = "netbsd"; +#elif XASH_OPENBSD + osname = "openbsd"; +#elif XASH_EMSCRIPTEN + osname = "emscripten"; +#elif XASH_DOS4GW + osname = "DOS4GW"; +#elif XASH_PSP + osname = "psp"; +#else +#error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" +#endif + + return osname; +} + +/* +============ +Q_buildos + +Returns current name of operating system. Without any spaces. +============ +*/ +const char *Q_buildarch( void ) +{ + const char *archname; + +#if XASH_AMD64 + archname = "amd64"; +#elif XASH_X86 + archname = "i386"; +#elif XASH_ARM && XASH_64BIT + archname = "arm64"; +#elif XASH_ARM + archname = "armv" + #if XASH_ARM == 8 + "8_32" // for those who (mis)using 32-bit OS on 64-bit CPU + #elif XASH_ARM == 7 + "7" + #elif XASH_ARM == 6 + "6" + #elif XASH_ARM == 5 + "5" + #elif XASH_ARM == 4 + "4" + #endif + + #if XASH_ARM_HARDFP + "hf"; + #else + "l"; + #endif +#elif XASH_MIPS && defined XASH_BIG_ENDIAN + archname = "mips"; +#elif XASH_MIPS && defined XASH_LITTLE_ENDIAN + archname = "mipsel"; +#elif XASH_JS + archname = "javascript"; +#elif XASH_E2K + archname = "e2k"; +#else +#error "Place your architecture name here! If this is a mistake, try to fix conditions above and report a bug" +#endif + + return archname; +} + +/* +============= +Q_buildcommit + +Returns a short hash of current commit in VCS as string. +XASH_BUILD_COMMIT must be passed in quotes + +if XASH_BUILD_COMMIT is not defined, +Q_buildcommit will identify this build as "notset" +============= +*/ +const char *Q_buildcommit( void ) +{ +#ifdef XASH_BUILD_COMMIT + return XASH_BUILD_COMMIT; +#else + return "notset"; +#endif +} + diff --git a/engine/common/common.h b/engine/common/common.h index b73a66b21..f19fb4f62 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -141,6 +141,7 @@ typedef enum #define MAX_DECALS 256 // touching TE_DECAL messages, etc #define MAX_STATIC_ENTITIES 32 // static entities that moved on the client when level is spawn #endif +#endif #define GameState (&host.game) @@ -723,7 +724,6 @@ int R_CreateDecalList( struct decallist_s *pList ); void R_ClearAllDecals( void ); void CL_ClearStaticEntities( void ); qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position ); -struct cl_entity_s *CL_GetEntityByIndex( int index ); void CL_ServerCommand( qboolean reliable, const char *fmt, ... ) _format( 2 ); void CL_HudMessage( const char *pMessage ); const char *CL_MsgInfo( int cmd ); diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c new file mode 100644 index 000000000..6e5eff676 --- /dev/null +++ b/engine/common/filesystem.c @@ -0,0 +1,4556 @@ +/* +filesystem.c - game filesystem based on DP fs +Copyright (C) 2007 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "build.h" +#include +#include +#include +#if XASH_WIN32 +#include +#include +#elif XASH_DOS4GW +#include +#include +#elif XASH_PSP +#include +#include +#include +#include +#else +#include +#include +#endif +#include "miniz.h" // header-only zlib replacement +#include "common.h" +#include "wadfile.h" +#include "filesystem.h" +#include "library.h" +#include "xash3d_mathlib.h" +#include "protocol.h" + +#define FILE_COPY_SIZE (1024 * 1024) +#define FILE_BUFF_SIZE (2048) + +// PAK errors +#define PAK_LOAD_OK 0 +#define PAK_LOAD_COULDNT_OPEN 1 +#define PAK_LOAD_BAD_HEADER 2 +#define PAK_LOAD_BAD_FOLDERS 3 +#define PAK_LOAD_TOO_MANY_FILES 4 +#define PAK_LOAD_NO_FILES 5 +#define PAK_LOAD_CORRUPTED 6 + +// WAD errors +#define WAD_LOAD_OK 0 +#define WAD_LOAD_COULDNT_OPEN 1 +#define WAD_LOAD_BAD_HEADER 2 +#define WAD_LOAD_BAD_FOLDERS 3 +#define WAD_LOAD_TOO_MANY_FILES 4 +#define WAD_LOAD_NO_FILES 5 +#define WAD_LOAD_CORRUPTED 6 + +#ifndef XASH_NO_ZIP +// ZIP errors +#define ZIP_LOAD_OK 0 +#define ZIP_LOAD_COULDNT_OPEN 1 +#define ZIP_LOAD_BAD_HEADER 2 +#define ZIP_LOAD_BAD_FOLDERS 3 +#define ZIP_LOAD_NO_FILES 5 +#define ZIP_LOAD_CORRUPTED 6 +#endif // XASH_NO_ZIP + +typedef struct stringlist_s +{ + // maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +typedef struct wadtype_s +{ + char *ext; + signed char type; +} wadtype_t; + +struct file_s +{ +#if XASH_PSP + SceUID handle; // psp file descriptor +#else + int handle; // file descriptor +#endif + fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) + fs_offset_t position; // current position in the file + fs_offset_t offset; // offset into the package (0 if external file) + int ungetc; // single stored character from ungetc, cleared to EOF when read +#if !XASH_PSP /* unused */ + time_t filetime; // pak, wad or real filetime +#endif + // contents buffer + fs_offset_t buff_ind, buff_len; // buffer current index and length +#if XASH_PSP + byte *buff; // aligned intermediate buffer +#else + byte buff[FILE_BUFF_SIZE]; // intermediate buffer +#endif +#ifdef XASH_REDUCE_FD +#if XASH_PSP + qboolean backup_hold; +#endif + const char *backup_path; + fs_offset_t backup_position; + uint backup_options; +#endif +}; + +struct wfile_s +{ + string filename; + int infotableofs; + byte *mempool; // W_ReadLump temp buffers + int numlumps; + file_t *handle; + dlumpinfo_t *lumps; + time_t filetime; +}; + +typedef struct pack_s +{ + string filename; +#if XASH_PSP + SceUID handle; +#else + int handle; +#endif + int numfiles; + time_t filetime; // common for all packed files + dpackfile_t *files; +} pack_t; + +#ifndef XASH_NO_ZIP +typedef struct zipfile_s +{ + char name[MAX_SYSPATH]; + fs_offset_t offset; // offset of local file header + fs_offset_t size; //original file size + fs_offset_t compressed_size; // compressed file size + unsigned short flags; +} zipfile_t; + +typedef struct zip_s +{ + string filename; +#if XASH_PSP + SceUID handle; +#else + int handle; +#endif + int numfiles; + time_t filetime; + zipfile_t *files; +} zip_t; +#endif // XASH_NO_ZIP + +typedef struct searchpath_s +{ + string filename; + pack_t *pack; + wfile_t *wad; +#ifndef XASH_NO_ZIP + zip_t *zip; +#endif // XASH_NO_ZIP + int flags; + struct searchpath_s *next; +} searchpath_t; + +static byte *fs_mempool; +static searchpath_t *fs_searchpaths = NULL; // chain +static searchpath_t fs_directpath; // static direct path +static char fs_basedir[MAX_SYSPATH]; // base game directory +static char fs_gamedir[MAX_SYSPATH]; // game current directory +static char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) + +static qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes +#if !XASH_WIN32 +static qboolean fs_caseinsensitive = true; // try to search missing files +#endif + +#if XASH_PSP +static fsh_handle_t *fsh_basedir = NULL; +static fsh_handle_t *fsh_gamedir = NULL; +#endif + +#ifdef XASH_REDUCE_FD +static file_t *fs_last_readfile; +#ifndef XASH_NO_ZIP +static zip_t *fs_last_zip; +#endif // XASH_NO_ZIP +static pack_t *fs_last_pak; + +static void FS_EnsureOpenFile( file_t *file ) +{ + if( fs_last_readfile == file ) + return; + + if( file && !file->backup_path ) + return; +#if XASH_PSP + if( fs_last_readfile && (fs_last_readfile->handle != -1) && (fs_last_readfile->backup_hold == false) ) + { + fs_last_readfile->backup_position = sceIoLseek( fs_last_readfile->handle, 0, PSP_SEEK_CUR ); + sceIoClose( fs_last_readfile->handle ); + fs_last_readfile->handle = -1; + } + fs_last_readfile = file; + if( file && (file->handle == -1) ) + { + file->handle = sceIoOpen( file->backup_path, file->backup_options, 0666 ); + sceIoLseek( file->handle, file->backup_position, PSP_SEEK_SET ); + } +#else + if( fs_last_readfile && (fs_last_readfile->handle != -1) ) + { + fs_last_readfile->backup_position = lseek( fs_last_readfile->handle, 0, SEEK_CUR ); + close( fs_last_readfile->handle ); + fs_last_readfile->handle = -1; + } + fs_last_readfile = file; + if( file && (file->handle == -1) ) + { + file->handle = open( file->backup_path, file->backup_options ); + lseek( file->handle, file->backup_position, SEEK_SET ); + } +#endif +} + +#ifndef XASH_NO_ZIP +static void FS_EnsureOpenZip( zip_t *zip ) +{ + if( fs_last_zip == zip ) + return; +#if XASH_PSP + if( fs_last_zip && (fs_last_zip->handle != -1) ) + { + sceIoClose( fs_last_zip->handle ); + fs_last_zip->handle = -1; + } + fs_last_zip = zip; + if( zip && (zip->handle == -1) ) + zip->handle = sceIoOpen( zip->filename, PSP_O_RDONLY, 0666 ); +#else + if( fs_last_zip && (fs_last_zip->handle != -1) ) + { + close( fs_last_zip->handle ); + fs_last_zip->handle = -1; + } + fs_last_zip = zip; + if( zip && (zip->handle == -1) ) + zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); +#endif +} +#endif // XASH_NO_ZIP + +static void FS_BackupFileName( file_t *file, const char *path, uint options ) +{ + if( path == NULL ) + { + if( file->backup_path ) + Mem_Free( (void*)file->backup_path ); + if( file == fs_last_readfile ) + fs_last_readfile = NULL; // FS_EnsureOpenFile( NULL ); + } +#if XASH_PSP + else if( options == PSP_O_RDONLY ) +#else + else if( options == O_RDONLY || options == (O_RDONLY|O_BINARY) ) +#endif + { + file->backup_path = copystring( path ); + file->backup_options = options; + } +} + + +#else +static void FS_EnsureOpenFile( file_t *file ) {} +#ifndef XASH_NO_ZIP +static void FS_EnsureOpenZip( zip_t *zip ) {} +#endif +static void FS_BackupFileName( file_t *file, const char *path, uint options ) {} +#endif + +static void FS_InitMemory( void ); +static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); +static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ); +static dpackfile_t *FS_AddFileToPack( const char* name, pack_t *pack, fs_offset_t offset, fs_offset_t size ); +#ifndef XASH_NO_ZIP +void Zip_Close( zip_t *zip ); +#endif +static byte *W_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); +static wfile_t *W_Open( const char *filename, int *errorcode ); +static qboolean FS_SysFolderExists( const char *path ); +static int FS_SysFileTime( const char *filename ); +static signed char W_TypeFromExt( const char *lumpname ); +static const char *W_ExtFromType( signed char lumptype ); +static void FS_Purge( file_t* file ); + +/* +============================================================================= + +FILEMATCH COMMON SYSTEM + +============================================================================= +*/ +static void stringlistinit( stringlist_t *list ) +{ + memset( list, 0, sizeof( *list )); +} + +static void stringlistfreecontents( stringlist_t *list ) +{ + int i; + + for( i = 0; i < list->numstrings; i++ ) + { + if( list->strings[i] ) + Mem_Free( list->strings[i] ); + list->strings[i] = NULL; + } + + if( list->strings ) + Mem_Free( list->strings ); + + list->numstrings = 0; + list->maxstrings = 0; + list->strings = NULL; +} + +static void stringlistappend( stringlist_t *list, char *text ) +{ + size_t textlen; + + if( !Q_stricmp( text, "." ) || !Q_stricmp( text, ".." )) + return; // ignore the virtual directories + + if( list->numstrings >= list->maxstrings ) + { + list->maxstrings += 4096; + list->strings = Mem_Realloc( fs_mempool, list->strings, list->maxstrings * sizeof( *list->strings )); + } + + textlen = Q_strlen( text ) + 1; + list->strings[list->numstrings] = Mem_Calloc( fs_mempool, textlen ); + memcpy( list->strings[list->numstrings], text, textlen ); + list->numstrings++; +} + +static void stringlistsort( stringlist_t *list ) +{ + char *temp; + int i, j; + + // this is a selection sort (finds the best entry for each slot) + for( i = 0; i < list->numstrings - 1; i++ ) + { + for( j = i + 1; j < list->numstrings; j++ ) + { + if( Q_strcmp( list->strings[i], list->strings[j] ) > 0 ) + { + temp = list->strings[i]; + list->strings[i] = list->strings[j]; + list->strings[j] = temp; + } + } + } +} + +// convert names to lowercase because windows doesn't care, but pattern matching code often does +static void listlowercase( stringlist_t *list ) +{ + char *c; + int i; + + for( i = 0; i < list->numstrings; i++ ) + { + for( c = list->strings[i]; *c; c++ ) + *c = Q_tolower( *c ); + } +} + +static void listdirectory( stringlist_t *list, const char *path, qboolean lowercase ) +{ + int i; + signed char *c; +#if XASH_WIN32 + char pattern[4096]; + struct _finddata_t n_file; + int hFile; +#elif XASH_PSP + SceUID dir; + SceIoDirent entry; +#else + DIR *dir; + struct dirent *entry; +#endif + +#if XASH_WIN32 + Q_snprintf( pattern, sizeof( pattern ), "%s*", path ); + + // ask for the directory listing handle + hFile = _findfirst( pattern, &n_file ); + if( hFile == -1 ) return; + + // start a new chain with the the first name + stringlistappend( list, n_file.name ); + // iterate through the directory + while( _findnext( hFile, &n_file ) == 0 ) + stringlistappend( list, n_file.name ); + _findclose( hFile ); +#elif XASH_PSP + if( ( dir = sceIoDopen( path ) ) < 0 ) + return; + + // iterate through the directory + while( 1 ) + { + // zero the dirent, to avoid possible problems with sceIoDread + memset( &entry, 0, sizeof( SceIoDirent ) ); + if( !sceIoDread( dir, &entry ) ) + break; + stringlistappend( list, entry.d_name ); + } + sceIoDclose( dir ); +#else + if( !( dir = opendir( path ) ) ) + return; + + // iterate through the directory + while( ( entry = readdir( dir ) )) + stringlistappend( list, entry->d_name ); + closedir( dir ); +#endif + +#if XASH_DOS4GW + // convert names to lowercase because 8.3 always in CAPS + listlowercase( list ); +#endif +} + +/* +============================================================================= + +OTHER PRIVATE FUNCTIONS + +============================================================================= +*/ + +/* +================== +FS_FixFileCase + +emulate WIN32 FS behaviour when opening local file +================== +*/ +static const char *FS_FixFileCase( const char *path ) +{ +#if defined __DOS__ & !defined __WATCOM_LFN__ + // not fix, but convert to 8.3 CAPS and rotate slashes + // it is still recommended to package game data + static char out[PATH_MAX]; + int i = 0; + int last = 0; + while(*path) + { + char c = *path++; + + if(c == '/') c = '\\'; + else c = toupper(c); + out[i++] = c; + if(c == '\\' || c == '.') + { + if( i - last > 8 ) + { + char *l = &out[last]; + l[6] = '~'; + l[7] = '1'; + l[8] = c; + i = last + 9; + } + last = i; + } + } + + out[i] = 0; + return out; +#elif !XASH_WIN32 && !XASH_IOS && !XASH_PSP // assume case insensitive + DIR *dir; struct dirent *entry; + char path2[PATH_MAX], *fname; + + if( !fs_caseinsensitive ) + return path; + + if( path[0] != '/' ) + Q_snprintf( path2, sizeof( path2 ), "./%s", path ); + else Q_strncpy( path2, path, PATH_MAX ); + + fname = Q_strrchr( path2, '/' ); + + if( fname ) + *fname++ = 0; + else + { + fname = (char*)path; + Q_strcpy( path2, "."); + } + + /* android has too slow directory scanning, + so drop out some not useful cases */ + if( fname - path2 > 4 ) + { + char *point; + // too many wad textures + if( !Q_stricmp( fname - 5, ".wad") ) + return path; + point = Q_strchr( fname, '.' ); + if( point ) + { + if( !Q_strcmp( point, ".mip") || !Q_strcmp( point, ".dds" ) || !Q_strcmp( point, ".ent" ) ) + return path; + if( fname[0] == '{' ) + return path; + } + } + + //Con_Reportf( "FS_FixFileCase: %s\n", path ); + + if( !( dir = opendir( path2 ) ) ) + return path; + + while( ( entry = readdir( dir ) ) ) + { + if( Q_stricmp( entry->d_name, fname ) ) + continue; + + path = va( "%s/%s", path2, entry->d_name ); + //Con_Reportf( "FS_FixFileCase: %s %s %s\n", path2, fname, entry->d_name ); + break; + } + closedir( dir ); +#endif + return path; +} + +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) +{ + int left, right, middle; + dpackfile_t *pfile; + + // look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // If we found the file, there's a problem + if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); + pack->numfiles++; + + Q_strncpy( pfile->name, name, sizeof( pfile->name )); + pfile->filepos = offset; + pfile->filelen = size; + + return pfile; +} + +/* +============ +FS_CreatePath + +Only used for FS_Open. +============ +*/ +void FS_CreatePath( char *path ) +{ + char *ofs, save; + + for( ofs = path + 1; *ofs; ofs++ ) + { + if( *ofs == '/' || *ofs == '\\' ) + { + // create the directory + save = *ofs; + *ofs = 0; + _mkdir( path ); + *ofs = save; + } + } +} + +/* +============ +FS_Path_f + +debug info +============ +*/ +void FS_Path_f( void ) +{ + searchpath_t *s; + + Con_Printf( "Current search path:\n" ); + + for( s = fs_searchpaths; s; s = s->next ) + { + if( s->pack ) Con_Printf( "%s (%i files)", s->pack->filename, s->pack->numfiles ); + else if( s->wad ) Con_Printf( "%s (%i files)", s->wad->filename, s->wad->numlumps ); +#ifndef XASH_NO_ZIP + else if( s->zip ) Con_Printf( "%s (%i files)", s->zip->filename, s->zip->numfiles ); +#endif + else Con_Printf( "%s", s->filename ); + + if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); + if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); + if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); + if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); + if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); + + Con_Printf( "\n" ); + } +} + +/* +============ +FS_ClearPath_f + +only for debug targets +============ +*/ +void FS_ClearPaths_f( void ) +{ + FS_ClearSearchPath(); +} + +/* +================= +FS_LoadPackPAK + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +pack_t *FS_LoadPackPAK( const char *packfile, int *error ) +{ + dpackheader_t header; +#if XASH_PSP + SceUID packhandle; +#else + int packhandle; +#endif + int i, numpackfiles; + pack_t *pack; + dpackfile_t *info; +#if XASH_PSP + packhandle = sceIoOpen( packfile, PSP_O_RDONLY, 0666 ); +#else + packhandle = open( packfile, O_RDONLY|O_BINARY ); +#endif +#if !XASH_WIN32 && !XASH_PSP + if( packhandle < 0 ) + { + const char *fpackfile = FS_FixFileCase( packfile ); + if( fpackfile!= packfile ) + packhandle = open( fpackfile, O_RDONLY|O_BINARY ); + } +#endif + + if( packhandle < 0 ) + { + Con_Reportf( "%s couldn't open\n", packfile ); + if( error ) *error = PAK_LOAD_COULDNT_OPEN; + return NULL; + } +#if XASH_PSP + sceIoRead( packhandle, (void *)&header, sizeof( header )); +#else + read( packhandle, (void *)&header, sizeof( header )); +#endif + if( header.ident != IDPACKV1HEADER ) + { + Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_HEADER; +#if XASH_PSP + sceIoClose( packhandle ); +#else + close( packhandle ); +#endif + return NULL; + } + + if( header.dirlen % sizeof( dpackfile_t )) + { + Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_FOLDERS; +#if XASH_PSP + sceIoClose( packhandle ); +#else + close( packhandle ); +#endif + return NULL; + } + + numpackfiles = header.dirlen / sizeof( dpackfile_t ); + + if( numpackfiles > MAX_FILES_IN_PACK ) + { + Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); + if( error ) *error = PAK_LOAD_TOO_MANY_FILES; +#if XASH_PSP + sceIoClose( packhandle ); +#else + close( packhandle ); +#endif + return NULL; + } + + if( numpackfiles <= 0 ) + { + Con_Reportf( "%s has no files. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_NO_FILES; +#if XASH_PSP + sceIoClose( packhandle ); +#else + close( packhandle ); +#endif + return NULL; + } + + info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); +#if XASH_PSP + sceIoLseek( packhandle, header.dirofs, PSP_SEEK_SET ); +#else + lseek( packhandle, header.dirofs, SEEK_SET ); +#endif +#if XASH_PSP + if( header.dirlen != sceIoRead( packhandle, (void *)info, header.dirlen ) ) +#else + if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) +#endif + { + Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); + if( error ) *error = PAK_LOAD_CORRUPTED; +#if XASH_PSP + sceIoClose( packhandle ); +#else + close( packhandle ); +#endif + Mem_Free( info ); + return NULL; + } + + pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); + Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); + pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); + pack->filetime = FS_SysFileTime( packfile ); + pack->handle = packhandle; + pack->numfiles = 0; + + // parse the directory + for( i = 0; i < numpackfiles; i++ ) + FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed +#if XASH_PSP + sceIoClose( packhandle ); +#else + close( packhandle ); +#endif + pack->handle = -1; +#endif + + if( error ) *error = PAK_LOAD_OK; + Mem_Free( info ); + + return pack; +} + +#ifndef XASH_NO_ZIP +static zip_t *FS_LoadZip( const char *zipfile, int *error ) +{ + int numpackfiles = 0, i; + zip_cdf_header_t header_cdf; + zip_header_eocd_t header_eocd; + uint signature; + fs_offset_t filepos = 0, length; + zipfile_t *info = NULL; + char filename_buffer[MAX_SYSPATH]; + zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( zip_t ) ); +#if XASH_PSP + zip->handle = sceIoOpen( zipfile, PSP_O_RDONLY, 0666 ); +#else + zip->handle = open( zipfile, O_RDONLY|O_BINARY ); +#endif +#if !XASH_WIN32 && !XASH_PSP + if( zip->handle < 0 ) + { + const char *fzipfile = FS_FixFileCase( zipfile ); + if( fzipfile != zipfile ) + zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); + } +#endif + + if( zip->handle < 0 ) + { + Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + Zip_Close( zip ); + return NULL; + } +#if XASH_PSP + length = sceIoLseek( zip->handle, 0, PSP_SEEK_END ); +#else + length = lseek( zip->handle, 0, SEEK_END ); +#endif + if( length > UINT_MAX ) + { + Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + Zip_Close( zip ); + return NULL; + } +#if XASH_PSP + sceIoLseek( zip->handle, 0, PSP_SEEK_SET ); + sceIoRead( zip->handle, &signature, sizeof( signature ) ); +#else + lseek( zip->handle, 0, SEEK_SET ); + read( zip->handle, &signature, sizeof( signature ) ); +#endif + if( signature == ZIP_HEADER_EOCD ) + { + Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_NO_FILES; + + Zip_Close( zip ); + return NULL; + } + + if( signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + Zip_Close( zip ); + return NULL; + } + + // Find oecd +#if XASH_PSP + sceIoLseek( zip->handle, 0, PSP_SEEK_SET ); +#else + lseek( zip->handle, 0, SEEK_SET ); +#endif + filepos = length; + + while ( filepos > 0 ) + { +#if XASH_PSP + sceIoLseek( zip->handle, filepos, PSP_SEEK_SET ); + sceIoRead( zip->handle, &signature, sizeof( signature ) ); +#else + lseek( zip->handle, filepos, SEEK_SET ); + read( zip->handle, &signature, sizeof( signature ) ); +#endif + if( signature == ZIP_HEADER_EOCD ) + break; + + filepos -= sizeof( char ); // step back one byte + } + + if( ZIP_HEADER_EOCD != signature ) + { + Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + Zip_Close( zip ); + return NULL; + } +#if XASH_PSP + sceIoRead( zip->handle, &header_eocd, sizeof( zip_header_eocd_t ) ); + + // Move to CDF start + sceIoLseek( zip->handle, header_eocd.central_directory_offset, PSP_SEEK_SET ); +#else + read( zip->handle, &header_eocd, sizeof( zip_header_eocd_t ) ); + + // Move to CDF start + lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); +#endif + // Calc count of files in archive + info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( zipfile_t ) * header_eocd.total_central_directory_record ); + + for( i = 0; i < header_eocd.total_central_directory_record; i++ ) + { +#if XASH_PSP + sceIoRead( zip->handle, &header_cdf, sizeof( header_cdf ) ); +#else + read( zip->handle, &header_cdf, sizeof( header_cdf ) ); +#endif + if( header_cdf.signature != ZIP_HEADER_CDF ) + { + Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + Mem_Free( info ); + Zip_Close( zip ); + return NULL; + } + + if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) + { + memset( &filename_buffer, '\0', MAX_SYSPATH ); +#if XASH_PSP + sceIoRead( zip->handle, &filename_buffer, header_cdf.filename_len ); +#else + read( zip->handle, &filename_buffer, header_cdf.filename_len ); +#endif + Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); + + info[numpackfiles].size = header_cdf.uncompressed_size; + info[numpackfiles].compressed_size = header_cdf.compressed_size; + info[numpackfiles].offset = header_cdf.local_header_offset; + numpackfiles++; + } + else +#if XASH_PSP + sceIoLseek( zip->handle, header_cdf.filename_len, PSP_SEEK_CUR ); + + if( header_cdf.extrafield_len ) + sceIoLseek( zip->handle, header_cdf.extrafield_len, PSP_SEEK_CUR ); + + if( header_cdf.file_commentary_len ) + sceIoLseek( zip->handle, header_cdf.file_commentary_len, PSP_SEEK_CUR ); +#else + lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); + + if( header_cdf.extrafield_len ) + lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); + + if( header_cdf.file_commentary_len ) + lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); +#endif + } + + // recalculate offsets + for( i = 0; i < numpackfiles; i++ ) + { + zip_header_t header; +#if XASH_PSP + sceIoLseek( zip->handle, info[i].offset, PSP_SEEK_SET ); + sceIoRead( zip->handle, &header, sizeof( header ) ); +#else + lseek( zip->handle, info[i].offset, SEEK_SET ); + read( zip->handle, &header, sizeof( header ) ); +#endif + info[i].flags = header.compression_flags; + info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); + } + + Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); + zip->filetime = FS_SysFileTime( zipfile ); + zip->numfiles = numpackfiles; + zip->files = info; + +#ifdef XASH_REDUCE_FD + // will reopen when needed +#if XASH_PSP + sceIoClose( zip->handle ); + zip->handle = -1; +#else + close( zip->handle ); + zip->handle = -1; +#endif +#endif + + if( error ) + *error = ZIP_LOAD_OK; + + return zip; +} + +void Zip_Close( zip_t *zip ) +{ + if( !zip ) + return; + + Mem_Free( zip->files ); + + FS_EnsureOpenZip( NULL ); +#if XASH_PSP + if( zip->handle >= 0 ) + sceIoClose( zip->handle ); +#else + if( zip->handle >= 0 ) + close( zip->handle ); +#endif + Mem_Free( zip ); +} + +static byte *Zip_LoadFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + zipfile_t *file = NULL; + byte *compressed_buffer = NULL, *decompressed_buffer = NULL; + int zlib_result = 0; + dword test_crc, final_crc; + z_stream decompress_stream; + + if( sizeptr ) *sizeptr = 0; + + search = FS_FindFile( path, &index, gamedironly ); + + if( !search || !search->zip ) + return NULL; + + file = &search->zip->files[index]; + + FS_EnsureOpenZip( search->zip ); +#if XASH_PSP + if( sceIoLseek( search->zip->handle, file->offset, PSP_SEEK_SET ) < 0 ) + return NULL; +#else + if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) + return NULL; +#endif + /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) + return NULL; + + if( header.signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); + return NULL; + }*/ + + if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) + { + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; +#if XASH_PSP + sceIoRead( search->zip->handle, decompressed_buffer, file->size ); +#else + read( search->zip->handle, decompressed_buffer, file->size ); +#endif +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else if( file->flags == ZIP_COMPRESSION_DEFLATED ) + { + compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; +#if XASH_PSP + sceIoRead( search->zip->handle, compressed_buffer, file->compressed_size ); +#else + read( search->zip->handle, compressed_buffer, file->compressed_size ); +#endif + memset( &decompress_stream, 0, sizeof( decompress_stream ) ); + + decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; + decompress_stream.next_in = (Bytef *)compressed_buffer; + decompress_stream.total_out = decompress_stream.avail_out = file->size; + decompress_stream.next_out = (Bytef *)decompressed_buffer; + + decompress_stream.zalloc = Z_NULL; + decompress_stream.zfree = Z_NULL; + decompress_stream.opaque = Z_NULL; + + if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) + { + Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); + inflateEnd( &decompress_stream ); + + if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) + { + Mem_Free( compressed_buffer ); // finaly free compressed buffer +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); + return NULL; + } + + FS_EnsureOpenZip( NULL ); + return NULL; +} +#endif // XASH_NO_ZIP + +/* +==================== +FS_AddWad_Fullpath +==================== +*/ +static qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + wfile_t *wad = NULL; + const char *ext = COM_FileExtension( wadfile ); + int errorcode = WAD_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->wad && !Q_stricmp( search->wad->filename, wadfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "wad" )) + wad = W_Open( wadfile, &errorcode ); + + if( wad ) + { + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->wad = wad; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); + return true; + } + else + { + if( errorcode != WAD_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); + return false; + } +} + +/* +================ +FS_AddPak_Fullpath + +Adds the given pack to the search path. +The pack type is autodetected by the file extension. + +Returns true if the file was successfully added to the +search path or if it was already included. + +If keep_plain_dirs is set, the pack will be added AFTER the first sequence of +plain directories. +================ +*/ +static qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = COM_FileExtension( pakfile ); + int i, errorcode = PAK_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->pack && !Q_stricmp( search->pack->filename, pakfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "pak" )) + pak = FS_LoadPackPAK( pakfile, &errorcode ); + + if( pak ) + { + string fullpath; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->pack = pak; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < pak->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + return true; + } + else + { + if( errorcode != PAK_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); + return false; + } +} + +#ifndef XASH_NO_ZIP +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + zip_t *zip = NULL; + const char *ext = COM_FileExtension( zipfile ); + int errorcode = ZIP_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->pack && !Q_stricmp( search->pack->filename, zipfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + + if( !Q_stricmp( ext, "pk3" ) ) + zip = FS_LoadZip( zipfile, &errorcode ); + + if( zip ) + { + string fullpath; + int i; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); + search->zip = zip; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < zip->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + return true; + } + else + { + if( errorcode != ZIP_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); + return false; + } +} +#endif // XASH_NO_ZIP + +/* +================ +FS_AddGameDirectory + +Sets fs_writedir, adds the directory to the head of the path, +then loads and adds pak1.pak pak2.pak ... +================ +*/ +void FS_AddGameDirectory( const char *dir, uint flags ) +{ + stringlist_t list; + searchpath_t *search; + string fullpath; + int i; + + if( !FBitSet( flags, FS_NOWRITE_PATH )) + Q_strncpy( fs_writedir, dir, sizeof( fs_writedir )); + stringlistinit( &list ); + listdirectory( &list, dir, false ); + stringlistsort( &list ); + + // add any PAK package in the directory + for( i = 0; i < list.numstrings; i++ ) + { + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pak" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddPak_Fullpath( fullpath, NULL, flags ); + } + } + +#ifndef XASH_NO_ZIP + // add any Zip package in the directory + for( i = 0; i < list.numstrings; i++ ) + { + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "zip" ) || !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddZip_Fullpath( fullpath, NULL, flags ); + } + } +#endif + + FS_AllowDirectPaths( true ); + + // add any WAD package in the directory + for( i = 0; i < list.numstrings; i++ ) + { + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "wad" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + stringlistfreecontents( &list ); + FS_AllowDirectPaths( false ); + + // add the directory to the search path + // (unpacked files have the priority over packed files) + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + Q_strncpy( search->filename, dir, sizeof ( search->filename )); + search->next = fs_searchpaths; + search->flags = flags; + fs_searchpaths = search; +} + +/* +================ +FS_AddGameHierarchy +================ +*/ +void FS_AddGameHierarchy( const char *dir, uint flags ) +{ + int i; + qboolean isGameDir = flags & FS_GAMEDIR_PATH; + + GI->added = true; + + if( !COM_CheckString( dir )) + return; + + // add the common game directory + + // recursive gamedirs + // for example, czeror->czero->cstrike->valve + for( i = 0; i < SI.numgames; i++ ) + { + if( !Q_strnicmp( SI.games[i]->gamefolder, dir, 64 )) + { + Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, SI.games[i]->gamefolder, SI.games[i]->basedir ); + if( !SI.games[i]->added && Q_stricmp( SI.games[i]->gamefolder, SI.games[i]->basedir ) ) + { + SI.games[i]->added = true; + FS_AddGameHierarchy( SI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); + } + break; + } + } + + if( host.rodir[0] ) + { + // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH + uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); + if( isGameDir ) + newFlags |= FS_GAMERODIR_PATH; + + FS_AllowDirectPaths( true ); + FS_AddGameDirectory( va( "%s/%s/", host.rodir, dir ), newFlags ); + FS_AllowDirectPaths( false ); + } +#if !XASH_PSP + if( isGameDir ) + FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); +#endif + FS_AddGameDirectory( va( "%s/", dir ), flags ); +#if !XASH_PSP + if( isGameDir ) + FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); +#endif +} + +/* +================ +FS_ClearSearchPath +================ +*/ +void FS_ClearSearchPath( void ) +{ + while( fs_searchpaths ) + { + searchpath_t *search = fs_searchpaths; + + if( !search ) break; + + if( FBitSet( search->flags, FS_STATIC_PATH )) + { + // skip read-only pathes + if( search->next ) + fs_searchpaths = search->next->next; + else break; + } + else fs_searchpaths = search->next; + + if( search->pack ) + { + if( search->pack->files ) + Mem_Free( search->pack->files ); +#if XASH_PSP + if( search->pack->handle >= 0 ) + sceIoClose( search->pack->handle ); +#else + if( search->pack->handle >= 0 ) + close( search->pack->handle ); +#endif + Mem_Free( search->pack ); + } + + if( search->wad ) + { + W_Close( search->wad ); + } + +#ifndef XASH_NO_ZIP + if( search->zip ) + { + Zip_Close(search->zip); + } +#endif // XASH_NO_ZIP + + Mem_Free( search ); + } +} + +/* +==================== +FS_CheckNastyPath + +Return true if the path should be rejected due to one of the following: +1: path elements that are non-portable +2: path elements that would allow access to files outside the game directory, + or are just not a good idea for a mod to be using. +==================== +*/ +int FS_CheckNastyPath( const char *path, qboolean isgamedir ) +{ + // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless + if( !COM_CheckString( path )) return 2; + + if( fs_ext_path ) return 0; // allow any path + + // Mac: don't allow Mac-only filenames - : is a directory separator + // instead of /, but we rely on / working already, so there's no reason to + // support a Mac-only path + // Amiga and Windows: : tries to go to root of drive + if( Q_strstr( path, ":" )) return 1; // non-portable attempt to go to root of drive + + // Amiga: // is parent directory + if( Q_strstr( path, "//" )) return 1; // non-portable attempt to go to parent directory + + // all: don't allow going to parent directory (../ or /../) + if( Q_strstr( path, ".." )) return 2; // attempt to go outside the game directory + + // Windows and UNIXes: don't allow absolute paths + if( path[0] == '/' ) return 2; // attempt to go outside the game directory + + // all: forbid trailing slash on gamedir + if( isgamedir && path[Q_strlen( path )-1] == '/' ) return 2; + + // all: forbid leading dot on any filename for any reason + if( Q_strstr( path, "/." )) return 2; // attempt to go outside the game directory + + // after all these checks we're pretty sure it's a / separated filename + // and won't do much if any harm + return 0; +} + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan( void ) +{ + const char *str; + const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; + Con_Reportf( "FS_Rescan( %s )\n", GI->title ); + + FS_ClearSearchPath(); + +#if XASH_IOS + { + FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); + FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); + } +#else + str = getenv( "XASH3D_EXTRAS_PAK1" ); + if( COM_CheckString( str ) ) + FS_AddPak_Fullpath( str, NULL, extrasFlags ); + str = getenv( "XASH3D_EXTRAS_PAK2" ); + if( COM_CheckString( str ) ) + FS_AddPak_Fullpath( str, NULL, extrasFlags ); +#endif + + if( Q_stricmp( GI->basedir, GI->gamefolder )) + FS_AddGameHierarchy( GI->basedir, 0 ); + if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) + FS_AddGameHierarchy( GI->falldir, 0 ); + FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); + + if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) + Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc + else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc +} + +/* +================ +FS_Rescan_f +================ +*/ +void FS_Rescan_f( void ) +{ + FS_Rescan(); +} + +/* +================ +FS_WriteGameInfo + +assume GameInfo is valid +================ +*/ +static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) +{ + file_t *f = FS_Open( filepath, "w", false ); // we in binary-mode + int i, write_ambients = false; + + if( !f ) Sys_Error( "FS_WriteGameInfo: can't write %s\n", filepath ); // may be disk-space is out? + + FS_Printf( f, "// generated by %s %s-%s (%s-%s)\n\n\n", XASH_ENGINE_NAME, XASH_VERSION, Q_buildcommit(), Q_buildos(), Q_buildarch() ); + + if( Q_strlen( GameInfo->basedir )) + FS_Printf( f, "basedir\t\t\"%s\"\n", GameInfo->basedir ); + + // DEPRECATED: gamedir key isn't supported by FWGS fork + // but write it anyway to keep compability with original Xash3D + if( Q_strlen( GameInfo->gamefolder )) + FS_Printf( f, "gamedir\t\t\"%s\"\n", GameInfo->gamefolder ); + + if( Q_strlen( GameInfo->falldir )) + FS_Printf( f, "fallback_dir\t\"%s\"\n", GameInfo->falldir ); + + if( Q_strlen( GameInfo->title )) + FS_Printf( f, "title\t\t\"%s\"\n", GameInfo->title ); + + if( Q_strlen( GameInfo->startmap )) + FS_Printf( f, "startmap\t\t\"%s\"\n", GameInfo->startmap ); + + if( Q_strlen( GameInfo->trainmap )) + FS_Printf( f, "trainmap\t\t\"%s\"\n", GameInfo->trainmap ); + + if( GameInfo->version != 0.0f ) + FS_Printf( f, "version\t\t%g\n", GameInfo->version ); + + if( GameInfo->size != 0 ) + FS_Printf( f, "size\t\t%lu\n", GameInfo->size ); + + if( Q_strlen( GameInfo->game_url )) + FS_Printf( f, "url_info\t\t\"%s\"\n", GameInfo->game_url ); + + if( Q_strlen( GameInfo->update_url )) + FS_Printf( f, "url_update\t\t\"%s\"\n", GameInfo->update_url ); + + if( Q_strlen( GameInfo->type )) + FS_Printf( f, "type\t\t\"%s\"\n", GameInfo->type ); + + if( Q_strlen( GameInfo->date )) + FS_Printf( f, "date\t\t\"%s\"\n", GameInfo->date ); + + if( Q_strlen( GameInfo->dll_path )) + FS_Printf( f, "dllpath\t\t\"%s\"\n", GameInfo->dll_path ); + + if( Q_strlen( GameInfo->game_dll )) + FS_Printf( f, "gamedll\t\t\"%s\"\n", GameInfo->game_dll ); + + if( Q_strlen( GameInfo->game_dll_linux )) + FS_Printf( f, "gamedll_linux\t\t\"%s\"\n", GameInfo->game_dll_linux ); + + if( Q_strlen( GameInfo->game_dll_osx )) + FS_Printf( f, "gamedll_osx\t\t\"%s\"\n", GameInfo->game_dll_osx ); +#if XASH_PSP + if( Q_strlen( GameInfo->game_dll_psp )) + FS_Printf( f, "gamedll_psp\t\t\"%s\"\n", GameInfo->game_dll_psp ); +#endif + if( Q_strlen( GameInfo->iconpath )) + FS_Printf( f, "icon\t\t\"%s\"\n", GameInfo->iconpath ); + + switch( GameInfo->gamemode ) + { + case 1: FS_Print( f, "gamemode\t\t\"singleplayer_only\"\n" ); break; + case 2: FS_Print( f, "gamemode\t\t\"multiplayer_only\"\n" ); break; + } + + if( Q_strlen( GameInfo->sp_entity )) + FS_Printf( f, "sp_entity\t\t\"%s\"\n", GameInfo->sp_entity ); + if( Q_strlen( GameInfo->mp_entity )) + FS_Printf( f, "mp_entity\t\t\"%s\"\n", GameInfo->mp_entity ); + if( Q_strlen( GameInfo->mp_filter )) + FS_Printf( f, "mp_filter\t\t\"%s\"\n", GameInfo->mp_filter ); + + if( GameInfo->secure ) + FS_Printf( f, "secure\t\t\"%i\"\n", GameInfo->secure ); + + if( GameInfo->nomodels ) + FS_Printf( f, "nomodels\t\t\"%i\"\n", GameInfo->nomodels ); + + if( GameInfo->max_edicts > 0 ) + FS_Printf( f, "max_edicts\t%i\n", GameInfo->max_edicts ); + if( GameInfo->max_tents > 0 ) + FS_Printf( f, "max_tempents\t%i\n", GameInfo->max_tents ); + if( GameInfo->max_beams > 0 ) + FS_Printf( f, "max_beams\t\t%i\n", GameInfo->max_beams ); + if( GameInfo->max_particles > 0 ) + FS_Printf( f, "max_particles\t%i\n", GameInfo->max_particles ); + + for( i = 0; i < NUM_AMBIENTS; i++ ) + { + if( *GameInfo->ambientsound[i] ) + { + if( !write_ambients ) + { + FS_Print( f, "\n" ); + write_ambients = true; + } + FS_Printf( f, "ambient%i\t\t%s\n", i, GameInfo->ambientsound[i] ); + } + } + + FS_Print( f, "\n\n\n" ); + FS_Close( f ); // all done +} + +void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir ) +{ + memset( GameInfo, 0, sizeof( *GameInfo )); + + // filesystem info + Q_strncpy( GameInfo->gamefolder, gamedir, sizeof( GameInfo->gamefolder )); + Q_strncpy( GameInfo->basedir, "valve", sizeof( GameInfo->basedir )); + GameInfo->falldir[0] = 0; + Q_strncpy( GameInfo->startmap, "c0a0", sizeof( GameInfo->startmap )); + Q_strncpy( GameInfo->trainmap, "t0a0", sizeof( GameInfo->trainmap )); + Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title )); + GameInfo->version = 1.0f; + + // .dll pathes + Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path )); + Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll )); + Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux )); + Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx )); +#if XASH_PSP + Q_strncpy( GameInfo->game_dll_psp, "dlls/server.prx", sizeof( GameInfo->game_dll_psp )); +#endif + // .ico path + Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath )); + + Q_strncpy( GameInfo->sp_entity, "info_player_start", sizeof( GameInfo->sp_entity )); + Q_strncpy( GameInfo->mp_entity, "info_player_deathmatch", sizeof( GameInfo->mp_entity )); + + GameInfo->max_edicts = 900; // default value if not specified + GameInfo->max_tents = 500; + GameInfo->max_beams = 128; + GameInfo->max_particles = 4096; +} + +void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qboolean isGameInfo ) +{ + char *pfile = (char*) buf; + qboolean found_linux = false, found_osx = false; +#if XASH_PSP + qboolean found_psp = false; +#endif + string token; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + // different names in liblist/gameinfo + if( !Q_stricmp( token, isGameInfo ? "title" : "game" )) + { + pfile = COM_ParseFile( pfile, GameInfo->title ); + } + // valid for both + else if( !Q_stricmp( token, "fallback_dir" )) + { + pfile = COM_ParseFile( pfile, GameInfo->falldir ); + } + // valid for both + else if( !Q_stricmp( token, "startmap" )) + { + pfile = COM_ParseFile( pfile, GameInfo->startmap ); + COM_StripExtension( GameInfo->startmap ); // HQ2:Amen has extension .bsp + } + // only trainmap is valid for gameinfo + else if( !Q_stricmp( token, "trainmap" ) || + (!isGameInfo && !Q_stricmp( token, "trainingmap" ))) + { + pfile = COM_ParseFile( pfile, GameInfo->trainmap ); + COM_StripExtension( GameInfo->trainmap ); // HQ2:Amen has extension .bsp + } + // valid for both + else if( !Q_stricmp( token, "url_info" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_url ); + } + // different names + else if( !Q_stricmp( token, isGameInfo ? "url_update" : "url_dl" )) + { + pfile = COM_ParseFile( pfile, GameInfo->update_url ); + } + // valid for both + else if( !Q_stricmp( token, "gamedll" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_dll ); + COM_FixSlashes( GameInfo->game_dll ); + } + // valid for both + else if( !Q_stricmp( token, "gamedll_linux" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_dll_linux ); + found_linux = true; + } + // valid for both + else if( !Q_stricmp( token, "gamedll_osx" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_dll_osx ); + found_osx = true; + } +#if XASH_PSP + // valid for both + else if( !Q_stricmp( token, "gamedll_psp" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_dll_psp ); + found_psp = true; + } +#endif + // valid for both + else if( !Q_stricmp( token, "icon" )) + { + pfile = COM_ParseFile( pfile, GameInfo->iconpath ); + COM_FixSlashes( GameInfo->iconpath ); + COM_DefaultExtension( GameInfo->iconpath, ".ico" ); + } + else if( !Q_stricmp( token, "type" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( !isGameInfo && !Q_stricmp( token, "singleplayer_only" )) + { + // TODO: Remove this ugly hack too. + // This was made because Half-Life has multiplayer, + // but for some reason it's marked as singleplayer_only. + // Old WON version is fine. + if( !Q_stricmp( GameInfo->gamefolder, "valve") ) + GameInfo->gamemode = GAME_NORMAL; + else + GameInfo->gamemode = GAME_SINGLEPLAYER_ONLY; + Q_strncpy( GameInfo->type, "Single", sizeof( GameInfo->type )); + } + else if( !isGameInfo && !Q_stricmp( token, "multiplayer_only" )) + { + GameInfo->gamemode = GAME_MULTIPLAYER_ONLY; + Q_strncpy( GameInfo->type, "Multiplayer", sizeof( GameInfo->type )); + } + else + { + // pass type without changes + if( !isGameInfo ) + GameInfo->gamemode = GAME_NORMAL; + Q_strncpy( GameInfo->type, token, sizeof( GameInfo->type )); + } + } + // valid for both + else if( !Q_stricmp( token, "version" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->version = Q_atof( token ); + } + // valid for both + else if( !Q_stricmp( token, "size" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->size = Q_atoi( token ); + } + else if( !Q_stricmp( token, "edicts" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_edicts = Q_atoi( token ); + } + else if( !Q_stricmp( token, isGameInfo ? "mp_entity" : "mpentity" )) + { + pfile = COM_ParseFile( pfile, GameInfo->mp_entity ); + } + else if( !Q_stricmp( token, isGameInfo ? "mp_filter" : "mpfilter" )) + { + pfile = COM_ParseFile( pfile, GameInfo->mp_filter ); + } + // valid for both + else if( !Q_stricmp( token, "secure" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->secure = Q_atoi( token ); + } + // valid for both + else if( !Q_stricmp( token, "nomodels" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->nomodels = Q_atoi( token ); + } + else if( !Q_stricmp( token, isGameInfo ? "max_edicts" : "edicts" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_edicts = bound( MIN_EDICTS, Q_atoi( token ), MAX_EDICTS ); + } + // only for gameinfo + else if( isGameInfo ) + { + if( !Q_stricmp( token, "basedir" )) + { + string fs_path; + pfile = COM_ParseFile( pfile, fs_path ); + if( Q_stricmp( fs_path, GameInfo->basedir ) || Q_stricmp( fs_path, GameInfo->gamefolder )) + Q_strncpy( GameInfo->basedir, fs_path, sizeof( GameInfo->basedir )); + } + else if( !Q_stricmp( token, "sp_entity" )) + { + pfile = COM_ParseFile( pfile, GameInfo->sp_entity ); + } + else if( isGameInfo && !Q_stricmp( token, "dllpath" )) + { + pfile = COM_ParseFile( pfile, GameInfo->dll_path ); + } + else if( isGameInfo && !Q_stricmp( token, "date" )) + { + pfile = COM_ParseFile( pfile, GameInfo->date ); + } + else if( !Q_stricmp( token, "max_tempents" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_tents = bound( 300, Q_atoi( token ), 2048 ); + } + else if( !Q_stricmp( token, "max_beams" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_beams = bound( 64, Q_atoi( token ), 512 ); + } + else if( !Q_stricmp( token, "max_particles" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_particles = bound( 4096, Q_atoi( token ), 32768 ); + } + else if( !Q_stricmp( token, "gamemode" )) + { + pfile = COM_ParseFile( pfile, token ); + // TODO: Remove this ugly hack too. + // This was made because Half-Life has multiplayer, + // but for some reason it's marked as singleplayer_only. + // Old WON version is fine. + if( !Q_stricmp( token, "singleplayer_only" ) && Q_stricmp( GameInfo->gamefolder, "valve") ) + GameInfo->gamemode = GAME_SINGLEPLAYER_ONLY; + else if( !Q_stricmp( token, "multiplayer_only" )) + GameInfo->gamemode = GAME_MULTIPLAYER_ONLY; + } + else if( !Q_strnicmp( token, "ambient", 7 )) + { + int ambientNum = Q_atoi( token + 7 ); + + if( ambientNum < 0 || ambientNum > ( NUM_AMBIENTS - 1 )) + ambientNum = 0; + pfile = COM_ParseFile( pfile, GameInfo->ambientsound[ambientNum] ); + } + else if( !Q_stricmp( token, "noskills" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->noskills = Q_atoi( token ); + } + } + } + +#if XASH_PSP + if( !found_psp ) + Q_snprintf( GameInfo->game_dll_psp, sizeof( GameInfo->game_dll_psp ), "dlls/server.prx" ); +#else + if( !found_linux || !found_osx ) + { + // just replace extension from dll to so/dylib + char gamedll[64]; + Q_strncpy( gamedll, GameInfo->game_dll, sizeof( gamedll )); + COM_StripExtension( gamedll ); + + if( !found_linux ) + Q_snprintf( GameInfo->game_dll_linux, sizeof( GameInfo->game_dll_linux ), "%s.so", gamedll ); + + if( !found_osx ) + Q_snprintf( GameInfo->game_dll_osx, sizeof( GameInfo->game_dll_osx ), "%s.dylib", gamedll ); + } +#endif + // make sure what gamedir is really exist + if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", host.rootdir, GameInfo->falldir ))) + GameInfo->falldir[0] = '\0'; +} + +/* +================ +FS_CreateDefaultGameInfo +================ +*/ +void FS_CreateDefaultGameInfo( const char *filename ) +{ + gameinfo_t defGI; + + FS_InitGameInfo( &defGI, fs_basedir ); + + // make simple gameinfo.txt + FS_WriteGameInfo( filename, &defGI ); +} + +/* +================ +FS_ParseLiblistGam +================ +*/ +static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo ) +{ + char *afile; + + if( !GameInfo ) return false; + afile = (char *)FS_LoadFile( filename, NULL, false ); + if( !afile ) return false; + + FS_InitGameInfo( GameInfo, gamedir ); + + FS_ParseGenericGameInfo( GameInfo, afile, false ); + + Mem_Free( afile ); + + return true; +} + +/* +================ +FS_ConvertGameInfo +================ +*/ +static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path ) +{ + gameinfo_t GameInfo; + + memset( &GameInfo, 0, sizeof( GameInfo )); + + if( FS_ParseLiblistGam( liblist_path, gamedir, &GameInfo )) + { + Con_DPrintf( "Convert %s to %s\n", liblist_path, gameinfo_path ); + FS_WriteGameInfo( gameinfo_path, &GameInfo ); + + return true; + } + + return false; +} + +/* +================ +FS_ReadGameInfo +================ +*/ +static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, gameinfo_t *GameInfo ) +{ + char *afile; + + afile = (char *)FS_LoadFile( filepath, NULL, false ); + if( !afile ) return false; + + FS_InitGameInfo( GameInfo, gamedir ); + + FS_ParseGenericGameInfo( GameInfo, afile, true ); + + Mem_Free( afile ); + + return true; +} + +/* +================ +FS_CheckForGameDir +================ +*/ +static qboolean FS_CheckForGameDir( const char *gamedir ) +{ + // if directory contain config.cfg it's 100% gamedir + if( FS_FileExists( va( "%s/config.cfg", gamedir ), false )) + return true; + + // if directory contain progs.dat it's 100% gamedir + if( FS_FileExists( va( "%s/progs.dat", gamedir ), false )) + return true; + + // quake mods probably always archived but can missed config.cfg before first running + if( FS_FileExists( va( "%s/pak0.pak", gamedir ), false )) + return true; + + // NOTE; adds here some additional checks if you wished + + return false; +} + +/* +================ +FS_ParseGameInfo +================ +*/ +static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) +{ + string liblist_path, gameinfo_path; + string default_gameinfo_path; + gameinfo_t tmpGameInfo; + qboolean haveUpdate = false; + + Q_snprintf( default_gameinfo_path, sizeof( default_gameinfo_path ), "%s/gameinfo.txt", fs_basedir ); + Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir ); + Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir ); + + // here goes some RoDir magic... + if( host.rodir[0] ) + { + string filepath_ro, liblist_ro; + fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime; + + Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", host.rodir, gamedir ); + Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", host.rodir, gamedir ); + + roLibListTime = FS_SysFileTime( liblist_ro ); + roGameInfoTime = FS_SysFileTime( filepath_ro ); + rwGameInfoTime = FS_SysFileTime( gameinfo_path ); + + if( roLibListTime > rwGameInfoTime ) + { + haveUpdate = FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_ro ); + } + else if( roGameInfoTime > rwGameInfoTime ) + { + char *afile_ro = (char *)FS_LoadDirectFile( filepath_ro, NULL ); + + if( afile_ro ) + { + gameinfo_t gi; + + haveUpdate = true; + + FS_InitGameInfo( &gi, gamedir ); + FS_ParseGenericGameInfo( &gi, afile_ro, true ); + FS_WriteGameInfo( gameinfo_path, &gi ); + Mem_Free( afile_ro ); + } + } + } + + // if user change liblist.gam update the gameinfo.txt + if( FS_FileTime( liblist_path, false ) > FS_FileTime( gameinfo_path, false )) + FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path ); + + // force to create gameinfo for specified game if missing + if(( FS_CheckForGameDir( gamedir ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false )) + { + memset( &tmpGameInfo, 0, sizeof( tmpGameInfo )); + + if( FS_ReadGameInfo( default_gameinfo_path, gamedir, &tmpGameInfo )) + { + // now we have copy of game info from basedir but needs to change gamedir + Con_DPrintf( "Convert %s to %s\n", default_gameinfo_path, gameinfo_path ); + Q_strncpy( tmpGameInfo.gamefolder, gamedir, sizeof( tmpGameInfo.gamefolder )); + FS_WriteGameInfo( gameinfo_path, &tmpGameInfo ); + } + else FS_CreateDefaultGameInfo( gameinfo_path ); + } + + if( !GameInfo || !FS_FileExists( gameinfo_path, false )) + return false; // no dest + + if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo )) + return true; + return false; +} + +/* +================ +FS_LoadGameInfo + +can be passed null arg +================ +*/ +void FS_LoadGameInfo( const char *rootfolder ) +{ + int i; + + // lock uplevel of gamedir for read\write + fs_ext_path = false; + + if( rootfolder ) Q_strcpy( fs_gamedir, rootfolder ); + Con_Reportf( "FS_LoadGameInfo( %s )\n", fs_gamedir ); + + // clear any old pathes + FS_ClearSearchPath(); + + // validate gamedir + for( i = 0; i < SI.numgames; i++ ) + { + if( !Q_stricmp( SI.games[i]->gamefolder, fs_gamedir )) + break; + } + + if( i == SI.numgames ) + Sys_Error( "Couldn't find game directory '%s'\n", fs_gamedir ); + + SI.GameInfo = SI.games[i]; + + if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll ) ) + { + SI.gamedll[0] = 0; + } + + if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib ) ) + { + SI.clientlib[0] = 0; + } + + FS_Rescan(); // create new filesystem + + Image_CheckPaletteQ1 (); + Host_InitDecals (); // reload decals +} + +/* +================ +FS_Init +================ +*/ +void FS_Init( void ) +{ + stringlist_t dirs; + qboolean hasBaseDir = false; + qboolean hasGameDir = false; + int i; + + FS_InitMemory(); + + Cmd_AddCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); + Cmd_AddCommand( "fs_path", FS_Path_f, "show filesystem search pathes" ); + Cmd_AddCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); + +#if !XASH_WIN32 && !XASH_PSP + if( Sys_CheckParm( "-casesensitive" ) ) + fs_caseinsensitive = false; + + if( !fs_caseinsensitive ) + { + if( host.rodir[0] && !Q_strcmp( host.rodir, host.rootdir ) ) + { + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + } + } + else +#endif + { + if( host.rodir[0] && !Q_stricmp( host.rodir, host.rootdir ) ) + { + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + } + } + + // ignore commandlineoption "-game" for other stuff + SI.numgames = 0; + + Q_strncpy( fs_basedir, SI.basedirName, sizeof( fs_basedir )); // default dir + + if( !Sys_GetParmFromCmdLine( "-game", fs_gamedir )) + Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // gamedir == basedir + + if( FS_CheckNastyPath( fs_basedir, true )) + { + // this is completely fatal... + Sys_Error( "invalid base directory \"%s\"\n", fs_basedir ); + } + + if( FS_CheckNastyPath( fs_gamedir, true )) + { + Con_Printf( S_ERROR "invalid game directory \"%s\"\n", fs_gamedir ); + Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // default dir + } + + // add readonly directories first + if( host.rodir[0] ) + { + stringlistinit( &dirs ); + listdirectory( &dirs, host.rodir, false ); + stringlistsort( &dirs ); + + for( i = 0; i < dirs.numstrings; i++ ) + { + // no need to check folders here, FS_CreatePath will not fail if path exists + // and listdirectory returns only really existing directories + FS_CreatePath( va( "%s" PATH_SPLITTER "%s/", host.rootdir, dirs.strings[i] )); + } + + stringlistfreecontents( &dirs ); + } + + // validate directories + stringlistinit( &dirs ); + listdirectory( &dirs, "./", false ); + stringlistsort( &dirs ); + + for( i = 0; i < dirs.numstrings; i++ ) + { + if( !Q_stricmp( fs_basedir, dirs.strings[i] )) + hasBaseDir = true; + + if( !Q_stricmp( fs_gamedir, dirs.strings[i] )) + hasGameDir = true; + } + + if( !hasGameDir ) + { + Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir ); + if( hasBaseDir ) Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); + } + + // build list of game directories here + FS_AddGameDirectory( "./", 0 ); + + for( i = 0; i < dirs.numstrings; i++ ) + { + if( !FS_SysFolderExists( dirs.strings[i] ) || ( !Q_stricmp( dirs.strings[i], ".." ) && !fs_ext_path )) + continue; + + if( SI.games[SI.numgames] == NULL ) + SI.games[SI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); + + if( FS_ParseGameInfo( dirs.strings[i], SI.games[SI.numgames] )) + SI.numgames++; // added + } + + stringlistfreecontents( &dirs ); +#if XASH_PSP + fsh_basedir = FSH_Create( fs_basedir, 5000 ); + if( !fsh_basedir ) + Con_Reportf( "FS_Init: FS Helper error (fsh_basedir)\n" ); + + if( Q_stricmp( fs_basedir, fs_gamedir )) + { + fsh_gamedir = FSH_Create( fs_gamedir, 5000 ); + if( !fsh_gamedir ) + Con_Reportf( "FS_Init: FS Helper error (fsh_gamedir)\n" ); + } + else fsh_gamedir = NULL; +#endif + Con_Reportf( "FS_Init: done\n" ); +} + +void FS_AllowDirectPaths( qboolean enable ) +{ + fs_ext_path = enable; +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown( void ) +{ + int i; + + // release gamedirs + for( i = 0; i < SI.numgames; i++ ) + if( SI.games[i] ) Mem_Free( SI.games[i] ); + + memset( &SI, 0, sizeof( sysinfo_t )); + + FS_ClearSearchPath(); // release all wad files too + Mem_FreePool( &fs_mempool ); +#if XASH_PSP + if( fsh_basedir ) + FSH_Free( fsh_basedir ); + fsh_basedir = NULL; + + if( fsh_gamedir ) + FSH_Free( fsh_gamedir ); + fsh_gamedir = NULL; +#endif +} + +/* +==================== +FS_SysFileTime + +Internal function used to determine filetime +==================== +*/ +static int FS_SysFileTime( const char *filename ) +{ +#if XASH_PSP + SceIoStat buf; + time_t fileTime; + + if( sceIoGetstat( filename, &buf ) < 0 ) + return -1; + + if( sceRtcGetTime_t(( pspTime* )&buf.st_mtime, &fileTime ) < 0 ) // typedef pspTime! wft? ( pspsdk bug ) + return -1; + + return fileTime; +#else + struct stat buf; + + if( stat( filename, &buf ) == -1 ) + return -1; + + return buf.st_mtime; +#endif +} + +/* +==================== +FS_SysOpen + +Internal function used to create a file_t and open the relevant non-packed file on disk +==================== +*/ +static file_t *FS_SysOpen( const char *filepath, const char *mode ) +{ + file_t *file; + int mod, opt; + uint ind; +#if XASH_PSP +#ifdef XASH_REDUCE_FD + qboolean backup_hold = false; +#endif + // Parse the mode string + switch( mode[0] ) + { + case 'r': // read + mod = PSP_O_RDONLY; + opt = 0; + break; + case 'w': // write + mod = PSP_O_WRONLY; + opt = PSP_O_CREAT | PSP_O_TRUNC; + break; + case 'a': // append + mod = PSP_O_WRONLY; + opt = PSP_O_CREAT | PSP_O_APPEND; + break; + case 'e': // edit + mod = PSP_O_WRONLY; + opt = PSP_O_CREAT; + break; + default: + return NULL; + } + + for( ind = 1; mode[ind] != '\0'; ind++ ) + { + switch( mode[ind] ) + { + case '+': + mod = PSP_O_RDWR; + break; + case 'b': // not used + break; +#ifdef XASH_REDUCE_FD + case 'h': // hold + backup_hold = true; + break; +#endif + default: + break; + } + } +#else + // Parse the mode string + switch( mode[0] ) + { + case 'r': // read + mod = O_RDONLY; + opt = 0; + break; + case 'w': // write + mod = O_WRONLY; + opt = O_CREAT | O_TRUNC; + break; + case 'a': // append + mod = O_WRONLY; + opt = O_CREAT | O_APPEND; + break; + case 'e': // edit + mod = O_WRONLY; + opt = O_CREAT; + break; + default: + return NULL; + } + + for( ind = 1; mode[ind] != '\0'; ind++ ) + { + switch( mode[ind] ) + { + case '+': + mod = O_RDWR; + break; + case 'b': + opt |= O_BINARY; + break; + default: + break; + } + } +#endif +#if XASH_PSP + file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t ) + FILE_BUFF_SIZE + 64 ); + file->buff = (byte *)(((( u32 )file + sizeof( file_t )) & ( ~( 64 - 1 ))) + 64 ); +#else + file = (file_t *)Mem_Calloc( fs_mempool, sizeof( *file )); +#endif +#if !XASH_PSP + file->filetime = FS_SysFileTime( filepath ); +#endif + file->ungetc = EOF; +#if XASH_PSP + file->handle = sceIoOpen( filepath, mod|opt, 0666 ); +#else + file->handle = open( filepath, mod|opt, 0666 ); +#endif + +#if XASH_PSP + file->backup_hold = backup_hold; + if( file->handle >= 0 ) + { + FS_BackupFileName( file, filepath, mod|opt ); + if( mod == PSP_O_WRONLY || mod == PSP_O_RDWR ) + { + if( FSH_AddFilePath( fsh_basedir, filepath ) == -2 ) + FSH_AddFilePath( fsh_gamedir, filepath ); + } + } +#elif !XASH_WIN32 + if( file->handle < 0 ) + { + const char *ffilepath = FS_FixFileCase( filepath ); + if( ffilepath != filepath ) + file->handle = open( ffilepath, mod|opt, 0666 ); + if( file->handle >= 0 ) + FS_BackupFileName( file, ffilepath, mod|opt ); + } + else + FS_BackupFileName( file, filepath, mod|opt ); +#endif + + + if( file->handle < 0 ) + { + Mem_Free( file ); + return NULL; + } + +#if XASH_PSP + file->real_length = sceIoLseek( file->handle, 0, PSP_SEEK_END ); +#else + file->real_length = lseek( file->handle, 0, SEEK_END ); +#endif + // uncomment do disable write + //if( opt & O_CREAT ) + // return NULL; + + // For files opened in append mode, we start at the end of the file +#if XASH_PSP + if( opt & PSP_O_APPEND ) file->position = file->real_length; + else sceIoLseek( file->handle, 0, PSP_SEEK_SET ); +#else + if( opt & O_APPEND ) file->position = file->real_length; + else lseek( file->handle, 0, SEEK_SET ); +#endif + + return file; +} +/* +static int FS_DuplicateHandle( const char *filename, int handle, fs_offset_t pos ) +{ +#ifdef HAVE_DUP + return dup( handle ); +#else + int newhandle = open( filename, O_RDONLY|O_BINARY ); + lseek( newhandle, pos, SEEK_SET ); + return newhandle; +#endif +} +*/ + +static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) +{ +#if XASH_PSP + file_t *file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t ) + FILE_BUFF_SIZE + 64 ); + file->buff = (byte *)( ( ( ( u32 )file + sizeof( file_t ) ) & ( ~( 64 - 1 ) ) ) + 64 ); +#else + file_t *file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t )); +#endif +#ifndef XASH_REDUCE_FD +#if XASH_PSP + file->handle = sceIoOpen( syspath, PSP_O_RDONLY, 0666 ); + if( file->handle < 0 ) + { + Mem_Free( file ); + return NULL; + } + + if( sceIoLseek( file->handle, offset, SEEK_SET ) < 0 ) + { + Mem_Free( file ); + return NULL; + } +#else /* XASH_PSP */ +#ifdef HAVE_DUP + file->handle = dup( handle ); +#else + file->handle = open( syspath, O_RDONLY|O_BINARY ); +#endif + + if( lseek( file->handle, offset, SEEK_SET ) == -1 ) + { + Mem_Free( file ); + return NULL; + } +#endif /* XASH_PSP */ +#else /* XASH_REDUCE_FD */ + file->backup_position = offset; + file->backup_path = copystring( syspath ); +#if XASH_PSP + file->backup_hold = false; + file->backup_options = PSP_O_RDONLY; +#else + file->backup_options = O_RDONLY|O_BINARY; +#endif + file->handle = -1; +#endif /* XASH_REDUCE_FD */ + + file->real_length = len; + file->offset = offset; + file->position = 0; + file->ungetc = EOF; + + return file; +} + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) +{ + dpackfile_t *pfile; + + pfile = &pack->files[pack_ind]; + + return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); +} + +/* +=========== +FS_OpenZipFile + +Open a packed file using its package file descriptor +=========== +*/ +#ifndef XASH_NO_ZIP +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) +{ + zipfile_t *pfile; + pfile = &zip->files[pack_ind]; + + // compressed files handled in Zip_LoadFile + if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) + return NULL; + + return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); +} +#endif // XASH_NO_ZIP + +/* +================== +FS_SysFileExists + +Look for a file in the filesystem only +================== +*/ +qboolean FS_SysFileExists( const char *path, qboolean caseinsensitive ) +{ +#if XASH_WIN32 + int desc; + + if(( desc = open( path, O_RDONLY|O_BINARY )) < 0 ) + return false; + + close( desc ); + return true; +#elif XASH_PSP + int result; + SceIoStat buf; + + result = FSH_Find( fsh_basedir, path ); + if( result == -2 ) + result = FSH_Find( fsh_gamedir, path ); + + if( result >= 0 ) + return true; + if( result == -1 ) + return false; + + result = sceIoGetstat( path, &buf ); + if ( result < 0 ) + return false; + return FIO_S_ISREG( buf.st_mode ); +#else + int ret; + struct stat buf; + + ret = stat( path, &buf ); + + // speedup custom path search + if( caseinsensitive && ( ret < 0 ) ) + { + const char *fpath = FS_FixFileCase( path ); + if( fpath != path ) + ret = stat( fpath, &buf ); + } + + if( ret < 0 ) + return false; + + return S_ISREG( buf.st_mode ); +#endif +} + +/* +================== +FS_SysFolderExists + +Look for a existing folder +================== +*/ +qboolean FS_SysFolderExists( const char *path ) +{ +#if XASH_WIN32 + DWORD dwFlags = GetFileAttributes( path ); + + return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY ); +#elif XASH_PSP + SceUID dir = sceIoDopen( path ); + + if( dir >= 0 ) + { + sceIoDclose( dir ); + return true; + } + else if( ( dir == 0x80010002 ) || ( dir == 0x80010014 ) ) + { + return false; + } + else + { + Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %#010x\n", dir ); + return false; + } +#else + DIR *dir = opendir( path ); + + if( dir ) + { + closedir( dir ); + return true; + } + else if( (errno == ENOENT) || (errno == ENOTDIR) ) + { + return false; + } + else + { + Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno ) ); + return false; + } +#endif +} + +/* +==================== +FS_FindFile + +Look for a file in the packages and in the filesystem + +Return the searchpath where the file was found (or NULL) +and the file index in the package if relevant +==================== +*/ +static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) +{ + searchpath_t *search; + char *pEnvPath; + + // search through the path, one element at a time + for( search = fs_searchpaths; search; search = search->next ) + { + if( gamedironly & !FBitSet( search->flags, FS_GAMEDIRONLY_SEARCH_FLAGS )) + continue; + + // is the element a pak file? + if( search->pack ) + { + int left, right, middle; + pack_t *pak; + + pak = search->pack; + + // look for the file (binary search) + left = 0; + right = pak->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pak->files[middle].name, name ); + + // Found it + if( !diff ) + { + if( index ) *index = middle; + return search; + } + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + } + else if( search->wad ) + { + dlumpinfo_t *lump; + signed char type = W_TypeFromExt( name ); + qboolean anywadname = true; + string wadname, wadfolder; + string shortname; + + // quick reject by filetype + if( type == TYP_NONE ) continue; + COM_ExtractFilePath( name, wadname ); + wadfolder[0] = '\0'; + + if( Q_strlen( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( search->wad->filename, shortname ); + COM_DefaultExtension( shortname, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, shortname )) + continue; + + // NOTE: we can't using long names for wad, + // because we using original wad names[16]; + COM_FileBase( name, shortname ); + + lump = W_FindLump( search->wad, shortname, type ); + + if( lump ) + { + if( index ) + *index = lump - search->wad->lumps; + return search; + } + } +#ifndef XASH_NO_ZIP + else if( search->zip ) + { + int i; + for( i = 0; search->zip->numfiles > i; i++) + { + if( !Q_stricmp( search->zip->files[i].name, name ) ) + { + if( index ) + *index = i; + return search; + } + } + } +#endif // XASH_NO_ZIP + else + { + char netpath[MAX_SYSPATH]; + + Q_sprintf( netpath, "%s%s", search->filename, name ); + + if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) + { + if( index != NULL ) *index = -1; + return search; + } + } + } + + if( fs_ext_path ) + { + char netpath[MAX_SYSPATH]; + + // clear searchpath + search = &fs_directpath; + memset( search, 0, sizeof( searchpath_t )); + + // root folder has a more priority than netpath + Q_strncpy( search->filename, host.rootdir, sizeof( search->filename )); + Q_strcat( search->filename, PATH_SPLITTER ); + Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); + + if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) + { + if( index != NULL ) + *index = -1; + return search; + } + +#if 0 + // search for environment path + while( ( pEnvPath = getenv( "Path" ) ) ) + { + char *end = Q_strchr( pEnvPath, ';' ); + if( !end ) break; + Q_strncpy( search->filename, pEnvPath, (end - pEnvPath) + 1 ); + Q_strcat( search->filename, PATH_SPLITTER ); + Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); + + if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) + { + if( index != NULL ) + *index = -1; + return search; + } + pEnvPath += (end - pEnvPath) + 1; // move pointer + } +#endif // 0 + } + + if( index != NULL ) + *index = -1; + + return NULL; +} + + +/* +=========== +FS_OpenReadFile + +Look for a file in the search paths and open it in read-only mode +=========== +*/ +file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly ) +{ + searchpath_t *search; + int pack_ind; + + search = FS_FindFile( filename, &pack_ind, gamedironly ); + + // not found? + if( search == NULL ) + return NULL; + + if( search->pack ) + return FS_OpenPackedFile( search->pack, pack_ind ); + else if( search->wad ) + return NULL; // let W_LoadFile get lump correctly +#ifndef XASH_NO_ZIP + else if( search->zip ) + return FS_OpenZipFile( search->zip, pack_ind ); +#endif // XASH_NO_ZIP + else if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; + + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysOpen( path, mode ); + } + + return NULL; +} + +/* +============================================================================= + +MAIN PUBLIC FUNCTIONS + +============================================================================= +*/ +/* +==================== +FS_Open + +Open a file. The syntax is the same as fopen +==================== +*/ +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) +{ + // some stupid mappers used leading '/' or '\' in path to models or sounds + if( filepath[0] == '/' || filepath[0] == '\\' ) + filepath++; + + if( filepath[0] == '/' || filepath[0] == '\\' ) + filepath++; + + if( FS_CheckNastyPath( filepath, false )) + return NULL; + + // if the file is opened in "write", "append", or "read/write" mode + if( mode[0] == 'w' || mode[0] == 'a'|| mode[0] == 'e' || Q_strchr( mode, '+' )) + { + char real_path[MAX_SYSPATH]; + + // open the file on disk directly +#if XASH_PSP /* Fixed double slashes */ + Q_sprintf( real_path, "%s%s", fs_writedir, filepath ); +#else + Q_sprintf( real_path, "%s/%s", fs_writedir, filepath ); +#endif + FS_CreatePath( real_path );// Create directories up to the file + return FS_SysOpen( real_path, mode ); + } + + // else, we look at the various search paths and open the file in read-only mode + return FS_OpenReadFile( filepath, mode, gamedironly ); +} + +/* +==================== +FS_Close + +Close a file +==================== +*/ +int FS_Close( file_t *file ) +{ + if( !file ) return 0; + + FS_BackupFileName( file, NULL, 0 ); + + if( file->handle >= 0 ) +#if XASH_PSP + if( sceIoClose( file->handle )) +#else + if( close( file->handle )) +#endif + return EOF; + + Mem_Free( file ); + return 0; +} + +/* +==================== +FS_Write + +Write "datasize" bytes into a file +==================== +*/ +fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ) +{ + fs_offset_t result; + + if( !file ) return 0; + + // if necessary, seek to the exact file position we're supposed to be + if( file->buff_ind != file->buff_len ) +#if XASH_PSP + if(file->position != file->buff_ind - file->buff_len) + { + sceIoLseek( file->handle, file->buff_ind - file->buff_len, PSP_SEEK_CUR ); + file->position = file->buff_ind - file->buff_len; + } +#else + lseek( file->handle, file->buff_ind - file->buff_len, SEEK_CUR ); +#endif + // purge cached data + FS_Purge( file ); +#if XASH_PSP + // write the buffer and update the position + result = sceIoWrite( file->handle, data, (fs_offset_t)datasize ); + file->position += result; //sceIoLseek( file->handle, 0, PSP_SEEK_CUR ); +#else + // write the buffer and update the position + result = write( file->handle, data, (fs_offset_t)datasize ); + file->position = lseek( file->handle, 0, SEEK_CUR ); +#endif + if( file->real_length < file->position ) + file->real_length = file->position; + + if( result < 0 ) + return 0; + return result; +} + +/* +==================== +FS_Read + +Read up to "buffersize" bytes from a file +==================== +*/ +fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ) +{ + fs_offset_t count, done; + fs_offset_t nb; + + // nothing to copy + if( buffersize == 0 ) return 1; + + // Get rid of the ungetc character + if( file->ungetc != EOF ) + { + ((char*)buffer)[0] = file->ungetc; + buffersize--; + file->ungetc = EOF; + done = 1; + } + else done = 0; + + // first, we copy as many bytes as we can from "buff" + if( file->buff_ind < file->buff_len ) + { + count = file->buff_len - file->buff_ind; + + done += ((fs_offset_t)buffersize > count ) ? count : (fs_offset_t)buffersize; + memcpy( buffer, &file->buff[file->buff_ind], done ); + file->buff_ind += done; + + buffersize -= done; + if( buffersize == 0 ) + return done; + } + + // NOTE: at this point, the read buffer is always empty + + FS_EnsureOpenFile( file ); + // we must take care to not read after the end of the file + count = file->real_length - file->position; + + // if we have a lot of data to get, put them directly into "buffer" +#if XASH_PSP + if( buffersize > FILE_BUFF_SIZE / 2 ) +#else + if( buffersize > sizeof( file->buff ) / 2 ) +#endif + { + if( count > (fs_offset_t)buffersize ) + count = (fs_offset_t)buffersize; +#if XASH_PSP + if( file->offset > 0 ) + sceIoLseek( file->handle, file->offset + file->position, PSP_SEEK_SET ); + nb = sceIoRead (file->handle, &((byte *)buffer)[done], count ); +#else + lseek( file->handle, file->offset + file->position, SEEK_SET ); + nb = read (file->handle, &((byte *)buffer)[done], count ); +#endif + if( nb > 0 ) + { + done += nb; + file->position += nb; + // purge cached data + FS_Purge( file ); + } + } + else + { +#if XASH_PSP + if( count > FILE_BUFF_SIZE ) + count = FILE_BUFF_SIZE; + + if( count == 0 ) + return done; + + if( file->offset > 0 ) + sceIoLseek( file->handle, file->offset + file->position, PSP_SEEK_SET ); + nb = sceIoRead( file->handle, file->buff, count ); +#else + if( count > (fs_offset_t)sizeof( file->buff )) + count = (fs_offset_t)sizeof( file->buff ); + + lseek( file->handle, file->offset + file->position, SEEK_SET ); + nb = read( file->handle, file->buff, count ); +#endif + if( nb > 0 ) + { + file->buff_len = nb; + file->position += nb; + + // copy the requested data in "buffer" (as much as we can) + count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize; + memcpy( &((byte *)buffer)[done], file->buff, count ); + file->buff_ind = count; + done += count; + } + } + + return done; +} + +/* +==================== +FS_Print + +Print a string into a file +==================== +*/ +int FS_Print( file_t *file, const char *msg ) +{ + return FS_Write( file, msg, Q_strlen( msg )); +} + +/* +==================== +FS_Printf + +Print a string into a file +==================== +*/ +int FS_Printf( file_t *file, const char *format, ... ) +{ + int result; + va_list args; + + va_start( args, format ); + result = FS_VPrintf( file, format, args ); + va_end( args ); + + return result; +} + +/* +==================== +FS_VPrintf + +Print a string into a file +==================== +*/ +int FS_VPrintf( file_t *file, const char *format, va_list ap ) +{ + int len; + fs_offset_t buff_size = MAX_SYSPATH; + char *tempbuff; + + if( !file ) return 0; + + while( 1 ) + { + tempbuff = (char *)Mem_Malloc( fs_mempool, buff_size ); + len = Q_vsprintf( tempbuff, format, ap ); + + if( len >= 0 && len < buff_size ) + break; + + Mem_Free( tempbuff ); + buff_size *= 2; + } +#if XASH_PSP + len = sceIoWrite( file->handle, tempbuff, len ); +#else + len = write( file->handle, tempbuff, len ); +#endif + Mem_Free( tempbuff ); + + return len; +} + +/* +==================== +FS_Getc + +Get the next character of a file +==================== +*/ +int FS_Getc( file_t *file ) +{ + char c; + + if( FS_Read( file, &c, 1 ) != 1 ) + return EOF; + + return c; +} + +/* +==================== +FS_UnGetc + +Put a character back into the read buffer (only supports one character!) +==================== +*/ +int FS_UnGetc( file_t *file, byte c ) +{ + // If there's already a character waiting to be read + if( file->ungetc != EOF ) + return EOF; + + file->ungetc = c; + return c; +} + +/* +==================== +FS_Gets + +Same as fgets +==================== +*/ +int FS_Gets( file_t *file, byte *string, size_t bufsize ) +{ + int c, end = 0; + + while( 1 ) + { + c = FS_Getc( file ); + + if( c == '\r' || c == '\n' || c < 0 ) + break; + + if( end < bufsize - 1 ) + string[end++] = c; + } + string[end] = 0; + + // remove \n following \r + if( c == '\r' ) + { + c = FS_Getc( file ); + + if( c != '\n' ) + FS_UnGetc( file, (byte)c ); + } + + return c; +} + +/* +==================== +FS_Seek + +Move the position index in a file +==================== +*/ +int FS_Seek( file_t *file, fs_offset_t offset, int whence ) +{ + // compute the file offset + switch( whence ) + { + case SEEK_CUR: + offset += file->position - file->buff_len + file->buff_ind; + break; + case SEEK_SET: + break; + case SEEK_END: + offset += file->real_length; + break; + default: + return -1; + } + + if( offset < 0 || offset > file->real_length ) + return -1; + + // if we have the data in our read buffer, we don't need to actually seek + if( file->position - file->buff_len <= offset && offset <= file->position ) + { + file->buff_ind = offset + file->buff_len - file->position; + return 0; + } + + FS_EnsureOpenFile( file ); + // Purge cached data + FS_Purge( file ); +#if XASH_PSP + if( file->position == offset && file->offset <= 0 ) + return 0; + if( sceIoLseek( file->handle, file->offset + offset, PSP_SEEK_SET ) < 0 ) + return -1; +#else + if( lseek( file->handle, file->offset + offset, SEEK_SET ) == -1 ) + return -1; +#endif + file->position = offset; + + return 0; +} + +/* +==================== +FS_Tell + +Give the current position in a file +==================== +*/ +fs_offset_t FS_Tell( file_t *file ) +{ + if( !file ) return 0; + return file->position - file->buff_len + file->buff_ind; +} + +/* +==================== +FS_Eof + +indicates at reached end of file +==================== +*/ +qboolean FS_Eof( file_t *file ) +{ + if( !file ) return true; + return (( file->position - file->buff_len + file->buff_ind ) == file->real_length ) ? true : false; +} + +/* +==================== +FS_Purge + +Erases any buffered input or output data +==================== +*/ +void FS_Purge( file_t *file ) +{ + file->buff_len = 0; + file->buff_ind = 0; + file->ungetc = EOF; +} + +/* +============ +FS_LoadFile + +Filename are relative to the xash directory. +Always appends a 0 byte. +============ +*/ +byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ) +{ + file_t *file; + byte *buf = NULL; + fs_offset_t filesize = 0; + + file = FS_Open( path, "rb", gamedironly ); + + if( file ) + { + filesize = file->real_length; + + buf = (byte *)Mem_Malloc( fs_mempool, filesize + 1 ); + buf[filesize] = '\0'; + FS_Read( file, buf, filesize ); + FS_Close( file ); + } + else + { + buf = W_LoadFile( path, &filesize, gamedironly ); +#ifndef XASH_NO_ZIP + if( !buf ) + buf = Zip_LoadFile( path, &filesize, gamedironly ); +#endif // XASH_NO_ZIP + + } + + if( filesizeptr ) + *filesizeptr = filesize; + + return buf; +} + +qboolean CRC32_File( dword *crcvalue, const char *filename ) +{ + char buffer[1024]; + int num_bytes; + file_t *f; + + f = FS_Open( filename, "rb", false ); + if( !f ) return false; + + Assert( crcvalue != NULL ); + CRC32_Init( crcvalue ); + + while( 1 ) + { + num_bytes = FS_Read( f, buffer, sizeof( buffer )); + + if( num_bytes > 0 ) + CRC32_ProcessBuffer( crcvalue, buffer, num_bytes ); + + if( FS_Eof( f )) break; + } + + FS_Close( f ); + return true; +} + +qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ) +{ + file_t *file; + byte buffer[1024]; + MD5Context_t MD5_Hash; + int bytes; + + if(( file = FS_Open( pszFileName, "rb", false )) == NULL ) + return false; + + memset( &MD5_Hash, 0, sizeof( MD5Context_t )); + + MD5Init( &MD5_Hash ); + + if( seed ) + { + MD5Update( &MD5_Hash, (const byte *)seed, 16 ); + } + + while( 1 ) + { + bytes = FS_Read( file, buffer, sizeof( buffer )); + + if( bytes > 0 ) + MD5Update( &MD5_Hash, buffer, bytes ); + + if( FS_Eof( file )) + break; + } + + FS_Close( file ); + MD5Final( digest, &MD5_Hash ); + + return true; +} + +/* +============ +FS_LoadFile + +Filename are relative to the xash directory. +Always appends a 0 byte. +============ +*/ +byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ) +{ + file_t *file; + byte *buf = NULL; + fs_offset_t filesize = 0; + + file = FS_SysOpen( path, "rb" ); + + if( !file ) + { + return NULL; + } + + // Try to load + filesize = file->real_length; + buf = (byte *)Mem_Malloc( fs_mempool, filesize + 1 ); + buf[filesize] = '\0'; + FS_Read( file, buf, filesize ); + FS_Close( file ); + + if( filesizeptr ) + *filesizeptr = filesize; + + return buf; +} + + +/* +============ +FS_WriteFile + +The filename will be prefixed by the current game directory +============ +*/ +qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ) +{ + file_t *file; + + file = FS_Open( filename, "wb", false ); + + if( !file ) + { + Con_Reportf( S_ERROR "FS_WriteFile: failed on %s\n", filename); + return false; + } + + FS_Write( file, data, len ); + FS_Close( file ); + + return true; +} + +/* +============================================================================= + +OTHERS PUBLIC FUNCTIONS + +============================================================================= +*/ +/* +================== +FS_FileExists + +Look for a file in the packages and in the filesystem +================== +*/ +int GAME_EXPORT FS_FileExists( const char *filename, int gamedironly ) +{ + if( FS_FindFile( filename, NULL, gamedironly )) + return true; + return false; +} + +/* +================== +FS_GetDiskPath + +Build direct path for file in the filesystem +return NULL for file in pack +================== +*/ +const char *FS_GetDiskPath( const char *name, qboolean gamedironly ) +{ + int index; + searchpath_t *search; + + search = FS_FindFile( name, &index, gamedironly ); + + if( search ) + { + if( index != -1 ) // file in pack or wad + return NULL; + return va( "%s%s", search->filename, name ); + } + + return NULL; +} + +/* +================== +FS_CheckForCrypt + +return true if library is crypted +================== +*/ +qboolean FS_CheckForCrypt( const char *dllname ) +{ + file_t *f; + int key; + + f = FS_Open( dllname, "rb", false ); + if( !f ) return false; + + FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes + FS_Read( f, &key, sizeof( key )); + FS_Close( f ); + + return ( key == 0x12345678 ) ? true : false; +} + +/* +================== +FS_FindLibrary + +search for library, assume index is valid +only for internal use +================== +*/ +dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) +{ + string dllpath; + searchpath_t *search; + dll_user_t *hInst; + int i, index; + int start = 0; + + // check for bad exports + if( !COM_CheckString( dllname )) + return NULL; + + fs_ext_path = directpath; + + // HACKHACK remove absoulte path to valve folder + if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) + start += 9; + + // replace all backward slashes + for( i = 0; i < Q_strlen( dllname ); i++ ) + { + if( dllname[i+start] == '\\' ) dllpath[i] = '/'; + else dllpath[i] = Q_tolower( dllname[i+start] ); + } + dllpath[i] = '\0'; + + COM_DefaultExtension( dllpath, "."OS_LIB_EXT ); // apply ext if forget + search = FS_FindFile( dllpath, &index, false ); + + if( !search && !directpath ) + { + fs_ext_path = false; + + // trying check also 'bin' folder for indirect paths + Q_strncpy( dllpath, dllname, sizeof( dllpath )); + search = FS_FindFile( dllpath, &index, false ); + if( !search ) return NULL; // unable to find + } + + // NOTE: for libraries we not fail even if search is NULL + // let the OS find library himself + hInst = Mem_Calloc( host.mempool, sizeof( dll_user_t )); + + // save dllname for debug purposes + Q_strncpy( hInst->dllName, dllname, sizeof( hInst->dllName )); + + // shortPath is used for LibraryLoadSymbols only + Q_strncpy( hInst->shortPath, dllpath, sizeof( hInst->shortPath )); + + hInst->encrypted = FS_CheckForCrypt( dllpath ); + + if( index < 0 && !hInst->encrypted && search ) + { + Q_snprintf( hInst->fullPath, sizeof( hInst->fullPath ), "%s%s", search->filename, dllpath ); + hInst->custom_loader = false; // we can loading from disk and use normal debugging + } + else + { + // NOTE: if search is NULL let the OS found library himself + Q_strncpy( hInst->fullPath, dllpath, sizeof( hInst->fullPath )); + hInst->custom_loader = (search) ? true : false; + } + fs_ext_path = false; // always reset direct paths + + return hInst; +} + +/* +================== +FS_FileSize + +return size of file in bytes +================== +*/ +fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ) +{ + int length = -1; // in case file was missed + file_t *fp; + +#if XASH_PSP + char buffer[64]; + + Q_sprintf( buffer, "%s/%s", fs_basedir, filename ); + length = FSH_FindSize( fsh_basedir, buffer ); + if( length < 0 && length != -3 ) + { + Q_sprintf( buffer, "%s/%s", fs_gamedir, filename ); + length = FSH_FindSize( fsh_gamedir, buffer ); + } + + if( length < 0 ) + { + fp = FS_Open( filename, "rb", gamedironly ); + + if( fp ) + { + // it exists + FS_Seek( fp, 0, SEEK_END ); + length = FS_Tell( fp ); + FS_Close( fp ); + } + else length = -1; + } +#else + fp = FS_Open( filename, "rb", gamedironly ); + + if( fp ) + { + // it exists + FS_Seek( fp, 0, SEEK_END ); + length = FS_Tell( fp ); + FS_Close( fp ); + } +#endif + return length; +} + +/* +================== +FS_FileLength + +return size of file in bytes +================== +*/ +fs_offset_t FS_FileLength( file_t *f ) +{ + if( !f ) return 0; + return f->real_length; +} + +/* +================== +FS_FileTime + +return time of creation file in seconds +================== +*/ +int FS_FileTime( const char *filename, qboolean gamedironly ) +{ + searchpath_t *search; + int pack_ind; + + search = FS_FindFile( filename, &pack_ind, gamedironly ); + if( !search ) return -1; // doesn't exist + + if( search->pack ) // grab pack filetime + return search->pack->filetime; + else if( search->wad ) // grab wad filetime + return search->wad->filetime; +#ifndef XASH_NO_ZIP + else if( search->zip ) + return search->zip->filetime; +#endif // XASH_NO_ZIP + else if( pack_ind < 0 ) + { + // found in the filesystem? + char path [MAX_SYSPATH]; + + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysFileTime( path ); + } + + return -1; // doesn't exist +} + +/* +================== +FS_Rename + +rename specified file from gamefolder +================== +*/ +qboolean FS_Rename( const char *oldname, const char *newname ) +{ + char oldpath[MAX_SYSPATH], newpath[MAX_SYSPATH]; + qboolean iRet; + + if( !oldname || !newname || !*oldname || !*newname ) + return false; + +#if XASH_PSP // BUG: Adrenaline PSVITA: sceIoRename requires full path + Q_snprintf( oldpath, sizeof( oldpath ), "%s/%s%s", host.rootdir, fs_writedir, oldname ); + Q_snprintf( newpath, sizeof( newpath ), "%s/%s%s", host.rootdir, fs_writedir, newname ); +#else + Q_snprintf( oldpath, sizeof( oldpath ), "%s%s", fs_writedir, oldname ); + Q_snprintf( newpath, sizeof( newpath ), "%s%s", fs_writedir, newname ); +#endif + + COM_FixSlashes( oldpath ); + COM_FixSlashes( newpath ); + +#if XASH_PSP + iRet = sceIoRename( oldpath, newpath ); + if( !iRet ) + { + if( FSH_RenameFilePath( fsh_basedir, oldpath, newpath ) == -2 ) + FSH_RenameFilePath( fsh_gamedir, oldpath, newpath ); + } +#else + iRet = rename( oldpath, newpath ); +#endif + + return (iRet == 0); +} + +/* +================== +FS_Delete + +delete specified file from gamefolder +================== +*/ +qboolean GAME_EXPORT FS_Delete( const char *path ) +{ + char real_path[MAX_SYSPATH]; + qboolean iRet; + + if( !path || !*path ) + return false; + + Q_snprintf( real_path, sizeof( real_path ), "%s%s", fs_writedir, path ); + COM_FixSlashes( real_path ); + +#if XASH_PSP + iRet = sceIoRemove( real_path ); + if( !iRet ) + { + if( FSH_RemoveFilePath( fsh_basedir, real_path ) == -2 ) + FSH_RemoveFilePath( fsh_gamedir, real_path ); + } +#else + iRet = remove( real_path ); +#endif + + return (iRet == 0); +} + +/* +================== +FS_FileCopy + +================== +*/ +qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ) +{ + char *buf = Mem_Malloc( fs_mempool, FILE_COPY_SIZE ); + int size, readSize; + qboolean done = true; + + while( fileSize > 0 ) + { + if( fileSize > FILE_COPY_SIZE ) + size = FILE_COPY_SIZE; + else size = fileSize; + + if(( readSize = FS_Read( pInput, buf, size )) < size ) + { + Con_Reportf( S_ERROR "FS_FileCopy: unexpected end of input file (%d < %d)\n", readSize, size ); + fileSize = 0; + done = false; + break; + } + + FS_Write( pOutput, buf, readSize ); + fileSize -= size; + } + + Mem_Free( buf ); + return done; +} + +/* +=========== +FS_Search + +Allocate and fill a search structure with information on matching filenames. +=========== +*/ +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) +{ + search_t *search = NULL; + searchpath_t *searchpath; + pack_t *pak; + wfile_t *wad; + int i, basepathlength, numfiles, numchars; + int resultlistindex, dirlistindex; + const char *slash, *backslash, *colon, *separator; + string netpath, temp; + stringlist_t resultlist; + stringlist_t dirlist; + char *basepath; + + if( pattern[0] == '.' || pattern[0] == ':' || pattern[0] == '/' || pattern[0] == '\\' ) + return NULL; // punctuation issues + + stringlistinit( &resultlist ); + stringlistinit( &dirlist ); + slash = Q_strrchr( pattern, '/' ); + backslash = Q_strrchr( pattern, '\\' ); + colon = Q_strrchr( pattern, ':' ); + separator = max( slash, backslash ); + separator = max( separator, colon ); + basepathlength = separator ? (separator + 1 - pattern) : 0; + basepath = Mem_Calloc( fs_mempool, basepathlength + 1 ); + if( basepathlength ) memcpy( basepath, pattern, basepathlength ); + basepath[basepathlength] = 0; + + // search through the path, one element at a time + for( searchpath = fs_searchpaths; searchpath; searchpath = searchpath->next ) + { + if( gamedironly && !FBitSet( searchpath->flags, FS_GAMEDIRONLY_SEARCH_FLAGS )) + continue; + + // is the element a pak file? + if( searchpath->pack ) + { + // look through all the pak file elements + pak = searchpath->pack; + for( i = 0; i < pak->numfiles; i++ ) + { + Q_strncpy( temp, pak->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + stringlistappend( &resultlist, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } + } + else if( searchpath->wad ) + { + string wadpattern, wadname, temp2; + signed char type = W_TypeFromExt( pattern ); + qboolean anywadname = true; + string wadfolder; + + // quick reject by filetype + if( type == TYP_NONE ) continue; + COM_ExtractFilePath( pattern, wadname ); + COM_FileBase( pattern, wadpattern ); + wadfolder[0] = '\0'; + + if( Q_strlen( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( searchpath->wad->filename, temp2 ); + COM_DefaultExtension( temp2, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, temp2 )) + continue; + + // look through all the wad file elements + wad = searchpath->wad; + + for( i = 0; i < wad->numlumps; i++ ) + { + // if type not matching, we already have no chance ... + if( type != TYP_ANY && wad->lumps[i].type != type ) + continue; + + // build the lumpname with image suffix (if present) + Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); + + while( temp[0] ) + { + if( matchpattern( temp, wadpattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + { + // build path: wadname/lumpname.ext + Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); + COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); + stringlistappend( &resultlist, temp2 ); + } + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } + } + else + { + // get a directory listing and look at each name + Q_sprintf( netpath, "%s%s", searchpath->filename, basepath ); + stringlistinit( &dirlist ); + listdirectory( &dirlist, netpath, caseinsensitive ); + + for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ ) + { + Q_sprintf( temp, "%s%s", basepath, dirlist.strings[dirlistindex] ); + + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + stringlistappend( &resultlist, temp ); + } + } + + stringlistfreecontents( &dirlist ); + } + } + + if( resultlist.numstrings ) + { + stringlistsort( &resultlist ); + numfiles = resultlist.numstrings; + numchars = 0; + + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + numchars += (int)Q_strlen( resultlist.strings[resultlistindex]) + 1; + search = Mem_Calloc( fs_mempool, sizeof(search_t) + numchars + numfiles * sizeof( char* )); + search->filenames = (char **)((char *)search + sizeof( search_t )); + search->filenamesbuffer = (char *)((char *)search + sizeof( search_t ) + numfiles * sizeof( char* )); + search->numfilenames = (int)numfiles; + numfiles = numchars = 0; + + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + size_t textlen; + + search->filenames[numfiles] = search->filenamesbuffer + numchars; + textlen = Q_strlen(resultlist.strings[resultlistindex]) + 1; + memcpy( search->filenames[numfiles], resultlist.strings[resultlistindex], textlen ); + numfiles++; + numchars += (int)textlen; + } + } + + stringlistfreecontents( &resultlist ); + + Mem_Free( basepath ); + + return search; +} + +void FS_InitMemory( void ) +{ + fs_mempool = Mem_AllocPool( "FileSystem Pool" ); + fs_searchpaths = NULL; +} + +/* +============================================================================= + +WADSYSTEM PRIVATE ROUTINES + +============================================================================= +*/ +// associate extension with wad type +static const wadtype_t wad_types[7] = +{ +{ "pal", TYP_PALETTE }, // palette +{ "dds", TYP_DDSTEX }, // DDS image +{ "lmp", TYP_GFXPIC }, // quake1, hl pic +{ "fnt", TYP_QFONT }, // hl qfonts +{ "mip", TYP_MIPTEX }, // hl/q1 mip +{ "txt", TYP_SCRIPT }, // scripts +{ NULL, TYP_NONE } +}; + +/* +=========== +W_TypeFromExt + +Extracts file type from extension +=========== +*/ +static signed char W_TypeFromExt( const char *lumpname ) +{ + const char *ext = COM_FileExtension( lumpname ); + const wadtype_t *type; + + // we not known about filetype, so match only by filename + if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) + return TYP_ANY; + + for( type = wad_types; type->ext; type++ ) + { + if( !Q_stricmp( ext, type->ext )) + return type->type; + } + return TYP_NONE; +} + +/* +=========== +W_ExtFromType + +Convert type to extension +=========== +*/ +static const char *W_ExtFromType( signed char lumptype ) +{ + const wadtype_t *type; + + // we not known aboyt filetype, so match only by filename + if( lumptype == TYP_NONE || lumptype == TYP_ANY ) + return ""; + + for( type = wad_types; type->ext; type++ ) + { + if( lumptype == type->type ) + return type->ext; + } + return ""; +} + +/* +=========== +W_FindLump + +Serach for already existed lump +=========== +*/ +static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) +{ + int left, right; + + if( !wad || !wad->lumps || matchtype == TYP_NONE ) + return NULL; + + // look for the file (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = (left + right) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) + return &wad->lumps[middle]; // found + else if( wad->lumps[middle].type < matchtype ) + diff = 1; + else if( wad->lumps[middle].type > matchtype ) + diff = -1; + else break; // not found + } + + // if we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + return NULL; +} + +/* +==================== +W_AddFileToWad + +Add a file to the list of files contained into a package +and sort LAT in alpha-bethical order +==================== +*/ +static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) +{ + int left, right; + dlumpinfo_t *plump; + + // look for the slot we should put that file into (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = ( left + right ) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if( wad->lumps[middle].type < newlump->type ) + diff = 1; + else if( wad->lumps[middle].type > newlump->type ) + diff = -1; + else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); + } + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // we have to move the right of the list by one slot to free the one we need + plump = &wad->lumps[left]; + memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); + wad->numlumps++; + + *plump = *newlump; + memcpy( plump->name, name, sizeof( plump->name )); + + return plump; +} + +/* +=========== +W_ReadLump + +reading lump into temp buffer +=========== +*/ +byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) +{ + size_t oldpos, size = 0; + byte *buf; + + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + // no wads loaded + if( !wad || !lump ) return NULL; + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); + size = FS_Read( wad->handle, buf, lump->disksize ); + + if( size < lump->disksize ) + { + Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + Mem_Free( buf ); + return NULL; + } + + if( lumpsizeptr ) *lumpsizeptr = lump->disksize; + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + return buf; +} + +/* +============================================================================= + +WADSYSTEM PUBLIC BASE FUNCTIONS + +============================================================================= +*/ +/* +=========== +W_Open + +open the wad for reading & writing +=========== +*/ +wfile_t *W_Open( const char *filename, int *error ) +{ + wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); + const char *basename; + int i, lumpcount; + dlumpinfo_t *srclumps; + size_t lat_size; + dwadinfo_t header; + + // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) + if( fs_ext_path ) basename = filename; + else basename = COM_FileWithoutPath( filename ); + + wad->handle = FS_Open( basename, "rb", false ); + + // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready + if( host.rodir[0] && fs_ext_path && wad->handle == NULL ) + wad->handle = FS_SysOpen( filename, "rb" ); + + if( wad->handle == NULL ) + { + Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); + if( error ) *error = WAD_LOAD_COULDNT_OPEN; + W_Close( wad ); + return NULL; + } + + // copy wad name + Q_strncpy( wad->filename, filename, sizeof( wad->filename )); + wad->filetime = FS_SysFileTime( filename ); + wad->mempool = Mem_AllocPool( filename ); + + if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + { + Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + W_Close( wad ); + return NULL; + } + + if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) + { + Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + W_Close( wad ); + return NULL; + } + + lumpcount = header.numlumps; + + if( lumpcount >= MAX_FILES_IN_WAD ) + { + Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); + if( error ) *error = WAD_LOAD_TOO_MANY_FILES; + } + else if( lumpcount <= 0 ) + { + Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); + if( error ) *error = WAD_LOAD_NO_FILES; + W_Close( wad ); + return NULL; + } + else if( error ) *error = WAD_LOAD_OK; + + wad->infotableofs = header.infotableofs; // save infotableofs position + + if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); + if( error ) *error = WAD_LOAD_BAD_FOLDERS; + W_Close( wad ); + return NULL; + } + + lat_size = lumpcount * sizeof( dlumpinfo_t ); + + // NOTE: lumps table can be reallocated for O_APPEND mode + srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); + + if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) + { + Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); + if( error ) *error = WAD_LOAD_CORRUPTED; + Mem_Free( srclumps ); + W_Close( wad ); + return NULL; + } + + // starting to add lumps + wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); + wad->numlumps = 0; + + // sort lumps for binary search + for( i = 0; i < lumpcount; i++ ) + { + char name[16]; + int k; + + // cleanup lumpname + Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); + + // check for '*' symbol issues (quake1) + k = Q_strlen( Q_strrchr( name, '*' )); + if( k ) name[Q_strlen( name ) - k] = '!'; + + // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) + if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) + srclumps[i].type = TYP_GFXPIC; + + W_AddFileToWad( name, wad, &srclumps[i] ); + } + + // release source lumps + Mem_Free( srclumps ); + + // and leave the file open + return wad; +} + +/* +=========== +W_Close + +finalize wad or just close +=========== +*/ +void W_Close( wfile_t *wad ) +{ + if( !wad ) return; + + Mem_FreePool( &wad->mempool ); + if( wad->handle != NULL ) + FS_Close( wad->handle ); + Mem_Free( wad ); // free himself +} + +/* +============================================================================= + +FILESYSTEM IMPLEMENTATION + +============================================================================= +*/ +/* +=========== +W_LoadFile + +loading lump into the tmp buffer +=========== +*/ +static byte *W_LoadFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + + search = FS_FindFile( path, &index, gamedironly ); + if( search && search->wad ) + return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); + return NULL; +} diff --git a/engine/common/host.c b/engine/common/host.c index 6b10ec107..975b3ee30 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -1064,10 +1064,10 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha // init host state machine COM_InitHostState(); - +#ifdef XASH_HASHED_VARS // init hashed commands BaseCmd_Init(); - +#endif // startup cmds and cvars subsystem Cmd_Init(); Cvar_Init(); @@ -1110,6 +1110,8 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha Sys_Error( "couldn't find xash3d data directory" ); host.rootdir[0] = 0; } +#elif XASH_PSP + COM_ExtractFilePath( argv[0], host.rootdir ); #elif (XASH_SDL == 2) && !XASH_NSWITCH // GetBasePath not impl'd in switch-sdl2 char *szBasePath = SDL_GetBasePath(); if( szBasePath ) diff --git a/engine/common/imagelib/img_tga.c b/engine/common/imagelib/img_tga.c index 59228bdf2..f95425b6d 100644 --- a/engine/common/imagelib/img_tga.c +++ b/engine/common/imagelib/img_tga.c @@ -24,7 +24,7 @@ Image_LoadTGA */ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesize ) { - int i, columns, rows, row_inc, row, col; + int i, columns, rows, row_inc, row, col, bpp = 1; byte *buf_p, *pixbuf, *targa_rgba; rgba_t palette[256]; byte red = 0, green = 0, blue = 0, alpha = 0; @@ -32,6 +32,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi int reflectivity[3] = { 0, 0, 0 }; qboolean compressed; tga_t targa_header; + int palIndex = 0; if( filesize < sizeof( tga_t )) return false; @@ -55,8 +56,6 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi // check for tga file if( !Image_ValidSize( name )) return false; - image.type = PF_RGBA_32; // always exctracted to 32-bit buffer - if( targa_header.image_type == 1 || targa_header.image_type == 9 ) { // uncompressed colormapped image @@ -119,11 +118,35 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi return false; } } + else + { + Con_DPrintf( S_ERROR "Image_LoadTGA: (%s) Type %i not supported\n", name, targa_header.image_type ); + return false; + } columns = targa_header.width; rows = targa_header.height; - image.size = image.width * image.height * 4; + if( Image_CheckFlag( IL_KEEP_8BIT ) && ( targa_header.image_type == 1 || targa_header.image_type == 9 )) + { + pixbuf = image.palette = Mem_Malloc( host.imagepool, 1024 ); + for( i = 0; i < targa_header.colormap_length; i++ ) + { + *pixbuf++ = palette[i][0]; + *pixbuf++ = palette[i][1]; + *pixbuf++ = palette[i][2]; + *pixbuf++ = palette[i][3]; + } + image.type = PF_INDEXED_32; + } + else + { + image.palette = NULL; + image.type = PF_RGBA_32; + bpp = 4; + } + + image.size = image.width * image.height * bpp; targa_rgba = image.rgba = Mem_Malloc( host.imagepool, image.size ); // if bit 5 of attributes isn't set, the image has been stored from bottom to top @@ -134,8 +157,8 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi } else { - pixbuf = targa_rgba + ( rows - 1 ) * columns * 4; - row_inc = -columns * 4 * 2; + pixbuf = targa_rgba + ( rows - 1 ) * columns * bpp; + row_inc = -columns * bpp * 2; } compressed = ( targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11 ); @@ -161,13 +184,13 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi case 1: case 9: // colormapped image - blue = *buf_p++; - if( blue < targa_header.colormap_length ) + palIndex = *buf_p++; + if( palIndex < targa_header.colormap_length ) { - red = palette[blue][0]; - green = palette[blue][1]; - alpha = palette[blue][3]; - blue = palette[blue][2]; + red = palette[palIndex][0]; + green = palette[palIndex][1]; + blue = palette[palIndex][2]; + alpha = palette[palIndex][3]; if( alpha != 255 ) image.flags |= IMAGE_HAS_ALPHA; } break; @@ -208,10 +231,18 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi reflectivity[1] += green; reflectivity[2] += blue; - *pixbuf++ = red; - *pixbuf++ = green; - *pixbuf++ = blue; - *pixbuf++ = alpha; + if( image.type == PF_INDEXED_32 || image.type == PF_INDEXED_24 ) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + if( ++col == columns ) { // run spans across rows @@ -223,6 +254,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi } VectorDivide( reflectivity, ( image.width * image.height ), image.fogParams ); + if( image.palette ) Image_GetPaletteBMP( image.palette ); image.depth = 1; return true; diff --git a/engine/common/imagelib/img_utils.c b/engine/common/imagelib/img_utils.c index 3fa0a43f2..73e49d2e5 100644 --- a/engine/common/imagelib/img_utils.c +++ b/engine/common/imagelib/img_utils.c @@ -93,6 +93,23 @@ static const loadpixformat_t load_null[] = { NULL, NULL, NULL, IL_HINT_NO } }; +// a1ba: that's weird, better debug +#if XASH_PSP +static const loadpixformat_t load_game[] = +{ +{ "%s%s.%s", "mip", Image_LoadMIP, IL_HINT_NO }, // hl textures from wad or buffer +{ "%s%s.%s", "mdl", Image_LoadMDL, IL_HINT_HL }, // hl studio model skins +{ "%s%s.%s", "spr", Image_LoadSPR, IL_HINT_HL }, // hl sprite frames +{ "%s%s.%s", "lmp", Image_LoadLMP, IL_HINT_NO }, // hl menu images (cached.wad etc) +{ "%s%s.%s", "fnt", Image_LoadFNT, IL_HINT_HL }, // hl console font (fonts.wad etc) +{ "%s%s.%s", "pal", Image_LoadPAL, IL_HINT_NO }, // install studio\sprite palette +{ "%s%s.%s", "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models +{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus +{ "%s%s.%s", "bmp", Image_LoadBMP, IL_HINT_NO }, // WON menu images +{ "%s%s.%s", "png", Image_LoadPNG, IL_HINT_NO }, // NightFire 007 menus +{ NULL, NULL, NULL, IL_HINT_NO } +}; +#else static const loadpixformat_t load_game[] = { { "%s%s.%s", "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models @@ -108,7 +125,7 @@ static const loadpixformat_t load_game[] = { "%s%s.%s", "ktx2", Image_LoadKTX2, IL_HINT_NO }, // ktx2 for world and studio models { NULL, NULL, NULL, IL_HINT_NO } }; - +#endif /* ============================================================================= diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 691f80b47..415537546 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -34,7 +34,13 @@ GNU General Public License for more details. static char szGameDir[128]; // safe place to keep gamedir static int szArgc; + +#if XASH_PSP +#define MAX_NARGVS 50 +static char *szArgv[MAX_NARGVS]; +#else static char **szArgv; +#endif static void Sys_ChangeGame( const char *progname ) { @@ -71,20 +77,23 @@ static int Sys_Start( void ) #endif #elif XASH_IOS IOS_LaunchDialog(); -#endif +#elif XASH_PSP return Host_Main( szArgc, szArgv, game, 0, Sys_ChangeGame ); } int main( int argc, char **argv ) { -#if XASH_PSVITA +#if XASH_PSP + Platform_ReadCmd( "start.cmd", &szArgc, szArgv ); +#elif XASH_PSVITA // inject -dev -console into args if required szArgc = PSVita_GetArgv( argc, argv, &szArgv ); #else szArgc = argc; szArgv = argv; #endif // XASH_PSVITA + return Sys_Start(); } #endif // XASH_ENABLE_MAIN diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index 5b3673cb9..3b24d042c 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -38,6 +38,7 @@ void COM_PushLibraryError( const char *error ) Q_strncat( s_szLastError, error, sizeof( s_szLastError ) ); } +#if !XASH_PSP void *COM_FunctionFromName_SR( void *hInstance, const char *pName ) { char **funcs = NULL; @@ -85,6 +86,7 @@ const char *COM_OffsetNameForFunction( void *function ) Con_Reportf( "COM_OffsetNameForFunction %s\n", sname ); return sname; } +#endif dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) { @@ -128,7 +130,7 @@ dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) static void COM_GenerateCommonLibraryName( const char *name, const char *ext, char *out, size_t size ) { -#if ( XASH_WIN32 || XASH_LINUX || XASH_APPLE ) && XASH_X86 +#if (( XASH_WIN32 || XASH_LINUX || XASH_APPLE ) && XASH_X86) || XASH_PSP Q_snprintf( out, size, "%s.%s", name, ext ); #elif ( XASH_WIN32 || XASH_LINUX || XASH_APPLE ) Q_snprintf( out, size, "%s_%s.%s", name, Q_buildarch(), ext ); @@ -191,6 +193,8 @@ static void COM_GenerateServerLibraryPath( char *out, size_t size ) Q_strncpy( out, GI->game_dll, size ); #elif XASH_APPLE Q_strncpy( out, GI->game_dll_osx, size ); +#elif XASH_PSP + Q_strncpy( out, GI->game_dll_psp, size ); #else // XASH_LINUX Q_strncpy( out, GI->game_dll_linux, size ); #endif @@ -203,6 +207,8 @@ static void COM_GenerateServerLibraryPath( char *out, size_t size ) Q_strncpy( dllpath, GI->game_dll, sizeof( dllpath ) ); #elif XASH_APPLE Q_strncpy( dllpath, GI->game_dll_osx, sizeof( dllpath ) ); +#elif XASH_PSP + Q_strncpy( dllpath, GI->game_dll_psp, sizeof( dllpath ) ); #else // XASH_APPLE Q_strncpy( dllpath, GI->game_dll_linux, sizeof( dllpath ) ); #endif diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 2adf38ae1..9e64976ee 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -1735,12 +1735,16 @@ static void Mod_LoadSubmodels( model_t *mod, dbspmodel_t *bmod ) oldmaxfaces = Q_max( oldmaxfaces, out->numfaces ); } +#if XASH_PSP + refState.max_surfaces = 0; // sorting disabled +#else // these array used to sort translucent faces in bmodels if( oldmaxfaces > refState.max_surfaces ) { refState.draw_surfaces = (sortedface_t *)Z_Realloc( refState.draw_surfaces, oldmaxfaces * sizeof( sortedface_t )); refState.max_surfaces = oldmaxfaces; } +#endif } /* diff --git a/engine/common/net_buffer.c b/engine/common/net_buffer.c index c219e8c79..675151fdf 100644 --- a/engine/common/net_buffer.c +++ b/engine/common/net_buffer.c @@ -151,17 +151,6 @@ void MSG_Clear( sizebuf_t *sb ) sb->bOverflow = false; } -static qboolean MSG_Overflow( sizebuf_t *sb, int nBits ) -{ - if( sb->iCurBit + nBits > sb->nDataBits ) - sb->bOverflow = true; - return sb->bOverflow; -} - -qboolean MSG_CheckOverflow( sizebuf_t *sb ) -{ - return MSG_Overflow( sb, 0 ); -} int MSG_SeekToBit( sizebuf_t *sb, int bitPos, int whence ) { @@ -193,17 +182,6 @@ void MSG_SeekToByte( sizebuf_t *sb, int bytePos ) sb->iCurBit = bytePos << 3; } -void MSG_WriteOneBit( sizebuf_t *sb, int nValue ) -{ - if( !MSG_Overflow( sb, 1 )) - { - if( nValue ) sb->pData[sb->iCurBit>>3] |= BIT( sb->iCurBit & 7 ); - else sb->pData[sb->iCurBit>>3] &= ~BIT( sb->iCurBit & 7 ); - - sb->iCurBit++; - } -} - void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits ) { Assert( numbits >= 0 && numbits <= 32 ); diff --git a/engine/common/net_buffer.h b/engine/common/net_buffer.h index f31b32c6c..f28c6bb25 100644 --- a/engine/common/net_buffer.h +++ b/engine/common/net_buffer.h @@ -64,6 +64,19 @@ _inline int MSG_TellBit( sizebuf_t *sb ) { return sb->iCurBit; } _inline const char *MSG_GetName( sizebuf_t *sb ) { return sb->pDebugName; } qboolean MSG_CheckOverflow( sizebuf_t *sb ); +static inline qboolean MSG_Overflow( sizebuf_t *sb, int nBits ) +{ + if( sb->iCurBit + nBits > sb->nDataBits ) + sb->bOverflow = true; + return sb->bOverflow; +} + +static inline MSG_CheckOverflow( sizebuf_t *sb ) +{ + return MSG_Overflow( sb, 0 ); +} + + #if XASH_BIG_ENDIAN #define MSG_BigShort( x ) ( x ) #else @@ -78,7 +91,16 @@ void MSG_StartWriting( sizebuf_t *sb, void *pData, int nBytes, int iStartBit, in void MSG_Clear( sizebuf_t *sb ); // Bit-write functions -void MSG_WriteOneBit( sizebuf_t *sb, int nValue ); +static inline void MSG_WriteOneBit( sizebuf_t *sb, int nValue ) +{ + if( likely( !MSG_Overflow( sb, 1 ))) + { + if( nValue ) sb->pData[sb->iCurBit>>3] |= BIT( sb->iCurBit & 7 ); + else sb->pData[sb->iCurBit>>3] &= ~BIT( sb->iCurBit & 7 ); + + sb->iCurBit++; + } +} void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits ); void MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits ); void MSG_WriteBitLong( sizebuf_t *sb, uint data, int numbits, qboolean bSigned ); diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index 42b8b4daf..a5070b94f 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -1095,7 +1095,6 @@ int Delta_TestBaseline( entity_state_t *from, entity_state_t *to, qboolean playe if( from == NULL ) return 0; return countBits; } - if( FBitSet( to->entityType, ENTITY_BEAM )) dt = Delta_FindStructByIndex( DT_CUSTOM_ENTITY_STATE_T ); else if( player ) @@ -1793,7 +1792,6 @@ void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t * numChanges++; } else MSG_WriteOneBit( msg, 0 ); - if( FBitSet( to->entityType, ENTITY_BEAM )) { dt = Delta_FindStructByIndex( DT_CUSTOM_ENTITY_STATE_T ); @@ -1806,7 +1804,6 @@ void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t * { dt = Delta_FindStructByIndex( DT_ENTITY_STATE_T ); } - Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1924,7 +1921,6 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state { dt = Delta_FindStructByIndex( DT_ENTITY_STATE_T ); } - Assert( dt && dt->bInitialized ); pField = dt->pFields; diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index de05146a3..c831c8279 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -356,7 +356,7 @@ qboolean NET_GetHostByName( const char *hostname, int family, struct sockaddr_st #endif } -#if !XASH_EMSCRIPTEN && !XASH_DOS4GW && !defined XASH_NO_ASYNC_NS_RESOLVE +#if !XASH_EMSCRIPTEN && !XASH_DOS4GW && !XASH_PSP && !defined XASH_NO_ASYNC_NS_RESOLVE #define CAN_ASYNC_NS_RESOLVE #endif // !XASH_EMSCRIPTEN && !XASH_DOS4GW && !defined XASH_NO_ASYNC_NS_RESOLVE diff --git a/engine/common/netchan.h b/engine/common/netchan.h index fd69fd7a2..4f80f2cc9 100644 --- a/engine/common/netchan.h +++ b/engine/common/netchan.h @@ -86,7 +86,18 @@ GNU General Public License for more details. #define NETSPLIT_BACKUP 8 #define NETSPLIT_BACKUP_MASK (NETSPLIT_BACKUP - 1) #define NETSPLIT_HEADER_SIZE 18 - +#if XASH_PSP + #undef MULTIPLAYER_BACKUP + #undef SINGLEPLAYER_BACKUP + #undef NUM_PACKET_ENTITIES + #undef MAX_CUSTOM_BASELINES + #undef NET_MAX_FRAGMENT + #define MULTIPLAYER_BACKUP 16 + #define SINGLEPLAYER_BACKUP 16 + #define NUM_PACKET_ENTITIES 32 + #define MAX_CUSTOM_BASELINES 8 + #define NET_MAX_FRAGMENT 32768 +#else #if XASH_LOW_MEMORY == 2 #undef MULTIPLAYER_BACKUP #undef SINGLEPLAYER_BACKUP @@ -108,7 +119,7 @@ GNU General Public License for more details. #define MAX_CUSTOM_BASELINES 8 #define NET_MAX_FRAGMENT 32768 #endif - +#endif /* XASH_PSP */ typedef struct netsplit_chain_packet_s { // bool vector diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 4cd64bad5..9b87261f6 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -177,6 +177,29 @@ GNU General Public License for more details. #define FRAGMENT_MAX_SIZE 64000 // maximal fragment size #define FRAGMENT_LOCAL_SIZE FRAGMENT_MAX_SIZE // local connection +#if XASH_PSP + +#undef MAX_VISIBLE_PACKET +#undef MAX_VISIBLE_PACKET_VIS_BYTES +#undef MAX_EVENTS +#undef MAX_SUPPORTED_MODELS +#undef MAX_MODELS +#undef MAX_SOUNDS +#undef MAX_CUSTOM +#undef MAX_RENDER_DECALS +#undef MAX_RESOURCES + +#define MAX_VISIBLE_PACKET 256 +#define MAX_VISIBLE_PACKET_VIS_BYTES ((MAX_VISIBLE_PACKET + 7) / 8) +#define MAX_EVENTS 256 +#define MAX_SUPPORTED_MODELS 512 +#define MAX_MODELS MAX_SUPPORTED_MODELS +#define MAX_SOUNDS 512 +#define MAX_CUSTOM 32 +#define MAX_RENDER_DECALS 256 +#define MAX_RESOURCES (MAX_MODELS+MAX_SOUNDS+MAX_CUSTOM+MAX_EVENTS) + +#else #if XASH_LOW_MEMORY == 2 #undef MAX_VISIBLE_PACKET #undef MAX_VISIBLE_PACKET_VIS_BYTES @@ -215,7 +238,7 @@ GNU General Public License for more details. #define MAX_RENDER_DECALS 128 #define MAX_RESOURCES 1024 #endif - +#endif /* XASH_PSP */ // Quake1 Protocol #define PROTOCOL_VERSION_QUAKE 15 diff --git a/engine/common/soundlib/snd_main.c b/engine/common/soundlib/snd_main.c index dee4c2000..ee5dcfabc 100644 --- a/engine/common/soundlib/snd_main.c +++ b/engine/common/soundlib/snd_main.c @@ -62,7 +62,7 @@ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ) fs_offset_t filesize = 0; const loadwavfmt_t *format; byte *f; - + Sound_Reset(); // clear old sounddata Q_strncpy( loadname, filename, sizeof( loadname )); diff --git a/engine/common/soundlib/snd_mp3.c b/engine/common/soundlib/snd_mp3.c index 062a8bdb4..2d716c9c9 100644 --- a/engine/common/soundlib/snd_mp3.c +++ b/engine/common/soundlib/snd_mp3.c @@ -12,8 +12,8 @@ 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. */ - #include "soundlib.h" +#if !XASH_PSP #include "libmpg/libmpg.h" #pragma pack( push, 1 ) @@ -455,3 +455,485 @@ void Stream_FreeMPG( stream_t *stream ) Mem_Free( stream ); } + +#else // XASH_PSP +#include "platform/psp/scemp3/pspmp3.h" + +/* +================================================================= + + PSP MPEG decoding + +================================================================= +*/ +#define MP3_ID3V1_ID "TAG" +#define MP3_ID3V1_ID_SZ 3 +#define MP3_ID3V1_SZ 128 + +#define MP3_ID3V2_ID "ID3" +#define MP3_ID3V2_ID_SZ 3 +#define MP3_ID3V2_SIZE_OFF 6 +#define MP3_ID3V2_SIZE_SZ 4 +#define MP3_ID3V2_HEADER_SZ 10 + +#define MP3_ERRORS_MAX 3 + +#define PCM_WIDTH 2 // always 16-bit PCM +#define PCM_CHANNELS 2 // always 2 + +#define MP3_BUFFER_SIZE ( 128 * 1024 ) +#define PCM_BUFFER_SIZE (( 1152 * PCM_WIDTH * PCM_CHANNELS ) * 2 ) // framesample * width * channels * 2(double buffer) + +typedef struct +{ + int handle; + int cachePos; + int frameCount; + byte *pcmTempPtr; +} mp3_decoder_t; + +/* +================= +Sound_GetID3V2SizeMPG +================= +*/ +__inline int Sound_GetID3V2SizeMPG( const byte *tagSize ) +{ + int size; + byte *sizePtr = (byte*)&size; + + // 7 bit per byte, invert endian + sizePtr[0] = (( tagSize[3] >> 0 ) & 0x7F ) | (( tagSize[2] & 0x01 ) << 7 ); + sizePtr[1] = (( tagSize[2] >> 1 ) & 0x3F ) | (( tagSize[1] & 0x03 ) << 6 ); + sizePtr[2] = (( tagSize[1] >> 2 ) & 0x1F ) | (( tagSize[0] & 0x07 ) << 5 ); + sizePtr[3] = (( tagSize[0] >> 3 ) & 0x0F ); + + return size; +} + +#if 0 +/* +================= +Sound_FindHeadMPG +================= +*/ +fs_offset_t Sound_FindHeadMPG( const byte *buffer, fs_offset_t filesize ) +{ + fs_offset_t result; + + result = 0; + if( filesize >= MP3_ID3V2_HEADER_SZ ) + { + if( !memcmp( buffer, MP3_ID3V2_ID, MP3_ID3V2_ID_SZ )) + result = Sound_GetID3V2SizeMPG( &buffer[MP3_ID3V2_SIZE_OFF] ) + MP3_ID3V2_HEADER_SZ; + } + return result; +} + +/* +================= +Sound_FindTailMPG +================= +*/ +fs_offset_t Sound_FindTailMPG( const byte *buffer, fs_offset_t filesize ) +{ + fs_offset_t result; + + result = filesize; + if( filesize >= MP3_ID3V1_SZ ) + { + if( !memcmp( &buffer[filesize - MP3_ID3V1_SZ], MP3_ID3V1_ID, MP3_ID3V1_ID_SZ )) + result -= MP3_ID3V1_SZ; + } + return result; +} +#endif + +qboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesize ) +{ +#if 0 + int status; + int handle; + size_t bytesWrite = 0; + byte out[PCM_BUFFER_SIZE] __attribute__((aligned(64))); + fs_offset_t headOffset; + fs_offset_t tailOffset; + fs_offset_t contentSize; + int frameSample; + uint bps; + int frameLen; + int frameSize; + int frameCount; + + headOffset = Sound_FindHeadMPG( buffer, filesize ); + tailOffset = Sound_FindTailMPG( buffer, filesize ); + contentSize = tailOffset - headOffset; + // TODO: read Xing header + + handle = sceMp3ReserveMp3Handle( NULL ); + if ( handle < 0 ) + { + Con_DPrintf( S_ERROR "sceMp3ReserveMp3Handle returned 0x%08X\n", handle ); + return false; + } + + status = sceMp3LowLevelInit( handle, &buffer[headOffset] ); + if ( status < 0 ) + { + Con_DPrintf( S_ERROR "sceMp3LowLevelInit returned 0x%08X\n", status ); + return false; + } + + frameSample = sceMp3GetMaxOutputSample( handle ); + frameSize = (( frameSample / 8 ) * sceMp3GetBitRate( handle ) * 1000 ) / sceMp3GetSamplingRate( handle ); + frameCount = contentSize / frameSize; + + sound.channels = sceMp3GetMp3ChannelNum( handle ); + sound.rate = sceMp3GetSamplingRate( handle ); + sound.width = PCM_WIDTH; // always 16-bit PCM + sound.loopstart = -1; + sound.size = (frameCount * frameSample) * sound.channels * sound.width; // invalid for VBR +/* + status = sceMp3LowLevelDecode(handle, &buffer[headOffset + mp3usedsize], &mp3usedsize, pcm, &pcmoutsize); + if(status < 0) + { + Con_DPrintf( S_ERROR "sceMp3LowLevelDecode returned 0x%08X\n", status); + } + sceMp3ReleaseMp3Handle( handle ); +*/ + return true; +#else + Con_DPrintf( S_ERROR "Sound_LoadMPG unimplemented function!\n"); + return false; +#endif +} + +/* +================= +Stream_FindHeadMPG +================= +*/ +SceOff Stream_FindHeadMPG( file_t *file ) +{ + SceOff result; + byte tagId[MP3_ID3V2_ID_SZ]; + byte tagSize[MP3_ID3V2_SIZE_SZ]; + + result = 0; + + FS_Seek( file, 0, SEEK_SET ); + + if( FS_Read( file, tagId, MP3_ID3V2_ID_SZ) < MP3_ID3V2_ID_SZ ) + { + Con_DPrintf( S_ERROR "Sound_Mp3FindHead: ID3V2 ID read error\n"); + return result; + } + + if( !memcmp( tagId, MP3_ID3V2_ID, MP3_ID3V2_ID_SZ )) + { + FS_Seek( file, MP3_ID3V2_SIZE_OFF, SEEK_SET ); + if( FS_Read( file, tagSize, MP3_ID3V2_SIZE_SZ ) < MP3_ID3V2_SIZE_SZ ) + Con_DPrintf( S_ERROR "Sound_Mp3FindHead: ID3V2 SIZE read error\n"); + else result = Sound_GetID3V2SizeMPG( tagSize ) + MP3_ID3V2_HEADER_SZ; + } + + return result; +} + +/* +================= +Stream_FindTailMPG +================= +*/ +SceOff Stream_FindTailMPG( file_t *file ) +{ + SceOff result; + byte tagId[MP3_ID3V1_ID_SZ]; + + result = FS_FileLength( file ); + + FS_Seek( file, result - MP3_ID3V1_SZ, SEEK_SET ); + + if( FS_Read( file, tagId, MP3_ID3V1_ID_SZ ) < MP3_ID3V1_ID_SZ ) + { + Con_DPrintf( S_ERROR "Sound_Mp3FindTail: ID3V1 ID read error\n"); + return result; + } + + if( !memcmp( tagId, MP3_ID3V1_ID, MP3_ID3V1_ID_SZ )) + result -= MP3_ID3V1_SZ; + + return result; +} + +/* +================= +Stream_FillBufferMPG +================= +*/ +int Stream_FillBufferMPG( file_t *file, mp3_decoder_t *desc ) +{ + int status; + byte *dstPtr; + int dstSize; + int dstPos; + fs_offset_t readSize; + + if( sceMp3CheckStreamDataNeeded( desc->handle ) <= 0 ) + return 0; + + // get Info on the stream (where to fill to, how much to fill, where to fill from) + status = sceMp3GetInfoToAddStreamData( desc->handle, &dstPtr, &dstSize, &dstPos ); + if( status < 0 ) + { + Con_DPrintf( S_ERROR "sceMp3GetInfoToAddStreamData returned 0x%08X\n", status); + return status; + } + + // seek file to position requested + if( desc->cachePos != dstPos ) + { + FS_Seek( file, dstPos, SEEK_SET ); + desc->cachePos = dstPos; + } + + // read the amount of data + readSize = FS_Read( file, dstPtr, dstSize ); + desc->cachePos += ( int )readSize; + + // notify mp3 library about how much we really wrote to the stream buffer + status = sceMp3NotifyAddStreamData( desc->handle, ( int )readSize ); + if ( status < 0 ) + Con_DPrintf( S_ERROR "sceMp3NotifyAddStreamData returned 0x%08X\n", status); + + return status; +} + +/* +================= +Stream_OpenMPG +================= +*/ +stream_t *Stream_OpenMPG( const char *filename ) +{ + stream_t *stream; + mp3_decoder_t *desc; + file_t *file; + int status; + void *bufferBase; + + file = FS_Open( filename, "rbh", false ); // hold mode + if( !file ) return NULL; + + // at this point we have valid stream + stream = Mem_Calloc( host.soundpool, sizeof( stream_t )); + stream->file = file; + stream->pos = 0; + + desc = Mem_Calloc( host.soundpool, sizeof( mp3_decoder_t ) + MP3_BUFFER_SIZE + PCM_BUFFER_SIZE + 63 ); + bufferBase = (void *)(((( uintptr_t )desc + sizeof( mp3_decoder_t )) & (~( 64 - 1 ))) + 64 ); + + // reserve a mp3 handle + SceMp3InitArg mp3Init; + mp3Init.mp3StreamStart = Stream_FindHeadMPG( file ); + mp3Init.mp3StreamEnd = Stream_FindTailMPG( file ); + mp3Init.mp3Buf = bufferBase; + mp3Init.mp3BufSize = MP3_BUFFER_SIZE; + mp3Init.pcmBuf = (void *)(( uintptr_t )bufferBase + MP3_BUFFER_SIZE ); + mp3Init.pcmBufSize = PCM_BUFFER_SIZE; + + desc->cachePos = 0; + desc->pcmTempPtr = mp3Init.pcmBuf; + desc->handle = sceMp3ReserveMp3Handle( &mp3Init ); + if ( desc->handle < 0 ) + { + Con_DPrintf( S_ERROR "sceMp3ReserveMp3Handle returned 0x%08X\n", desc->handle ); + Mem_Free( stream ); + Mem_Free( desc ); + FS_Close( file ); + return NULL; + } + + // Fill the stream buffer with some data so that sceMp3Init has something to work with + if( Stream_FillBufferMPG( file, desc ) != 0 ) + { + Mem_Free( stream ); + Mem_Free( desc ); + FS_Close( file ); + return NULL; + } + + status = sceMp3Init( desc->handle ); + if ( status < 0 ) + { + Con_DPrintf( S_ERROR "sceMp3Init returned 0x%08X\n", status ); + Mem_Free( stream ); + Mem_Free( desc ); + FS_Close( file ); + return NULL; + } + + desc->frameCount = sceMp3GetFrameNum( desc->handle ); + + stream->buffsize = 0; // how many samples left from previous frame + stream->channels = PCM_CHANNELS; // always 2 PCM channels + stream->rate = sceMp3GetSamplingRate( desc->handle ); + stream->width = PCM_WIDTH; // always 16 bit PCM + stream->ptr = (void *)desc; + stream->type = WF_MPGDATA; + + return stream; +} + +/* +================= +Stream_ReadMPG + +assume stream is valid +================= +*/ +int Stream_ReadMPG( stream_t *stream, int needBytes, void *buffer ) +{ + // buffer handling + int bytesWritten = 0; + mp3_decoder_t *desc; + int errdec; + + desc = ( mp3_decoder_t* )stream->ptr; + + while(1) + { + byte *data; + int outsize; + + if( !stream->buffsize ) + { + // Check if we need to fill our stream buffer + if( Stream_FillBufferMPG( stream->file, desc ) != 0 ) + return 0; + + for( errdec = 0; errdec < MP3_ERRORS_MAX; errdec++ ) + { + stream->pos = sceMp3Decode( desc->handle, (SceShort16**)&desc->pcmTempPtr ); + if(( int )stream->pos >= 0 ) // decoding successful + { + break; + } + else if( stream->pos == 0x80671402 ) // next frame header + { + if( Stream_FillBufferMPG( stream->file, desc ) != 0 ) + return 0; + } + else break; + } + + if(( int )stream->pos < 0 ) + { + if( stream->pos != 0x80671402 ) + Con_DPrintf( S_ERROR "sceMp3Decode returned 0x%08X\n", stream->pos ); + return 0; // ??? + } + } + + // check remaining size + if( bytesWritten + stream->pos > needBytes ) + outsize = ( needBytes - bytesWritten ); + else outsize = stream->pos; + + // copy raw sample to output buffer + data = (byte *)buffer + bytesWritten; + memcpy( data, &desc->pcmTempPtr[stream->buffsize], outsize ); + bytesWritten += outsize; + stream->pos -= outsize; + stream->buffsize += outsize; + + // continue from this sample on a next call + if( bytesWritten >= needBytes ) + return bytesWritten; + + stream->buffsize = 0; // no bytes remaining + } + + return 0; +} + +/* +================= +Stream_SetPosMPG + +assume stream is valid +================= +*/ +int Stream_SetPosMPG( stream_t *stream, int newpos ) +{ + mp3_decoder_t *desc; + int frame; + int status; + + desc = ( mp3_decoder_t* )stream->ptr; + + // get frame num + frame = newpos / sceMp3GetMaxOutputSample( desc->handle ); // VBR? + + if( frame < 1 || frame >= desc->frameCount - 1 ) + return false; + + status = sceMp3ResetPlayPositionByFrame( desc->handle, frame ); + if( status < 0 ) + { + Con_DPrintf( S_ERROR "sceMp3ResetPlayPositionByFrame returned 0x%08X\n", status ); + + // failed to seek for some reasons + return false; + } + + // flush any previous data + stream->buffsize = 0; + + return true; +} + +/* +================= +Stream_GetPosMPG + +assume stream is valid +================= +*/ +int Stream_GetPosMPG( stream_t *stream ) +{ + mp3_decoder_t *desc; + + desc = ( mp3_decoder_t * )stream->ptr; + + return sceMp3GetSumDecodedSample( desc->handle ); +} + +/* +================= +Stream_FreeMPG + +assume stream is valid +================= +*/ +void Stream_FreeMPG( stream_t *stream ) +{ + mp3_decoder_t *desc; + + desc = ( mp3_decoder_t * )stream->ptr; + if( desc ) + { + sceMp3ReleaseMp3Handle( desc->handle ); + Mem_Free( desc ); + stream->ptr = NULL; + } + + if( stream->file ) + { + FS_Close( stream->file ); + stream->file = NULL; + } + + Mem_Free( stream ); +} +#endif // XASH_PSP diff --git a/engine/common/soundlib/snd_wav.c b/engine/common/soundlib/snd_wav.c index 3f6ac5ec4..d31363465 100644 --- a/engine/common/soundlib/snd_wav.c +++ b/engine/common/soundlib/snd_wav.c @@ -331,7 +331,8 @@ stream_t *Stream_OpenWAV( const char *filename ) return NULL; // open - file = FS_Open( filename, "rb", false ); + // h - hold mode + file = FS_Open( filename, "rbh", false ); if( !file ) return NULL; // find "RIFF" chunk diff --git a/engine/common/soundlib/soundlib.h b/engine/common/soundlib/soundlib.h index 9ce2af46b..5f269a3be 100644 --- a/engine/common/soundlib/soundlib.h +++ b/engine/common/soundlib/soundlib.h @@ -74,7 +74,9 @@ struct stream_s // current stream state void *ptr; // internal decoder state +#if !XASH_PSP char temp[OUTBUF_SIZE]; // mpeg decoder stuff +#endif size_t pos; // actual track position (or actual buffer remains) int buffsize; // cached buffer size }; diff --git a/engine/common/system.c b/engine/common/system.c index d977e8dd4..55a8e3b44 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -44,6 +44,11 @@ GNU General Public License for more details. #include #endif +#if XASH_PSP +#include +#include +#endif + #include "menu_int.h" // _UPDATE_PAGE macro #include "library.h" @@ -144,6 +149,10 @@ const char *Sys_GetCurrentUser( void ) if( pw ) return pw->pw_name; +#elif XASH_PSP + static string s_userName; + if( sceUtilityGetSystemParamString( PSP_SYSTEMPARAM_ID_STRING_NICKNAME, s_userName, sizeof( s_userName )) == 0 ) + return s_userName; #endif return "Player"; } @@ -485,7 +494,12 @@ Sys_Quit void Sys_Quit( void ) { Host_Shutdown(); +#if XASH_PSP + sceKernelDelayThread( 50 * 1000 ); + sceKernelExitGame(); +#else exit( error_on_exit ); +#endif } /* diff --git a/engine/platform/platform.h b/engine/platform/platform.h index a69daab33..5621eac41 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -58,6 +58,15 @@ void SDLash_Init( void ); void SDLash_Shutdown( void ); #endif +#if XASH_PSP +void Platform_ReadCmd( const char *fname, int *argc, char **argv ); +SceUID Platform_LoadModule( const char *filename, int mpid, SceSize argsize, void *argp ); +int Platform_UnloadModule( SceUID modid, int *sce_code ); + +#include "psp/p5ram_psp.h" +#include "psp/fsh_psp.h" +#endif + #if XASH_ANDROID const char *Android_GetAndroidID( void ); const char *Android_LoadID( void ); diff --git a/engine/platform/psp/fsh_psp.c b/engine/platform/psp/fsh_psp.c new file mode 100644 index 000000000..a0c00a67f --- /dev/null +++ b/engine/platform/psp/fsh_psp.c @@ -0,0 +1,410 @@ +/* +fsh_psp.c - PSP filesystem helper +Copyright (C) 2022 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include +#include "common.h" +#include "fsh_psp.h" + +#define FSH_MAX_PATH 64 +#define FSH_EMPTY_STRING "*empty*" + +typedef struct fsh_path_s +{ + char path[FSH_MAX_PATH]; + int fsize; + uint hashvalue; + struct fsh_path_s *nexthash; +}fsh_path_t; + +typedef struct fsh_handle_s +{ + qboolean ready; + int count; + char folderpath[PATH_MAX]; + int folderpath_size; + uint empty_hash; + int pathlist_size; + int hashlist_size; + fsh_path_t *pathlist; + fsh_path_t **hashlist; + struct fsh_handle_s *next; +}fsh_handle_t; + +static fsh_handle_t *fsh_poolchain = NULL; + +/* +================ +FSH_AddHash +================ +*/ +_inline void FSH_AddHash( fsh_path_t **hashlist, uint hash, fsh_path_t *fptr ) +{ + fptr->hashvalue = hash; + fptr->nexthash = hashlist[hash]; + hashlist[hash] = fptr; +} + +/* +================ +FSH_RemoveHash +================ +*/ +_inline void FSH_RemoveHash( fsh_path_t **hashlist, uint hash, fsh_path_t *fptr ) +{ + fsh_path_t **fptr_prev; + + for( fptr_prev = &hashlist[hash]; *fptr_prev != NULL; fptr_prev = &( *fptr_prev )->nexthash ) + { + if( *fptr_prev == fptr ) + { + *fptr_prev = fptr->nexthash; + break; + } + } +} + +/* +================ +FSH_AddFilePath +================ +*/ +int FSH_AddFilePathWs( fsh_handle_t *handle, const char *path, int size ) +{ + uint hash; + fsh_path_t *fptr; + const char *strip_path; + + if( !handle ) + return -2; + + if( Q_strnicmp( path, handle->folderpath, handle->folderpath_size )) + return -2; + + strip_path = path + handle->folderpath_size + 1; // + '/' + + hash = COM_HashKey( strip_path, handle->hashlist_size ); + + if( handle->ready && handle->count > 0 ) + { + // see if already added + for( fptr = handle->hashlist[hash]; fptr != NULL; fptr = fptr->nexthash ) + { + if( !Q_stricmp( fptr->path, strip_path )) + return fptr - handle->pathlist; // index + } + + // find empty + for( fptr = handle->hashlist[handle->empty_hash]; fptr != NULL; fptr = fptr->nexthash ) + { + if( !Q_stricmp( fptr->path, FSH_EMPTY_STRING )) + { + Q_strncpy( fptr->path, strip_path, FSH_MAX_PATH - 1 ); + + // file size + fptr->fsize = size; + + // remove empty from hash table + FSH_RemoveHash( handle->hashlist, handle->empty_hash, fptr ); + + // add to hash table + FSH_AddHash( handle->hashlist, hash, fptr ); + + return fptr - handle->pathlist; // index + } + } + } + + // create new + if( handle->count + 1 >= handle->pathlist_size ) + return -1; + + fptr = &handle->pathlist[handle->count]; + Q_strncpy( fptr->path, strip_path, FSH_MAX_PATH - 1 ); + + // file size + fptr->fsize = size; + + // add to hash table + FSH_AddHash( handle->hashlist, hash, fptr ); + + handle->count++; + + return fptr - handle->pathlist; // index +} + +/* +================ +FSH_RemoveFilePath +================ +*/ +int FSH_RemoveFilePath( fsh_handle_t *handle, const char *path ) +{ + uint hash; + fsh_path_t *fptr, **fptr_prev; + const char *strip_path; + + if( !handle ) + return -2; + + if( Q_strnicmp( path, handle->folderpath, handle->folderpath_size )) + return -2; + + strip_path = path + handle->folderpath_size + 1; // + '/' + + hash = COM_HashKey( strip_path, handle->hashlist_size ); + + for( fptr = handle->hashlist[hash]; fptr != NULL; fptr = fptr->nexthash ) + { + if( !Q_stricmp( fptr->path, strip_path )) + { + memset( fptr->path, 0, FSH_MAX_PATH ); + Q_strncpy( fptr->path, FSH_EMPTY_STRING, FSH_MAX_PATH - 1 ); + fptr->fsize = -3; // undefined + + // remove from hash table + FSH_RemoveHash( handle->hashlist, hash, fptr ); + + // add empty to hash table + FSH_AddHash( handle->hashlist, handle->empty_hash, fptr ); + + return fptr - handle->pathlist; // index + } + } + + return -1; +} + +/* +================ +FSH_RenameFilePath +================ +*/ +int FSH_RenameFilePath( fsh_handle_t *handle, const char *oldname, const char *newname ) +{ + uint hash; + fsh_path_t *fptr, **fptr_prev; + const char *strip_path; + + if( !handle ) + return -2; + + if( Q_strnicmp( oldname, handle->folderpath, handle->folderpath_size )) + return -2; + + strip_path = oldname + handle->folderpath_size + 1; // + '/' + + hash = COM_HashKey( strip_path, handle->hashlist_size ); + + for( fptr = handle->hashlist[hash]; fptr != NULL; fptr = fptr->nexthash ) + { + if( !Q_stricmp( fptr->path, strip_path )) + { + strip_path = newname + handle->folderpath_size + 1; // + '/' + + memset( fptr->path, 0, FSH_MAX_PATH ); + Q_strncpy( fptr->path, strip_path, FSH_MAX_PATH - 1 ); + + // remove old from hash table + FSH_RemoveHash( handle->hashlist, hash, fptr ); + + // add new to hash table + hash = COM_HashKey( strip_path, handle->hashlist_size ); + FSH_AddHash( handle->hashlist, hash, fptr ); + + return fptr - handle->pathlist; // index; + } + } + + return -1; +} + +/* +================ +FSH_FindSize +================ +*/ +int FSH_FindSize( fsh_handle_t *handle, const char *path ) +{ + uint hash; + fsh_path_t *fptr; + const char *strip_path; + + if( !handle ) + return -2; + + if( !handle->ready || !handle->count || Q_strnicmp( path, handle->folderpath, handle->folderpath_size )) + return -2; + + strip_path = path + handle->folderpath_size + 1; // + '/' + + hash = COM_HashKey( strip_path, handle->hashlist_size ); + + for( fptr = handle->hashlist[hash]; fptr != NULL; fptr = fptr->nexthash ) + { + if( !Q_stricmp( fptr->path, strip_path )) + return fptr->fsize; + } + + return -1; +} + +/* +================ +FSH_Find +================ +*/ +int FSH_Find( fsh_handle_t *handle, const char *path ) +{ + uint hash; + fsh_path_t *fptr; + const char *strip_path; + + if( !handle ) + return -2; + + if( !handle->ready || !handle->count || Q_strnicmp( path, handle->folderpath, handle->folderpath_size )) + return -2; + + strip_path = path + handle->folderpath_size + 1; // + '/' + + hash = COM_HashKey( strip_path, handle->hashlist_size ); + + for( fptr = handle->hashlist[hash]; fptr != NULL; fptr = fptr->nexthash ) + { + if( !Q_stricmp( fptr->path, strip_path )) + return fptr - handle->pathlist; // index + } + + return -1; +} + +/* +================ +FSH_ScanDir +================ +*/ +static int FSH_ScanDir( fsh_handle_t *handle, const char *path ) +{ + SceUID dir; + SceIoDirent entry; + char temp[FSH_MAX_PATH]; + int result; + int fsize; + + if(( dir = sceIoDopen( path )) < 0 ) + return -1; + + result = 0; + + // iterate through the directory + while( 1 ) + { + // zero the dirent, to avoid possible problems with sceIoDread + memset( &entry, 0, sizeof( SceIoDirent )); + if( !sceIoDread( dir, &entry )) + break; + + // ignore the virtual directories + if( !Q_stricmp( entry.d_name, "." ) || !Q_stricmp( entry.d_name, ".." )) + continue; + + sprintf( temp, "%s/%s", path, entry.d_name ); + + if(FIO_S_ISDIR( entry.d_stat.st_mode )) + result = FSH_ScanDir( handle, temp ); + else if(FIO_S_ISREG( entry.d_stat.st_mode )) + { + if( entry.d_stat.st_size <= __INT_MAX__ ) + fsize = ( int )entry.d_stat.st_size; + else fsize = -3; // undefined + + result = FSH_AddFilePathWs( handle, temp, fsize ); + } + else continue; + if( result < 0 ) break; + } + sceIoDclose( dir ); + + return result; +} + +/* +================ +FSH_Create +================ +*/ +fsh_handle_t *FSH_Create( const char *path, int maxfiles ) +{ + fsh_handle_t *handle; + + handle = P5Ram_Alloc( sizeof( fsh_handle_t ), 1 ); + if( !handle ) + return NULL; + + handle->pathlist_size = maxfiles; + handle->pathlist = P5Ram_Alloc( handle->pathlist_size * sizeof( fsh_path_t ), 1 ); + if( !handle->pathlist ) + { + P5Ram_Free( handle ); + return NULL; + } + + handle->hashlist_size = maxfiles >> 2; + handle->hashlist = P5Ram_Alloc( handle->hashlist_size * sizeof( fsh_path_t* ), 1 ); + if( !handle->hashlist ) + { + P5Ram_Free( handle ); + return NULL; + } + + handle->empty_hash = COM_HashKey( FSH_EMPTY_STRING, handle->hashlist_size ); + + if( !Q_strnicmp( path, "./", 2 )) + path += 2; + + Q_strncpy( handle->folderpath, path, FSH_MAX_PATH - 1 ); + handle->folderpath_size = Q_strlen( handle->folderpath ); + + if( FSH_ScanDir( handle, handle->folderpath ) < 0 ) + { + P5Ram_Free( handle ); + return NULL; + } + + handle->next = fsh_poolchain; + fsh_poolchain = handle; + + handle->ready = true; + + return handle; +} + +/* +================ +FSH_Shutdown +================ +*/ +void FSH_Free( fsh_handle_t *handle ) +{ + if( !handle ) + return; + + if( handle->pathlist ) + P5Ram_Free( handle->pathlist ); + + if( handle->hashlist ) + P5Ram_Free( handle->hashlist ); + + P5Ram_Free( handle ); +} diff --git a/engine/platform/psp/fsh_psp.h b/engine/platform/psp/fsh_psp.h new file mode 100644 index 000000000..213b046ca --- /dev/null +++ b/engine/platform/psp/fsh_psp.h @@ -0,0 +1,37 @@ +/* +fsh_psp.h - PSP filesystem helper header +Copyright (C) 2022 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#ifndef FSH_PSP_H +#define FSH_PSP_H + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct fsh_handle_s fsh_handle_t; + +int FSH_AddFilePathWs( fsh_handle_t *handle, const char *path, int size ); +#define FSH_AddFilePath( handle, path ) FSH_AddFilePathWs(handle, path, -2 ) +int FSH_RemoveFilePath( fsh_handle_t *handle, const char *path ); +int FSH_RenameFilePath( fsh_handle_t *handle, const char *oldname, const char *newname ); +int FSH_FindSize( fsh_handle_t *handle, const char *path ); +int FSH_Find( fsh_handle_t *handle, const char *path ); +fsh_handle_t *FSH_Create( const char *path, int maxfiles ); +void FSH_Free( fsh_handle_t *handle ); + +#ifdef __cplusplus +} +#endif + +#endif // P5RAM_PSP_H diff --git a/engine/platform/psp/in_psp.c b/engine/platform/psp/in_psp.c new file mode 100644 index 000000000..b174d5089 --- /dev/null +++ b/engine/platform/psp/in_psp.c @@ -0,0 +1,239 @@ +/* +in_psp.c - PSP input component +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include "common.h" +#include "keydefs.h" +#include "input.h" +#include "client.h" + +#if XASH_INPUT == INPUT_PSP +#include + +#define PSP_MAX_KEYS sizeof(psp_keymap) / sizeof(struct psp_keymap_s) +#define PSP_EXT_KEY PSP_CTRL_HOME + +convar_t *psp_joy_dz_min; +convar_t *psp_joy_dz_max; +convar_t *psp_joy_cv_power; +convar_t *psp_joy_cv_expo; + +static struct psp_keymap_s +{ + int srckey; + int dstkey; + qboolean stdpressed; + qboolean extpressed; +}psp_keymap[] = +{ +#if 0 + { PSP_CTRL_SELECT, '~' , false, false }, + { PSP_CTRL_START, K_ESCAPE , false, false }, + { PSP_CTRL_UP, K_UPARROW , false, false }, + { PSP_CTRL_RIGHT, K_RIGHTARROW, false, false }, + { PSP_CTRL_DOWN, K_DOWNARROW , false, false }, + { PSP_CTRL_LEFT, K_LEFTARROW , false, false }, + { PSP_CTRL_LTRIGGER, K_JOY1 , false, false }, + { PSP_CTRL_RTRIGGER, K_JOY2 , false, false }, + { PSP_CTRL_TRIANGLE, K_SHIFT , false, false }, + { PSP_CTRL_CIRCLE, K_SPACE , false, false }, + { PSP_CTRL_CROSS, K_ENTER , false, false }, + { PSP_CTRL_SQUARE, K_BACKSPACE , false, false }, +#else + { PSP_CTRL_SELECT , K_MODE_BUTTON , false, false }, + { PSP_CTRL_START , K_START_BUTTON, false, false }, + { PSP_CTRL_UP , K_UPARROW , false, false }, + { PSP_CTRL_RIGHT , K_RIGHTARROW , false, false }, + { PSP_CTRL_DOWN , K_DOWNARROW , false, false }, + { PSP_CTRL_LEFT , K_LEFTARROW , false, false }, + { PSP_CTRL_LTRIGGER, K_L1_BUTTON , false, false }, + { PSP_CTRL_RTRIGGER, K_R1_BUTTON , false, false }, + { PSP_CTRL_TRIANGLE, K_Y_BUTTON , false, false }, + { PSP_CTRL_CIRCLE , K_B_BUTTON , false, false }, + { PSP_CTRL_CROSS , K_A_BUTTON , false, false }, + { PSP_CTRL_SQUARE , K_X_BUTTON , false, false }, +#endif +#if 0 + PSP_CTRL_HOME, + PSP_CTRL_HOLD, + PSP_CTRL_NOTE, + PSP_CTRL_SCREEN, + PSP_CTRL_VOLUP, + PSP_CTRL_VOLDOWN, + PSP_CTRL_WLAN_UP, + PSP_CTRL_REMOTE, + PSP_CTRL_DISC, + PSP_CTRL_MS, +#endif +}; +static signed short psp_joymap[256]; /* -32768 <> 32767 */ + +static float Platform_JoyAxisCompute( float axis, float deadzone_min, float deadzone_max, float power, float expo ) +{ + float abs_axis, fabs_axis, fcurve; + float scale, r_deadzone_max; + qboolean flip_axis = 0; + + expo = bound( 0.0f, expo, 1.0f ); + power = bound( 0.0f, power, 10.0f ); + deadzone_min = bound( 0.0f, deadzone_min, 127.0f ); + deadzone_max = bound( 0.0f, deadzone_max, 127.0f ); + + // (-127) - (0) - (+127) + abs_axis = axis - 128.0f; + if( abs_axis < 0.0f ) + { + abs_axis = -abs_axis - 1.0f; + flip_axis = 1; + } + if( abs_axis <= deadzone_min ) return 0.0f; + r_deadzone_max = 127.0f - deadzone_max; + if( abs_axis >= r_deadzone_max ) return ( flip_axis ? -127.0f : 127.0f ); + + scale = 127.0f / ( r_deadzone_max - deadzone_min ); + abs_axis -= deadzone_min; + abs_axis *= scale; + + if( expo ) + { + // x * ( x^power * expo + x * ( 1.0 - expo )) + fabs_axis = abs_axis / 127.0f; // 0.0f - 1.0f + fcurve = powf(fabs_axis, power) * expo + fabs_axis * (1.0f - expo); + abs_axis = fabs_axis * fcurve * 127.0f; + } + + return ( flip_axis ? -abs_axis : abs_axis ); +} + +void Platform_RunEvents( void ) +{ + int i; + SceCtrlData buf; + signed short curr_X, curr_Y; + static unsigned int last_buttons; + static signed short last_X, last_Y; + + sceCtrlPeekBufferPositive( &buf, 1 ); + + for( i = 0; i < PSP_MAX_KEYS; i++ ) + { + if( buf.Buttons & PSP_EXT_KEY ) + { + if(( last_buttons ^ buf.Buttons ) & psp_keymap[i].srckey ) + { + if( psp_keymap[i].stdpressed ) + { + psp_keymap[i].stdpressed = false; + Key_Event( psp_keymap[i].dstkey, psp_keymap[i].stdpressed ); + } + else + { + psp_keymap[i].extpressed = buf.Buttons & psp_keymap[i].srckey; + Key_Event( K_AUX16 + i, psp_keymap[i].extpressed); + } + } + } + else + { + // release + if( psp_keymap[i].extpressed ) + { + psp_keymap[i].extpressed = false; + Key_Event( K_AUX16 + i, psp_keymap[i].extpressed); + } + + if(( last_buttons ^ buf.Buttons ) & psp_keymap[i].srckey ) + { + psp_keymap[i].stdpressed = buf.Buttons & psp_keymap[i].srckey; + Key_Event( psp_keymap[i].dstkey, psp_keymap[i].stdpressed ); + } + } + } + last_buttons = buf.Buttons; + + if( FBitSet( psp_joy_dz_min->flags, FCVAR_CHANGED ) || FBitSet( psp_joy_dz_max->flags, FCVAR_CHANGED ) || + FBitSet( psp_joy_cv_power->flags, FCVAR_CHANGED ) || FBitSet( psp_joy_cv_expo->flags, FCVAR_CHANGED )) + { + for ( i = 0; i < 256; i++ ) + { + psp_joymap[i] = Platform_JoyAxisCompute( i, psp_joy_dz_min->value, psp_joy_dz_max->value, psp_joy_cv_power->value, psp_joy_cv_expo->value ); + psp_joymap[i] *= 256; + } + + ClearBits( psp_joy_dz_min->flags, FCVAR_CHANGED ); + ClearBits( psp_joy_dz_max->flags, FCVAR_CHANGED ); + ClearBits( psp_joy_cv_power->flags, FCVAR_CHANGED ); + ClearBits( psp_joy_cv_expo->flags, FCVAR_CHANGED ); + } + + curr_X = psp_joymap[buf.Lx]; + curr_Y = psp_joymap[buf.Ly]; + + if( last_X != curr_X ) + Joy_AxisMotionEvent( 2, -curr_X ); + + if( last_Y != curr_Y ) + Joy_AxisMotionEvent( 3, -curr_Y ); + + last_X = curr_X; + last_Y = curr_Y; +} + +void Platform_GetMousePos( int *x, int *y ) +{ + *x = *y = 0; +} + +void Platform_SetMousePos( int x, int y ) +{ + +} + +void Platform_EnableTextInput( qboolean enable ) +{ + +} + +int Platform_JoyInit( int numjoy ) +{ + int i; + + // set up cvars + psp_joy_dz_min = Cvar_Get( "psp_joy_dz_min", "15", FCVAR_ARCHIVE, "joy deadzone min (0 - 127)" ); + psp_joy_dz_max = Cvar_Get( "psp_joy_dz_max", "0", FCVAR_ARCHIVE, "joy deadzone max (0 - 127)" ); + psp_joy_cv_power = Cvar_Get( "psp_joy_cv_power", "2", FCVAR_ARCHIVE, "joy curve power (0 - 10)" ); + psp_joy_cv_expo = Cvar_Get( "psp_joy_cv_expo", "0.5", FCVAR_ARCHIVE, "joy curve expo (0 - 1.0)" ); + + // set up the controller. + sceCtrlSetSamplingCycle( 0 ); + sceCtrlSetSamplingMode( PSP_CTRL_MODE_ANALOG ); + + // building a joystick map + for ( i = 0; i < 256; i++ ) + { + psp_joymap[i] = Platform_JoyAxisCompute( i, psp_joy_dz_min->value, psp_joy_dz_max->value, psp_joy_cv_power->value, psp_joy_cv_expo->value ); + psp_joymap[i] *= 256; + } + return 1; +} + +void Platform_MouseMove( float *x, float *y ) +{ + +} + +void Platform_PreCreateMove( void ) +{ + +} +#endif /* XASH_INPUT */ diff --git a/engine/platform/psp/ka/KernelAccess.S b/engine/platform/psp/ka/KernelAccess.S new file mode 100644 index 000000000..440230562 --- /dev/null +++ b/engine/platform/psp/ka/KernelAccess.S @@ -0,0 +1,8 @@ + .set noreorder + +#include "pspstub.s" + + STUB_START "KernelAccess",0x40090000,0x00020005 + STUB_FUNC 0x8A5C745F,kaGeEdramSetSize + STUB_FUNC 0x71570ECF,kaGeEdramGetHwSize + STUB_END diff --git a/engine/platform/psp/ka/ka.h b/engine/platform/psp/ka/ka.h new file mode 100644 index 000000000..2e2238f95 --- /dev/null +++ b/engine/platform/psp/ka/ka.h @@ -0,0 +1,30 @@ +/* +kamod.h - kernel access module header +Copyright (C) 2022 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#ifndef KAMOD_H +#define KAMOD_H + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +int kaGeEdramSetSize(int size); +int kaGeEdramGetHwSize(void); + +#ifdef __cplusplus +} +#endif + +#endif // KAMOD_H diff --git a/engine/platform/psp/lib_psp.c b/engine/platform/psp/lib_psp.c new file mode 100644 index 000000000..d9f633745 --- /dev/null +++ b/engine/platform/psp/lib_psp.c @@ -0,0 +1,486 @@ +/* +lib_psp.c - dynamic library code for Sony PSP system +Copyright (C) 2018 Flying With Gauss +Copyright (C) 2022 Sergey Galushko + +This program is free software: you can redistribute it and/sor modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include "platform/platform.h" + +#if XASH_LIB == LIB_PSP +#include +#include "common.h" +#include "library.h" +#include "filesystem.h" +#include "server.h" + +#define MOD_MAXNAMELEN 256 + +typedef struct table_s +{ + const char *name; + void *pointer; +} table_t; +#include "generated_library_tables.h" + +typedef struct mod_func_s +{ + const char *name; + void *addr; +} mod_func_t; + +typedef struct mod_handle_s +{ + SceUID uid; + char name[MOD_MAXNAMELEN]; + uint segAddr; + uint segSize; + mod_func_t *func; + struct mod_handle_s *next; +} mod_handle_t; + +static mod_handle_t *modList = NULL; +static char *modErrorPtr = NULL; +static char modErrorBuf[1024]; + +#define Module_Error( fmt, ... ) Module_SetError( "%s: " fmt "\n", __FUNCTION__, ##__VA_ARGS__ ) + +int Module_SetError( const char *fmt, ... ) +{ + va_list args; + int result; + + va_start( args, fmt ); + result = Q_vsnprintf( modErrorBuf, sizeof( modErrorBuf ), fmt, args ); + va_end( args ); + + modErrorPtr = modErrorBuf; + + return result; +} + +char *Module_GetError( void ) +{ + char *errPtr = modErrorPtr; + modErrorPtr = NULL; + return errPtr; +} + +static mod_handle_t *Module_Find( const char *name ) +{ + mod_handle_t *modHandle; + + if( !name ) + { + Module_Error( "input mismatch" ); + return NULL; + } + + for( modHandle = modList; modHandle; modHandle = modHandle->next ) + { + if( !Q_strcmp( modHandle->name, name )) + return modHandle; + } + + return NULL; +} + +static const char *Module_Name( void *handle ) +{ + mod_handle_t *modHandle; + + if( !handle ) + { + Module_Error( "input mismatch" ); + return NULL; + } + + for( modHandle = modList; modHandle; modHandle = modHandle->next ) + { + if( modHandle == handle ) + return modHandle->name; + } + + return NULL; +} + +static mod_func_t *Module_LoadStatic( const char *name ) +{ + table_t *staticLib; + + if( !name ) + { + Module_Error( "input mismatch" ); + return NULL; + } + + for( staticLib = ( table_t* )libs; staticLib->pointer && staticLib->name; staticLib++ ) + { + if( !Q_strcmp( staticLib->name, name )) + return staticLib->pointer; + } + + return NULL; +} + +mod_handle_t *Module_Load( const char *name ) +{ + mod_handle_t *modHandle; + mod_func_t *modFunc; + qboolean skipPrefix; + char modStaticName[32]; + void *modArg[2]; + SceUID modUid; + uint modSegAddr, modSegSize; + SceKernelModuleInfo info; + char engine_cwd[PATH_MAX]; + + if( !name ) + return NULL; + + modHandle = Module_Find( name ); + if( modHandle ) + return modHandle; + + skipPrefix = false; + modSegAddr = modSegSize = 0; + + Q_sprintf( engine_cwd, "%s/", host.rootdir ); + + modArg[0] = &modFunc; + modArg[1] = engine_cwd; + modUid = Platform_LoadModule( name, 0, sizeof( modArg ), modArg ); + if( modUid < 0 ) + { + COM_FileBase( name, modStaticName ); + + if( !Q_strncmp( modStaticName, "lib", 3 ) ) + skipPrefix = true; + + modFunc = Module_LoadStatic( skipPrefix ? &modStaticName[3] : modStaticName ); + if( !modFunc ) + { + Module_Error( "module %s error ( %#010x )", name, modUid ); + return NULL; + } + + modUid = -1; + + Con_Reportf( "Module_Load( %s ): ( static ) success!\n", name ); + } + else + { + info.size = sizeof( info ); + if ( sceKernelQueryModuleInfo( modUid, &info ) >= 0 ) + { + if( info.nsegment > 0 ) + { + modSegAddr = info.segmentaddr[0]; + modSegSize = info.segmentsize[0]; + } + } + + Con_Reportf( "Module_Load( %s ): ( dynamic ) success!\n", name ); + } + + modHandle = calloc( 1, sizeof( mod_handle_t )); + if( !modHandle ) + { + Module_Error( "out of memory" ); + return NULL; + } + + Q_strncpy( modHandle->name, name, MOD_MAXNAMELEN ); + + modHandle->uid = modUid; + modHandle->func = modFunc; + modHandle->segAddr = modSegAddr; + modHandle->segSize = modSegSize; + modHandle->next = modList; + + modList = modHandle; + + return modHandle; +} + +void *Module_GetAddrByName( mod_handle_t *handle, const char *name ) +{ + mod_func_t *modFunc; + + if( !handle || !name ) + { + Module_Error( "input mismatch" ); + return NULL; + } + + if( !Module_Name( handle )) + { + Module_SetError( "unknown handle" ); + return NULL; + } + + if( !handle->func ) + { + Module_SetError( "call Module_Load() first" ); + return NULL; + } + + for( modFunc = handle->func; modFunc->addr && modFunc->name; modFunc++ ) + { + if( !Q_strcmp( modFunc->name, name )) + return modFunc->addr; + } + + Module_Error( "func %s not found in %s", name, handle->name ); + + return NULL; +} + +const char *Module_GetNameByAddr( mod_handle_t *handle, const void *addr ) +{ + mod_func_t *modFunc; + + if( !handle || !addr ) + { + Module_Error( "input mismatch" ); + return NULL; + } + + if( !Module_Name( handle )) + { + Module_SetError( "unknown handle" ); + return NULL; + } + + if( !handle->func ) + { + Module_SetError( "call Module_Load() first" ); + return NULL; + } + + for( modFunc = handle->func; modFunc->addr && modFunc->name; modFunc++ ) + { + if( modFunc->addr == addr ) + return modFunc->name; + } + + Module_Error( "addr %#010x not found in %s", addr, handle->name ); + + return NULL; +} + +uint Module_GetOffsetByAddr( mod_handle_t *handle, const void *addr ) +{ + uint addrUInt; + + if( !handle || !addr ) + { + Module_Error( "input mismatch" ); + return 0; + } + + if( !Module_Name( handle )) + { + Module_SetError( "unknown handle" ); + return 0; + } + + if( !handle->segAddr ) + { + Module_SetError( "unknown segment" ); + return 0; + } + + addrUInt = ( uint )addr; + if( addrUInt < handle->segAddr || addrUInt >= ( handle->segAddr + handle->segSize )) + { + Module_SetError( "addr %#010x is out of range", addrUInt ); + return 0; + } + + return addrUInt - handle->segAddr; +} + +void *Module_GetAddrByOffset( mod_handle_t *handle, uint offset ) +{ + if( !handle || !offset ) + { + Module_Error( "input mismatch" ); + return NULL; + } + + if( !Module_Name( handle )) + { + Module_SetError( "unknown handle" ); + return NULL; + } + + if( !handle->segAddr ) + { + Module_SetError( "unknown segment" ); + return NULL; + } + + if( offset >= handle->segSize ) + { + Module_SetError( "offset %#010x is out of range", offset ); + return NULL; + } + + return ( void* )( offset + handle->segAddr ); +} + +int Module_Unload( mod_handle_t *handle ) +{ + mod_handle_t *modHandle; + int result, sceCode; + + if( !handle ) + { + Module_Error( "input mismatch" ); + return -1; + } + + if( !Module_Name( handle )) + { + Module_Error( "unknown handle" ); + return -2; + } + + if( handle->uid != -1 ) + { + result = Platform_UnloadModule( handle->uid, &sceCode ); + if( result < 0 ) + { + if( result == -1 ) + Module_Error( "module %s doesn't want to stop", handle->name ); + else + Module_Error( "module %s error ( %#010x )", handle->name, sceCode ); + + return -3; + } + } + + if( handle != modList ) + { + for( modHandle = modList; modHandle; modHandle = modHandle->next ) + { + if( modHandle->next == handle ) + { + modHandle->next = handle->next; + break; + } + } + } + else modList = handle->next; + + free( handle ); + + return 0; +} + +qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +{ + // TODO: implement + return true; +} + +void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath ) +{ + dll_user_t *hInst = NULL; + void *pHandle = NULL; + + COM_ResetLibraryError(); + + // platforms where gameinfo mechanism is working goes here + // and use FS_FindLibrary + hInst = FS_FindLibrary( dllname, directpath ); + if( !hInst ) + { + // try to find by linker(LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, LD_32_LIBRARY_PATH and so on...) + if( !pHandle ) + { + pHandle = Module_Load( dllname ); + if( pHandle ) + return pHandle; + + COM_PushLibraryError( va( "Failed to find library %s", dllname )); + COM_PushLibraryError( Module_GetError() ); + return NULL; + } + } + + if( hInst->custom_loader ) + { + COM_PushLibraryError( va( "Custom library loader is not available. Extract library %s and fix gameinfo.txt!", hInst->fullPath )); + Mem_Free( hInst ); + return NULL; + } + + if( !( hInst->hInstance = Module_Load( hInst->fullPath ))) + { + COM_PushLibraryError( Module_GetError() ); + Mem_Free( hInst ); + return NULL; + } + + pHandle = hInst->hInstance; + + Mem_Free( hInst ); + + return pHandle; +} + +void COM_FreeLibrary( void *hInstance ) +{ + Module_Unload( hInstance ); +} + +void *COM_GetProcAddress( void *hInstance, const char *name ) +{ + return Module_GetAddrByName( hInstance, name ); +} + +void *COM_FunctionFromName_SR( void *hInstance, const char *pName ) +{ + void *funcAddr; + + if( !memcmp( pName, "ofs:", 4 )) + funcAddr = Module_GetAddrByOffset( hInstance, Q_atoi( &pName[4] )); + else + funcAddr = Module_GetAddrByName( hInstance, pName ); + + if( !funcAddr ) + Con_Reportf( S_ERROR "FunctionFromName: Can't get symbol %s: %s\n", pName, Module_GetError() ); + + return funcAddr; +} + +const char *COM_NameForFunction( void *hInstance, void *function ) +{ + static char offsetName[16]; + const char *funcName; + uint addrOffset; + + funcName = Module_GetNameByAddr( hInstance, function ); + if( funcName ) + return funcName; + + addrOffset = Module_GetOffsetByAddr( hInstance, function ); + if( !addrOffset ) + return NULL; + + Q_snprintf( offsetName, sizeof( offsetName ), "ofs:%u", addrOffset ); + Con_Reportf( "COM_NameForFunction: %s\n", offsetName ); + + return offsetName; +} +#endif // XASH_LIB == LIB_PSP diff --git a/engine/platform/psp/p5ram_psp.c b/engine/platform/psp/p5ram_psp.c new file mode 100644 index 000000000..1d58a0aa3 --- /dev/null +++ b/engine/platform/psp/p5ram_psp.c @@ -0,0 +1,165 @@ +/* +p5ram_psp.c - PSP P5 memory allocator +Copyright (C) 2022 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include +#include +#include + +#include +#include +#include +#include "p5ram_psp.h" + +#define P5RAM_ALIGN( x, a ) ((( x ) + (( typeof( x ))( a ) - 1 )) & ~( typeof( x )( a ) - 1 )) + +#define P5RAM_CHECK( addr ) ((( unsigned int )addr >= ( unsigned int )p5ram_addr) && (( unsigned int )addr < ( unsigned int )p5ram_addr + p5ram_size)) + +#define P5RAM_FLAG_INIT 0x01 +#define P5RAM_FLAG_SUSPENDED 0x02 + +typedef struct __attribute__(( aligned( 64 ))) p5ram_info_s +{ + SceUID uid; + size_t size; + struct p5ram_info_s *next; +}p5ram_info_t; + +static unsigned char p5ram_flags = 0x00; +static p5ram_info_t *p5ram_poolchain = NULL; +static void *p5ram_addr; +static unsigned int p5ram_size; + +int P5Ram_Init( void ) +{ + int result; + + if( p5ram_flags & P5RAM_FLAG_INIT ) + return -1; + + result = sceKernelVolatileMemLock( 0, &p5ram_addr, &p5ram_size ); + if( result == 0 ) + p5ram_flags |= P5RAM_FLAG_INIT; + + return result; +} + +void *P5Ram_Alloc( size_t size, int clear ) +{ + SceUID uid; + void *ptr; + + if(!( p5ram_flags & P5RAM_FLAG_INIT )) + return NULL; + + uid = sceKernelAllocPartitionMemory( 5, "USER P5", PSP_SMEM_Low, size + sizeof( p5ram_info_t ), NULL ); + if( uid < 0 ) return NULL; + + ptr = sceKernelGetBlockHeadAddr( uid ); + (( p5ram_info_t* )ptr )->uid = uid; + (( p5ram_info_t* )ptr )->size = size; + (( p5ram_info_t* )ptr )->next = p5ram_poolchain; + if( clear ) memset(( unsigned char* )ptr + sizeof( p5ram_info_t ), 0, size ); + + p5ram_poolchain = ptr; + + return ( void* )(( unsigned char* )ptr + sizeof( p5ram_info_t )); +} + +void P5Ram_Free( void *ptr ) +{ + p5ram_info_t *info, *next_ptr; + + if( !( p5ram_flags & P5RAM_FLAG_INIT ) || ptr == NULL ) return; + + info = ( p5ram_info_t* )((unsigned char*)ptr - sizeof( p5ram_info_t )); + + if( info == p5ram_poolchain ) + { + p5ram_poolchain = p5ram_poolchain->next; + } + else + { + next_ptr = p5ram_poolchain; + while( next_ptr ) + { + if( next_ptr->next == info ) + { + next_ptr->next = info->next; + break; + } + next_ptr = next_ptr->next; + } + } + + sceKernelFreePartitionMemory( info->uid ); +} + +void P5Ram_FreeAll( void ) +{ + p5ram_info_t *next_ptr; + + while( p5ram_poolchain ) + { + next_ptr = p5ram_poolchain->next; + sceKernelFreePartitionMemory( p5ram_poolchain->uid ); + p5ram_poolchain = next_ptr; + } +} + +void P5Ram_Shutdown( void ) +{ + if( !( p5ram_flags & P5RAM_FLAG_INIT )) + return; + + P5Ram_FreeAll(); + sceKernelVolatileMemUnlock( 0 ); + + p5ram_flags &= ~P5RAM_FLAG_INIT; +} + +void P5Ram_PowerCallback( int count, int arg, void *common ) +{ + if( !( p5ram_flags & ( P5RAM_FLAG_INIT | P5RAM_FLAG_SUSPENDED ))) + return; + + if ( arg & PSP_POWER_CB_POWER_SWITCH || arg & PSP_POWER_CB_SUSPENDING ) + { + P5Ram_Shutdown(); + p5ram_flags |= P5RAM_FLAG_SUSPENDED; + } + else if ( arg & PSP_POWER_CB_RESUMING ) + { + } + else if ( arg & PSP_POWER_CB_RESUME_COMPLETE ) + { + P5Ram_Init(); + p5ram_flags &= ~P5RAM_FLAG_SUSPENDED; + } +} + +#ifdef P5RAM_DEBUG +void P5Ram_Print( void ) +{ + p5ram_info_t *next_ptr; + + printf("+++++++++++++++++++++++++++++\n"); + next_ptr = p5ram_poolchain; + while( next_ptr ) + { + printf("P5 ALLOC [ 0x%08X 0x%08X 0x%08X 0x%08X ]\n", next_ptr, next_ptr->uid, next_ptr->size, next_ptr->next ); + next_ptr = next_ptr->next; + } + printf("+++++++++++++++++++++++++++++\n"); +} +#endif diff --git a/engine/platform/psp/p5ram_psp.h b/engine/platform/psp/p5ram_psp.h new file mode 100644 index 000000000..9f73f051e --- /dev/null +++ b/engine/platform/psp/p5ram_psp.h @@ -0,0 +1,38 @@ +/* +p5ram_psp.h - PSP P5 memory allocator header +Copyright (C) 2022 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#ifndef P5RAM_PSP_H +#define P5RAM_PSP_H + +//#define P5RAM_DEBUG + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +int P5Ram_Init( void ); +void *P5Ram_Alloc( size_t size, int clear ); +void P5Ram_Free( void *ptr ); +void P5Ram_FreeAll( void ); +void P5Ram_Shutdown( void ); +void P5Ram_PowerCallback( int count, int arg, void *common ); +#ifdef P5RAM_DEBUG +void P5Ram_Print( void ); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // P5RAM_PSP_H diff --git a/engine/platform/psp/scemp3/pspmp3.h b/engine/platform/psp/scemp3/pspmp3.h new file mode 100644 index 000000000..cc40773d7 --- /dev/null +++ b/engine/platform/psp/scemp3/pspmp3.h @@ -0,0 +1,248 @@ +/* + * PSP Software Development Kit - https://github.com/pspdev + * ----------------------------------------------------------------------- + * Licensed under the BSD license, see LICENSE in PSPSDK root for details. + * + * pspmp3.h - Prototypes for the sceMp3 library + * + * Copyright (c) 2008 David Perry + * Copyright (c) 2008 Alexander Berl + * Copyright (c) 2022 Sergey Galushko + * + */ + +#ifndef __SCELIBMP3_H__ +#define __SCELIBMP3_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SceMp3InitArg { + /** Stream start position */ + SceOff mp3StreamStart; + /** Stream end position */ + SceOff mp3StreamEnd; + /** Pointer to a buffer to contain raw mp3 stream data (+1472 bytes workspace) */ + SceUChar8* mp3Buf; + /** Size of mp3Buf buffer (must be >= 8192) */ + SceInt32 mp3BufSize; + /** Pointer to decoded pcm samples buffer */ + SceUChar8* pcmBuf; + /** Size of pcmBuf buffer (must be >= 9216) */ + SceInt32 pcmBufSize; +} SceMp3InitArg; + +/** + * sceMp3ReserveMp3Handle + * + * @param args - Pointer to SceMp3InitArg structure + * + * @return sceMp3 handle on success, < 0 on error. + */ +SceInt32 sceMp3ReserveMp3Handle(SceMp3InitArg* args); + +/** + * sceMp3ReleaseMp3Handle + * + * @param handle - sceMp3 handle + * + * @return 0 if success, < 0 on error. + */ +SceInt32 sceMp3ReleaseMp3Handle(SceInt32 handle); + +/** + * sceMp3InitResource + * + * @return 0 if success, < 0 on error. + */ +SceInt32 sceMp3InitResource(); + +/** + * sceMp3TermResource + * + * @return 0 if success, < 0 on error. + */ +SceInt32 sceMp3TermResource(); + +/** + * sceMp3Init + * + * @param handle - sceMp3 handle + * + * @return 0 if success, < 0 on error. + */ +SceInt32 sceMp3Init(SceInt32 handle); + +/** + * sceMp3Decode + * + * @param handle - sceMp3 handle + * @param dst - Pointer to destination pcm samples buffer + * + * @return number of bytes in decoded pcm buffer, < 0 on error. + */ +SceInt32 sceMp3Decode(SceInt32 handle, SceShort16** dst); + +/** + * sceMp3GetInfoToAddStreamData + * + * @param handle - sceMp3 handle + * @param dst - Pointer to stream data buffer + * @param towrite - Space remaining in stream data buffer + * @param srcpos - Position in source stream to start reading from + * + * @return 0 if success, < 0 on error. + */ +SceInt32 sceMp3GetInfoToAddStreamData(SceInt32 handle, SceUChar8** dst, SceInt32* towrite, SceInt32* srcpos); + +/** + * sceMp3NotifyAddStreamData + * + * @param handle - sceMp3 handle + * @param size - number of bytes added to the stream data buffer + * + * @return 0 if success, < 0 on error. + */ +SceInt32 sceMp3NotifyAddStreamData(SceInt32 handle, SceInt32 size); + +/** + * sceMp3CheckStreamDataNeeded + * + * @param handle - sceMp3 handle + * + * @return 1 if more stream data is needed, < 0 on error. + */ +SceInt32 sceMp3CheckStreamDataNeeded(SceInt32 handle); + +/** + * sceMp3SetLoopNum + * + * @param handle - sceMp3 handle + * @param loop - Number of loops + * + * @return 0 if success, < 0 on error. + */ +SceInt32 sceMp3SetLoopNum(SceInt32 handle, SceInt32 loop); + +/** + * sceMp3GetLoopNum + * + * @param handle - sceMp3 handle + * + * @return Number of loops + */ +SceInt32 sceMp3GetLoopNum(SceInt32 handle); + +/** + * sceMp3GetSumDecodedSample + * + * @param handle - sceMp3 handle + * + * @return Number of decoded samples + */ +SceInt32 sceMp3GetSumDecodedSample(SceInt32 handle); + +/** + * sceMp3GetMaxOutputSample + * + * @param handle - sceMp3 handle + * + * @return Number of max samples to output + */ +SceInt32 sceMp3GetMaxOutputSample(SceInt32 handle); + +/** + * sceMp3GetSamplingRate + * + * @param handle - sceMp3 handle + * + * @return Sampling rate of the mp3 + */ +SceInt32 sceMp3GetSamplingRate(SceInt32 handle); + +/** + * sceMp3GetBitRate + * + * @param handle - sceMp3 handle + * + * @return Bitrate of the mp3 + */ +SceInt32 sceMp3GetBitRate(SceInt32 handle); + +/** + * sceMp3GetMp3ChannelNum + * + * @param handle - sceMp3 handle + * + * @return Number of channels of the mp3 + */ +SceInt32 sceMp3GetMp3ChannelNum(SceInt32 handle); + +/** + * sceMp3ResetPlayPosition + * + * @param handle - sceMp3 handle + * + * @return < 0 on error + */ +SceInt32 sceMp3ResetPlayPosition(SceInt32 handle); + +/** + * sceMp3GetFrameNum + * + * @param handle - sceMp3 handle + * + * @return < 0 on error + */ +SceInt32 sceMp3GetFrameNum(SceInt32 handle); + +/** + * sceMp3ResetPlayPositionByFrame + * + * @param handle - sceMp3 handle + * @param frame - frame + * + * @return < 0 on error + */ +SceInt32 sceMp3ResetPlayPositionByFrame(SceInt32 handle, SceUInt32 frame); + +/** + * sceMp3GetMPEGVersion + * + * @param handle - sceMp3 handle + * + * @return < 0 on error + */ +SceInt32 sceMp3GetMPEGVersion(SceInt32 handle); + +/** + * sceMp3LowLevelInit + * + * @param handle - sceMp3 handle + * @param src - Pointer to a buffer to contain raw mp3 stream data + * + * @return < 0 on error + */ +SceInt32 sceMp3LowLevelInit(SceInt32 handle, SceUChar8* src); + +/** + * sceMp3LowLevelDecode + * + * @param handle - sceMp3 handle + * @param mp3src - + * @param mp3srcused - + * @param pcmdst - + * @param pcmdstoutsz - + * + * @return < 0 on error + */ +SceInt32 sceMp3LowLevelDecode(SceInt32 handle, SceUChar8* mp3src, SceUInt32* mp3srcused, SceShort16* pcmdst, SceUInt32* pcmdstoutsz); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/engine/platform/psp/scemp3/sceMp3.S b/engine/platform/psp/scemp3/sceMp3.S new file mode 100644 index 000000000..7f5d798e3 --- /dev/null +++ b/engine/platform/psp/scemp3/sceMp3.S @@ -0,0 +1,29 @@ + .set noreorder + +#include "pspimport.s" + + IMPORT_START "sceMp3",0x00090011 + IMPORT_FUNC "sceMp3",0x07EC321A,sceMp3ReserveMp3Handle + IMPORT_FUNC "sceMp3",0x0DB149F4,sceMp3NotifyAddStreamData + IMPORT_FUNC "sceMp3",0x2A368661,sceMp3ResetPlayPosition + IMPORT_FUNC "sceMp3",0x354D27EA,sceMp3GetSumDecodedSample + IMPORT_FUNC "sceMp3",0x35750070,sceMp3InitResource + IMPORT_FUNC "sceMp3",0x3C2FA058,sceMp3TermResource + IMPORT_FUNC "sceMp3",0x3CEF484F,sceMp3SetLoopNum + IMPORT_FUNC "sceMp3",0x44E07129,sceMp3Init + IMPORT_FUNC "sceMp3",0x732B042A,sceMp3EndEntry + IMPORT_FUNC "sceMp3",0x7F696782,sceMp3GetMp3ChannelNum + IMPORT_FUNC "sceMp3",0x87677E40,sceMp3GetBitRate + IMPORT_FUNC "sceMp3",0x87C263D1,sceMp3GetMaxOutputSample + IMPORT_FUNC "sceMp3",0x8AB81558,sceMp3StartEntry + IMPORT_FUNC "sceMp3",0x8F450998,sceMp3GetSamplingRate + IMPORT_FUNC "sceMp3",0xA703FE0F,sceMp3GetInfoToAddStreamData + IMPORT_FUNC "sceMp3",0xD021C0FB,sceMp3Decode + IMPORT_FUNC "sceMp3",0xD0A56296,sceMp3CheckStreamDataNeeded + IMPORT_FUNC "sceMp3",0xD8F54A51,sceMp3GetLoopNum + IMPORT_FUNC "sceMp3",0xF5478233,sceMp3ReleaseMp3Handle + IMPORT_FUNC "sceMp3",0xAE6D2027,sceMp3GetMPEGVersion + IMPORT_FUNC "sceMp3",0x3548AEC8,sceMp3GetFrameNum + IMPORT_FUNC "sceMp3",0x0840E808,sceMp3ResetPlayPositionByFrame + IMPORT_FUNC "sceMp3",0x1B839B83,sceMp3LowLevelInit + IMPORT_FUNC "sceMp3",0xE3EE2C81,sceMp3LowLevelDecode diff --git a/engine/platform/psp/snd_psp.c b/engine/platform/psp/snd_psp.c new file mode 100644 index 000000000..91e194ba2 --- /dev/null +++ b/engine/platform/psp/snd_psp.c @@ -0,0 +1,275 @@ +/* +snd_psp.c - psp sound hardware output +Copyright (C) 2022 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "platform/platform.h" +#if XASH_SOUND == SOUND_PSP +#include + +#include +#include +#include + +#include "sound.h" + +#define PSP_NUM_AUDIO_SAMPLES 1024 // must be multiple of 64 +#define PSP_OUTPUT_CHANNELS 2 +#define PSP_OUTPUT_BUFFER_SIZE (( PSP_NUM_AUDIO_SAMPLES ) * ( PSP_OUTPUT_CHANNELS )) + +static struct +{ + SceUID threadUID; + SceUID semaUID; + int channel; + volatile int volL; + volatile int volR; + volatile int running; +} snd_psp = { -1, -1, -1, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, 1 }; + +static short snd_psp_buff[2][PSP_OUTPUT_BUFFER_SIZE] __attribute__(( aligned( 64 ))); + +/* +================== +SNDDMA_MainThread + +Copy samples +================== +*/ +static int SNDDMA_MainThread( SceSize args, void *argp ) +{ + int index = 0; + + while( snd_psp.running ) + { + sceKernelWaitSema( snd_psp.semaUID, 1, NULL ); + + int len = PSP_OUTPUT_BUFFER_SIZE; + int size = dma.samples; + int pos = dma.samplepos; + int wrapped = pos + len - size; + + if( wrapped < 0 ) + { + sceDmacMemcpy( snd_psp_buff[index], dma.buffer + ( pos * 2 ), len * 2 ); + dma.samplepos += len; + } + else + { + int remaining = size - pos; + sceDmacMemcpy( snd_psp_buff[index], dma.buffer + ( pos * 2 ), remaining * 2 ); + if( wrapped > 0 ) + sceDmacMemcpy( snd_psp_buff[index] + ( remaining * 2 ), dma.buffer, wrapped * 2 ); + dma.samplepos = wrapped; + } + + sceKernelSignalSema( snd_psp.semaUID, 1 ); + + sceAudioOutputPannedBlocking( snd_psp.channel, snd_psp.volL, snd_psp.volR, snd_psp_buff[index] ); + index = !index; + } + + sceKernelExitThread( 0 ); + return 0; +} + +/* +================== +SNDDMA_Init + +Try to find a sound device to mix for. +Returns false if nothing is found. +================== +*/ +qboolean SNDDMA_Init( void ) +{ + int samplecount; + + dma.format.speed = SOUND_DMA_SPEED; + dma.format.channels = PSP_OUTPUT_CHANNELS; + dma.format.width = 2; + + // must be multiple of 64 + samplecount = s_samplecount->value; + if( !samplecount ) + samplecount = 0x4000; + + dma.samples = samplecount * PSP_OUTPUT_CHANNELS; + dma.samplepos = 0; + dma.buffer = memalign( 64, dma.samples * 2 ); // 16 bit + if( !dma.buffer ) + return false; + + // clearing buffers + memset( dma.buffer, 0, dma.samples * 2 ); + memset( snd_psp_buff, 0, sizeof( snd_psp_buff )); + + // allocate and initialize a hardware output channel + snd_psp.channel = sceAudioChReserve( PSP_AUDIO_NEXT_CHANNEL, + PSP_NUM_AUDIO_SAMPLES, PSP_AUDIO_FORMAT_STEREO ); + if( snd_psp.channel < 0 ) + { + SNDDMA_Shutdown(); + return false; + } + + // create semaphore + snd_psp.semaUID = sceKernelCreateSema( "sound_sema", 0, 1, 255, NULL ); + if( snd_psp.semaUID <= 0 ) + { + SNDDMA_Shutdown(); + return false; + } + + // create audio thread + snd_psp.threadUID = sceKernelCreateThread( "sound_thread", SNDDMA_MainThread, 0x12, 0x8000, 0, 0 ); + if( snd_psp.threadUID < 0 ) + { + SNDDMA_Shutdown(); + return false; + } + + // start audio thread + if( sceKernelStartThread( snd_psp.threadUID, 0, 0 ) < 0 ) + { + SNDDMA_Shutdown(); + return false; + } + + Con_Printf( "Using PSP audio driver: %d Hz\n", dma.format.speed ); + + dma.initialized = true; + + return true; +} + +/* +============== +SNDDMA_GetDMAPos + +return the current sample position (in mono samples read) +inside the recirculating dma buffer, so the mixing code will know +how many sample are required to fill it up. +=============== +*/ +int SNDDMA_GetDMAPos( void ) +{ + return dma.samplepos; +} + +/* +============== +SNDDMA_GetSoundtime + +update global soundtime +=============== +*/ +int SNDDMA_GetSoundtime( void ) +{ + static int buffers, oldsamplepos; + int samplepos, fullsamples; + + fullsamples = dma.samples / 2; + + // it is possible to miscount buffers + // if it has wrapped twice between + // calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + + if( samplepos < oldsamplepos ) + { + buffers++; // buffer wrapped + + if( paintedtime > 0x40000000 ) + { + // time to chop things off to avoid 32 bit limits + buffers = 0; + paintedtime = fullsamples; + S_StopAllSounds( true ); + } + } + + oldsamplepos = samplepos; + + return ( buffers * fullsamples + samplepos / 2 ); +} + +/* +============== +SNDDMA_BeginPainting + +Makes sure dma.buffer is valid +=============== +*/ +void SNDDMA_BeginPainting( void ) +{ + if( snd_psp.semaUID > 0 ) + sceKernelWaitSema( snd_psp.semaUID, 1, NULL ); +} + +/* +============== +SNDDMA_Submit + +Send sound to device if buffer isn't really the dma buffer +Also unlocks the dsound buffer +=============== +*/ +void SNDDMA_Submit( void ) +{ + if( snd_psp.semaUID > 0 ) + sceKernelSignalSema( snd_psp.semaUID, 1 ); +} + +/* +============== +SNDDMA_Shutdown + +Reset the sound device for exiting +=============== +*/ +void SNDDMA_Shutdown( void ) +{ + Con_Printf("Shutting down audio.\n"); + + snd_psp.running = 0; + + if( snd_psp.threadUID >= 0 ) + { + sceKernelWaitThreadEnd( snd_psp.threadUID, NULL ); + sceKernelDeleteThread( snd_psp.threadUID ); + snd_psp.threadUID = -1; + } + + if( snd_psp.semaUID > 0 ) + { + sceKernelDeleteSema( snd_psp.semaUID ); + snd_psp.semaUID = -1; + } + + if( snd_psp.channel >= 0 ) + { + sceAudioChRelease( snd_psp.channel ); + snd_psp.channel = -1; + } + + if( dma.buffer ) + { + free( dma.buffer ); + dma.buffer = NULL; + } + + dma.initialized = false; +} + +#endif // XASH_SOUND == SOUND_PSP diff --git a/engine/platform/psp/sys_psp.c b/engine/platform/psp/sys_psp.c new file mode 100644 index 000000000..3cea58dfe --- /dev/null +++ b/engine/platform/psp/sys_psp.c @@ -0,0 +1,353 @@ +/* +sys_psp.c - PSP System utils +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "platform/platform.h" +#include "platform/psp/ka/ka.h" +#include "platform/psp/scemp3/pspmp3.h" +#include +#include +#include +#include +#include +#include +#include + +#include + + +PSP_MODULE_INFO( "Xash3D", PSP_MODULE_USER, 1, 0 ); +PSP_MAIN_THREAD_ATTR( PSP_THREAD_ATTR_USER | PSP_THREAD_ATTR_VFPU ); +PSP_MAIN_THREAD_STACK_SIZE_KB( 512 ); +PSP_HEAP_SIZE_KB( -3 * 1024 ); /* 3 MB for prx modules */ + +static qboolean psp_audiolib_init = false; + +static int Platform_ExitCallback( int count, int arg, void *common ) +{ + host.crashed = true; + return 0; +} + +static int Platform_PowerCallback( int count, int arg, void *common ) +{ + P5Ram_PowerCallback( count, arg, common ); + return 0; +} + +static int Platform_CallbackThread( SceSize args, void *argp ) +{ + int cbid; + + //cbid = sceKernelCreateCallback( "Exit Callback", Platform_ExitCallback, NULL ); + //sceKernelRegisterExitCallback( cbid ); + + cbid = sceKernelCreateCallback( "Power Callback", Platform_PowerCallback, NULL ); + scePowerRegisterCallback( 0, cbid ); + + sceKernelSleepThreadCB(); + + return 0; +} + +/* Sets up the callback thread and returns its thread id */ +static int Platform_SetupCallbacks( void ) +{ + int thid = 0; + + thid = sceKernelCreateThread("update_thread", Platform_CallbackThread, 0x11, 0xFA0, 0, 0); + if(thid >= 0) + { + sceKernelStartThread(thid, 0, 0); + } + return thid; +} + +#if XASH_TIMER == TIMER_PSP +double Platform_DoubleTime( void ) +{ + return ( double )sceKernelGetSystemTimeWide() * 0.000001; // microseconds to seconds +} + +void Platform_Sleep( int msec ) +{ + sceKernelDelayThread( msec * 1000 ); +} +#endif + +void *Platform_GetNativeObject( const char *name ) +{ + return NULL; +} + +void Platform_Vibrate( float life, char flags ) +{ + +} + +void Platform_GetClipboardText( char *buffer, size_t size ) +{ + +} + +void Platform_SetClipboardText( const char *buffer, size_t size ) +{ + +} + +void Platform_ShellExecute( const char *path, const char *parms ) +{ + +} + +#if XASH_MESSAGEBOX == MSGBOX_PSP +void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ) +{ + static qboolean g_dbgscreen = false; + + // Clear the sound buffer. + S_StopAllSounds( true ); + + // Print message to the debug screen. + if ( !g_dbgscreen ) + { + pspDebugScreenInit(); + g_dbgscreen = true; + } + pspDebugScreenSetTextColor( 0x0080ff ); + pspDebugScreenPrintf( "\n\n\n\n%s:\n", title ); + pspDebugScreenSetTextColor( 0x0000ff ); + pspDebugScreenPrintData( message, Q_strlen( message ) ); + pspDebugScreenSetTextColor( 0xffffff ); + pspDebugScreenPrintf( "\n\nPress X to continue.\n" ); + + // Wait for a X button press. + SceCtrlData pad; + do + { + sceCtrlReadBufferPositive( &pad, 1 ); + } + while( !( pad.Buttons & PSP_CTRL_CROSS ) ); + pspDebugScreenClear(); +} +#endif // XASH_MESSAGEBOX == MSGBOX_PSP + +void Platform_ReadCmd( const char *fname, int *argc, char **argv ) +{ + int cmd_fd; + size_t cmd_fsize; + byte *cmd_buff; + int i, j = 0; + + cmd_fd = sceIoOpen( fname, PSP_O_RDONLY, 0777 ); + if( cmd_fd > 0 ) + { + cmd_fsize = sceIoLseek( cmd_fd, 0, PSP_SEEK_END ); + sceIoLseek( cmd_fd, 0, PSP_SEEK_SET ); + + printf( "CMD FILE(%s) Size: %i\n", fname, cmd_fsize ); + + if( cmd_fsize == 0 ) return; + + cmd_buff = malloc( cmd_fsize ); + if( !cmd_buff ) + { + printf( "CMD FILE(%s) Memory allocation error!\n", fname ); + sceIoClose(cmd_fd); + return; + } + + if( sceIoRead(cmd_fd, cmd_buff, cmd_fsize) >= 0 ) + { + for( i = 0; i < cmd_fsize; i++ ) + { + if( isspace( cmd_buff[i] ) != 0 ) + { + if( j == 0 ) continue; + + argv[*argc] = malloc( j + 1 ); + if( !argv[*argc] ) + { + printf( "CMD FILE(%s) Memory allocation error!\n", fname ); + free( cmd_buff ); + sceIoClose( cmd_fd ); + return; + } + memcpy( argv[*argc], &cmd_buff[i - j], j ); + argv[*argc][j] = 0x00; + ( int )( *argc )++; + j = 0; + } + else j++; + + } + if( j != 0 ) + { + argv[*argc] = malloc( j + 1 ); + if( !argv[*argc] ) + { + printf( "CMD FILE(%s) Memory allocation error!\n", fname ); + free( cmd_buff ); + sceIoClose( cmd_fd ); + return; + } + memcpy( argv[*argc], &cmd_buff[i - j], j ); + argv[*argc][j] = 0x00; + ( int )( *argc )++; + j = 0; + } + } + else printf( "CMD FILE(%s) Read error!\n", fname ); + + free( cmd_buff ); + sceIoClose( cmd_fd ); + } +} + +SceUID Platform_LoadModule( const char *filename, int mpid, SceSize argsize, void *argp ) +{ + SceKernelLMOption option; + SceUID modid = 0; + int retVal = 0, mresult; + + memset( &option, 0, sizeof( option ) ); + option.size = sizeof( option ); + option.mpidtext = mpid; + option.mpiddata = mpid; + option.position = 0; + option.access = 1; + + retVal = sceKernelLoadModule( filename, 0, &option ); + if(retVal < 0) + return retVal; + + modid = retVal; + + retVal = sceKernelStartModule( modid, argsize, argp, &mresult, NULL ); + if( retVal < 0 ) + return retVal; + + return modid; +} + +int Platform_UnloadModule( SceUID modid, int *sce_code ) +{ + int status; + *sce_code = sceKernelStopModule( modid, 0, NULL, &status, NULL); + + if( ( *sce_code ) < 0 ) + return -2; + else if( status == SCE_KERNEL_ERROR_NOT_STOPPED ) + return -1; + + *sce_code = sceKernelUnloadModule( modid ); + return ( ( ( *sce_code ) < 0 ) ? -2 : 0 ); +} + +int Platform_InitAudioLibs( void ) +{ + int status; + + if( psp_audiolib_init ) + return -1; + + status = sceUtilityLoadModule( PSP_MODULE_AV_AVCODEC ); + if ( status < 0 ) + { + Con_DPrintf( S_ERROR "sceUtilityLoadModule(PSP_MODULE_AV_AVCODEC) returned 0x%08X\n", status ); + return status; + } + + status = sceUtilityLoadModule( PSP_MODULE_AV_MP3 ); + if ( status < 0 ) + { + Con_DPrintf( S_ERROR "sceUtilityLoadModule(PSP_MODULE_AV_MP3) returned 0x%08X\n", status ); + return status; + } + + // init mp3 resources + status = sceMp3InitResource(); + if ( status < 0 ) + { + Con_DPrintf( S_ERROR "sceMp3InitResource returned 0x%08X\n", status ); + return status; + } + + psp_audiolib_init = true; + + return 0; +} + +int Platform_ShutdownAudioLibs( void ) +{ + int status; + + if( !psp_audiolib_init ) + return -1; + + status = sceMp3TermResource(); + if ( status < 0 ) + Con_DPrintf( S_ERROR "sceMp3TermResource returned 0x%08X\n", status ); + + status = sceUtilityUnloadModule( PSP_MODULE_AV_MP3 ); + if ( status < 0 ) + Con_DPrintf( S_ERROR "sceUtilityUnloadModule(PSP_MODULE_AV_MP3) returned 0x%08X\n", status ); + + status = sceUtilityUnloadModule( PSP_MODULE_AV_AVCODEC ); + if ( status < 0 ) + Con_DPrintf( S_ERROR "sceUtilityUnloadModule(PSP_MODULE_AV_AVCODEC) returned 0x%08X\n", status ); + + psp_audiolib_init = false; + + return 0; +} + +void Platform_Init( void ) +{ + SceUID kamID; + int result; + + // disable fpu exceptions (division by zero and etc...) + pspSdkDisableFPUExceptions(); + + // exit callback thread + Platform_SetupCallbacks(); + + // set max cpu/gpu frequency + scePowerSetClockFrequency( 333, 333, 166 ); + + // set max VRAM + kamID = Platform_LoadModule( "ka.prx", 1, 0, NULL ); + if( kamID >= 0 ) + { + result = kaGeEdramGetHwSize(); + if( result > 0 ) + result = kaGeEdramSetSize( result ); + Platform_UnloadModule( kamID, &result ); + } + + Platform_InitAudioLibs(); + + // P5 Ram init + P5Ram_Init(); +} + +void Platform_Shutdown( void ) +{ + P5Ram_Shutdown(); + Platform_ShutdownAudioLibs(); +#if XASH_PROFILING + gprof_cleanup(); +#endif +} diff --git a/engine/platform/psp/vid_psp.c b/engine/platform/psp/vid_psp.c new file mode 100644 index 000000000..d14f37e81 --- /dev/null +++ b/engine/platform/psp/vid_psp.c @@ -0,0 +1,384 @@ +/* +vid_psp.c - PSP video component +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#if XASH_VIDEO == VIDEO_PSP + +#if !XASH_DEDICATED +#include +#include + +#include "common.h" +#include "client.h" +#include "mod_local.h" +#include "input.h" +#include "vid_common.h" + +// Set frame buffer +#define PSP_FB_WIDTH 480 +#define PSP_FB_HEIGHT 272 +#define PSP_FB_BWIDTH 512 +#define PSP_FB_FORMAT PSP_DISPLAY_PIXEL_FORMAT_565 //4444,5551,565,8888 + +#if PSP_FB_FORMAT == PSP_DISPLAY_PIXEL_FORMAT_4444 +#define PSP_FB_BPP 2 +#elif PSP_FB_FORMAT == PSP_DISPLAY_PIXEL_FORMAT_5551 +#define PSP_FB_BPP 2 +#elif PSP_FB_FORMAT == PSP_DISPLAY_PIXEL_FORMAT_565 +#define PSP_FB_BPP 2 +#elif PSP_FB_FORMAT == PSP_DISPLAY_PIXEL_FORMAT_8888 +#define PSP_FB_BPP 4 +#endif + +#if 0 +// Set up screen scaling +#define PSP_WIDTH_AR 30 +#define PSP_HEIGHT_AR 17 +#define PSP_MIN_MAR 12 +#define PSP_MAX_MAR 16 +#define PSP_MIN_MAR_W (PSP_WIDTH_AR * PSP_MIN_MAR) +#define PSP_MIN_MAR_H (PSP_HEIGHT_AR * PSP_MIN_MAR) +#define PSP_MAX_MAR_W (PSP_WIDTH_AR * PSP_MAX_MAR) +#define PSP_MAX_MAR_H (PSP_HEIGHT_AR * PSP_MAX_MAR) +#endif + + +static qboolean vsync; +#if 1 +static vidmode_t vidmodes[] = { "480x272", PSP_FB_WIDTH, PSP_FB_HEIGHT}; +static int num_vidmodes = 1; +#else +static vidmode_t *vidmodes = NULL; +static int num_vidmodes = 0; +#endif +static struct +{ +#if 0 + int in_width, in_height; + int out_width, out_height; +#endif + void *draw_buffer; + void *disp_buffer; +}vid_psp; + +qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ) +{ + *stride = PSP_FB_BWIDTH; + *r = 31; + *g = 63 << 5; + *b = 31 << 11; + *bpp = 2; + + vid_psp.draw_buffer = (void*)malloc( PSP_FB_HEIGHT * PSP_FB_BWIDTH * PSP_FB_BPP ); + if( !vid_psp.draw_buffer ) + Host_Error( "Memory allocation failled! (vid_psp.draw_buffer)\n"); + + vid_psp.disp_buffer = (void*)malloc( PSP_FB_HEIGHT * PSP_FB_BWIDTH * PSP_FB_BPP ); + if( !vid_psp.disp_buffer ) + Host_Error( "Memory allocation failled! (vid_psp.disp_buffer)\n"); + + sceDisplaySetMode(0, PSP_FB_WIDTH, PSP_FB_HEIGHT); + sceDisplaySetFrameBuf(vid_psp.disp_buffer, PSP_FB_BWIDTH, PSP_FB_FORMAT, 1); + + return true; +} + +void *SW_LockBuffer( void ) +{ + return vid_psp.draw_buffer; +} + +void SW_UnlockBuffer( void ) +{ + void* p_swap = vid_psp.disp_buffer; + vid_psp.disp_buffer = vid_psp.draw_buffer; + vid_psp.draw_buffer = p_swap; + + sceKernelDcacheWritebackInvalidateAll(); + sceDisplaySetFrameBuf(vid_psp.disp_buffer, PSP_FB_BWIDTH, PSP_FB_FORMAT, PSP_DISPLAY_SETBUF_NEXTFRAME); + + if ( vsync ) + { + sceDisplayWaitVblankStart(); + } +} + +int R_MaxVideoModes( void ) +{ + return num_vidmodes; +} + +vidmode_t *R_GetVideoMode( int num ) +{ + if( !vidmodes || num < 0 || num >= R_MaxVideoModes() ) + { + return NULL; + } + + return vidmodes + num; +} +#if 0 +static void R_InitVideoModes( void ) +{ + int i; + + vidmodes = Mem_Malloc( host.mempool, (PSP_MAX_MAR - PSP_MIN_MAR) * sizeof( vidmode_t ) ); + + // from smallest to largest + for(i = PSP_MIN_MAR ; i <= PSP_MAX_MAR; i++ ) + { + vidmodes[num_vidmodes].width = PSP_WIDTH_AR * i; + vidmodes[num_vidmodes].height = PSP_HEIGHT_AR * i; + vidmodes[num_vidmodes].desc = + copystring( va( "%ix%i", vidmodes[num_vidmodes].width, vidmodes[num_vidmodes].height )); + + num_vidmodes++; + } +} + +static void R_FreeVideoModes( void ) +{ + int i; + + if(!vidmodes) + return; + + for( i = 0; i < num_vidmodes; i++ ) + Mem_Free( (char*)vidmodes[i].desc ); + Mem_Free( vidmodes ); + + vidmodes = NULL; +} +#endif +/* +================= +GL_GetProcAddress +================= +*/ +void *GL_GetProcAddress( const char *name ) +{ + return NULL; +} + +/* +=============== +GL_UpdateSwapInterval +=============== +*/ +void GL_UpdateSwapInterval( void ) +{ + // disable VSync while level is loading + if( cls.state < ca_active ) + { + // setup vsync here + vsync = false; + SetBits( gl_vsync->flags, FCVAR_CHANGED ); + } + else if( FBitSet( gl_vsync->flags, FCVAR_CHANGED )) + { + ClearBits( gl_vsync->flags, FCVAR_CHANGED ); + vsync = (gl_vsync->value > 0) ? true : false; + } +} + +static qboolean VID_SetScreenResolution( int width, int height ) +{ + int render_w = width, render_h = height; + uint rotate = vid_rotate->value; + + if( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale->value, vid_scale->value ) ) + { + if( rotate & 1 ) + { + int swap = render_w; + + render_w = render_h; + render_h = swap; + } + + render_h /= vid_scale->value; + render_w /= vid_scale->value; + } + else + { + Con_Printf( S_WARN "failed to setup screen transform\n" ); + } + + + + R_SaveVideoMode( width, height, render_w, render_h ); + + return true; +} + +void GL_SwapBuffers( void ) +{ + if ( vsync ) sceDisplayWaitVblankStart(); +} + +int GL_SetAttribute( int attr, int val ) +{ + return 0; +} + +int GL_GetAttribute( int attr, int *val ) +{ + return 0; +} + +/* +================== +R_Init_Video +================== +*/ +qboolean R_Init_Video( const int type ) +{ + string safe; + qboolean retval; + + refState.desktopBitsPixel = ( PSP_FB_BPP * 8 ); + + VID_StartupGamma(); + + switch( type ) + { + case REF_SOFTWARE: + glw_state.software = true; + break; + case REF_GL: + if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", safe ) ) + glw_state.safe = bound( SAFE_NO, Q_atoi( safe ), SAFE_DONTCARE ); + break; + default: + Host_Error( "Can't initialize unknown context type %d!\n", type ); + break; + } + + if( !(retval = VID_SetMode()) ) + { + return retval; + } + + switch( type ) + { + case REF_GL: + // refdll also can check extensions + ref.dllFuncs.GL_InitExtensions(); + break; + case REF_SOFTWARE: + default: + break; + } +#if 0 + R_InitVideoModes(); +#endif + host.renderinfo_changed = false; + + return true; +} + +rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ) +{ + Con_Reportf( "R_ChangeDisplaySettings: Setting video mode to %dx%d %s\n", width, height, fullscreen ? "fullscreen" : "windowed" ); + + refState.fullScreen = fullscreen; + + if( !VID_SetScreenResolution( width, height ) ) + return rserr_invalid_fullscreen; + + return rserr_ok; +} + +/* +================== +VID_SetMode + +Set the described video mode +================== +*/ +qboolean VID_SetMode( void ) +{ + qboolean fullscreen = false; + int iScreenWidth, iScreenHeight; + rserr_t err; + +#if 1 + iScreenWidth = PSP_FB_WIDTH; + iScreenHeight = PSP_FB_HEIGHT; +#else + vid_psp.out_width = PSP_FB_WIDTH; + vid_psp.out_height = PSP_FB_HEIGHT; + + iScreenWidth = Cvar_VariableInteger( "width" ); + iScreenHeight = Cvar_VariableInteger( "height" ); + + if( iScreenWidth < PSP_MIN_MAR_W || + iScreenHeight < PSP_MIN_MAR_H ) // trying to get resolution automatically by default + { + iScreenWidth = PSP_MIN_MAR_W; + iScreenHeight = PSP_MIN_MAR_H; + } + + if( iScreenWidth > PSP_MAX_MAR_W || + iScreenHeight > PSP_MAX_MAR_H ) // trying to get resolution automatically by default + { + iScreenWidth = PSP_MAX_MAR_W; + iScreenHeight = PSP_MAX_MAR_H; + } +#endif + if( !FBitSet( vid_fullscreen->flags, FCVAR_CHANGED ) ) + Cvar_SetValue( "fullscreen", DEFAULT_FULLSCREEN ); + else + ClearBits( vid_fullscreen->flags, FCVAR_CHANGED ); + + SetBits( gl_vsync->flags, FCVAR_CHANGED ); + fullscreen = true;//Cvar_VariableInteger("fullscreen") != 0; + + if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, fullscreen )) == rserr_ok ) + { +#if 0 + vid_psp.in_width = iScreenWidth; + vid_psp.in_height = iScreenHeight; +#endif + } + else + return false; + + return true; +} + +/* +================== +R_Free_Video +================== +*/ +void R_Free_Video( void ) +{ +#if 0 + R_FreeVideoModes(); +#endif + ref.dllFuncs.GL_ClearExtensions(); + if( glw_state.software ) + { + if( vid_psp.draw_buffer ) + free( vid_psp.draw_buffer ); + if( vid_psp.disp_buffer ) + free( vid_psp.disp_buffer ); + + vid_psp.draw_buffer = NULL; + vid_psp.disp_buffer = NULL; + } +} + +#endif // XASH_DEDICATED +#endif // XASH_VIDEO \ No newline at end of file diff --git a/engine/ref_api.h b/engine/ref_api.h index db47ea51d..88a489432 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -446,6 +446,11 @@ typedef struct ref_api_s // filesystem exports fs_api_t *fsapi; + +#if XASH_PSP + void *(*P5Ram_Alloc)( size_t size, int clear ); + void (*P5Ram_Free)( void *ptr ); +#endif } ref_api_t; struct mip_s; diff --git a/engine/server/server.h b/engine/server/server.h index b7f40ff7b..41fa01275 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -54,7 +54,7 @@ extern int SV_UPDATE_BACKUP; #define GROUP_OP_AND 0 #define GROUP_OP_NAND 1 -#ifdef NDEBUG +#if defined( NDEBUG ) #define SV_IsValidEdict( e ) ( e && !e->free ) #else #define SV_IsValidEdict( e ) SV_CheckEdict( e, __FILE__, __LINE__ ) @@ -509,7 +509,13 @@ qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multipla qboolean SV_InitGame( void ); void SV_ActivateServer( int runPhysics ); qboolean SV_SpawnServer( const char *server, const char *startspot, qboolean background ); -model_t *SV_ModelHandle( int modelindex ); +static inline model_t *GAME_EXPORT SV_ModelHandle( int modelindex ) +{ + if( unlikely( modelindex < 0 || modelindex >= MAX_MODELS )) + return NULL; + + return sv.models[modelindex]; +} void SV_DeactivateServer( void ); // @@ -648,10 +654,17 @@ void SV_RestartAmbientSounds( void ); void SV_RestartDecals( void ); void SV_RestartStaticEnts( void ); int pfnDropToFloor( edict_t* e ); -edict_t *SV_EdictNum( int n ); void SV_SetModel( edict_t *ent, const char *name ); int pfnDecalIndex( const char *m ); +static inline edict_t *SV_EdictNum( int n ) +{ + if( unlikely( n < 0 || n >= GI->max_edicts )) + return NULL; + + return &svgame.edicts[n]; +} + // // sv_log.c // diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 4e1433185..f0277a1b2 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -39,13 +39,6 @@ static vec3_t viewPoint[MAX_CLIENTS]; typedef void (__cdecl *LINK_ENTITY_FUNC)( entvars_t *pev ); typedef void (__stdcall *GIVEFNPTRSTODLL)( enginefuncs_t* engfuncs, globalvars_t *pGlobals ); -edict_t *SV_EdictNum( int n ) -{ - if(( n >= 0 ) && ( n < GI->max_edicts )) - return svgame.edicts + n; - return NULL; -} - #ifndef NDEBUG qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) { diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index 781d57936..706a45622 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -262,20 +262,6 @@ int GAME_EXPORT SV_GenericIndex( const char *filename ) return i; } -/* -================ -SV_ModelHandle - -get model by handle -================ -*/ -model_t *GAME_EXPORT SV_ModelHandle( int modelindex ) -{ - if( modelindex < 0 || modelindex >= MAX_MODELS ) - return NULL; - return sv.models[modelindex]; -} - static resourcetype_t SV_DetermineResourceType( const char *filename ) { if( !Q_strncmp( filename, DEFAULT_SOUNDPATH, sizeof( DEFAULT_SOUNDPATH ) - 1 ) && Sound_SupportedFileFormat( COM_FileExtension( filename ))) diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 9e49fd425..a9224b94b 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -945,12 +945,12 @@ static edict_t *SV_PushMove( edict_t *pusher, float movetime ) // filter movetypes to collide with if( !SV_CanPushed( check )) continue; - +#if !XASH_PSP pusher->v.solid = SOLID_NOT; block = SV_TestEntityPosition( check, pusher ); pusher->v.solid = oldsolid; if( block ) continue; - +#endif // if the entity is standing on the pusher, it will definately be moved if( !( FBitSet( check->v.flags, FL_ONGROUND ) && check->v.groundentity == pusher )) { @@ -966,7 +966,12 @@ static edict_t *SV_PushMove( edict_t *pusher, float movetime ) if( !SV_TestEntityPosition( check, NULL )) continue; } - +#if XASH_PSP + pusher->v.solid = SOLID_NOT; + block = SV_TestEntityPosition( check, pusher ); + pusher->v.solid = oldsolid; + if( block ) continue; +#endif // remove the onground flag for non-players if( check->v.movetype != MOVETYPE_WALK ) check->v.flags &= ~FL_ONGROUND; @@ -1058,18 +1063,19 @@ static edict_t *SV_PushRotate( edict_t *pusher, float movetime ) for( e = 1; e < svgame.numEntities; e++ ) { check = EDICT_NUM( e ); + if( !SV_IsValidEdict( check )) continue; // filter movetypes to collide with if( !SV_CanPushed( check )) continue; - +#if !XASH_PSP pusher->v.solid = SOLID_NOT; block = SV_TestEntityPosition( check, pusher ); pusher->v.solid = oldsolid; if( block ) continue; - +#endif // if the entity is standing on the pusher, it will definately be moved if( !(( check->v.flags & FL_ONGROUND ) && check->v.groundentity == pusher )) { @@ -1085,7 +1091,12 @@ static edict_t *SV_PushRotate( edict_t *pusher, float movetime ) if( !SV_TestEntityPosition( check, NULL )) continue; } - +#if XASH_PSP + pusher->v.solid = SOLID_NOT; + block = SV_TestEntityPosition( check, pusher ); + pusher->v.solid = oldsolid; + if( block ) continue; +#endif // save original position of contacted entity pushed_p->ent = check; VectorCopy( check->v.origin, pushed_p->origin ); @@ -1101,7 +1112,7 @@ static edict_t *SV_PushRotate( edict_t *pusher, float movetime ) Matrix4x4_VectorITransform( start_l, org, temp ); Matrix4x4_VectorTransform( end_l, temp, org2 ); VectorSubtract( org2, org, lmove ); - + // i can't clear FL_ONGROUND in all cases because many bad things may be happen if( check->v.movetype != MOVETYPE_WALK ) { @@ -1140,7 +1151,6 @@ static edict_t *SV_PushRotate( edict_t *pusher, float movetime ) return check; } } - return NULL; } diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c index 477c9590d..dec97a272 100644 --- a/engine/server/sv_save.c +++ b/engine/server/sv_save.c @@ -32,8 +32,11 @@ half-life implementation of saverestore system #define SAVEGAME_HEADER (('V'<<24)+('A'<<16)+('S'<<8)+'J') // little-endian "JSAV" #define SAVEGAME_VERSION 0x0071 // Version 0.71 GoldSrc compatible #define CLIENT_SAVEGAME_VERSION 0x0067 // Version 0.67 - +#if XASH_PSP +#define SAVE_HEAPSIZE 0x200000 // reserve 2Mb for now +#else #define SAVE_HEAPSIZE 0x400000 // reserve 4Mb for now +#endif #define SAVE_HASHSTRINGS 0xFFF // 4095 unique strings // savedata headers @@ -384,7 +387,13 @@ static void InitEntityTable( SAVERESTOREDATA *pSaveData, int entityCount ) ENTITYTABLE *pTable; int i; +#if XASH_PSP + pSaveData->pTable = P5Ram_Alloc( sizeof( ENTITYTABLE ) * entityCount, 1 ); + if( !pSaveData->pTable ) + Host_Error( "%s: P5Ram_Alloc (pSaveData->pTable) failed!", __FUNCTION__ ); +#else pSaveData->pTable = Mem_Calloc( host.mempool, sizeof( ENTITYTABLE ) * entityCount ); +#endif pSaveData->tableCount = entityCount; // setup entitytable @@ -700,8 +709,17 @@ static SAVERESTOREDATA *SaveInit( int size, int tokenCount ) { SAVERESTOREDATA *pSaveData; +#if XASH_PSP + pSaveData = P5Ram_Alloc( sizeof( SAVERESTOREDATA ) + size, 1 ); + if( !pSaveData ) + Host_Error( "%s: P5Ram_Alloc (pSaveData) failed!", __FUNCTION__ ); + pSaveData->pTokens = (char **)P5Ram_Alloc( tokenCount * sizeof( char* ), 1 ); + if( !pSaveData->pTokens ) + Host_Error( "%s: P5Ram_Alloc (pSaveData->pTokens) failed!", __FUNCTION__ ); +#else pSaveData = Mem_Calloc( host.mempool, sizeof( SAVERESTOREDATA ) + size ); pSaveData->pTokens = (char **)Mem_Calloc( host.mempool, tokenCount * sizeof( char* )); +#endif pSaveData->tokenCount = tokenCount; pSaveData->pBaseData = (char *)(pSaveData + 1); // skip the save structure); @@ -750,20 +768,32 @@ static void SaveFinish( SAVERESTOREDATA *pSaveData ) if( pSaveData->pTokens ) { +#if XASH_PSP + P5Ram_Free( pSaveData->pTokens ); +#else Mem_Free( pSaveData->pTokens ); +#endif pSaveData->pTokens = NULL; pSaveData->tokenCount = 0; } if( pSaveData->pTable ) { +#if XASH_PSP + P5Ram_Free( pSaveData->pTable ); +#else Mem_Free( pSaveData->pTable ); +#endif pSaveData->pTable = NULL; pSaveData->tableCount = 0; } svgame.globals->pSaveData = NULL; +#if XASH_PSP + P5Ram_Free( pSaveData ); +#else Mem_Free( pSaveData ); +#endif } /* @@ -2333,14 +2363,26 @@ int GAME_EXPORT SV_GetSaveComment( const char *savename, char *comment ) return 0; } +#if XASH_PSP + pSaveData = (char *)P5Ram_Alloc( size, 0 ); + if( !pSaveData ) + Host_Error( "%s: P5Ram_Alloc (pSaveData) failed!", __FUNCTION__ ); +#else pSaveData = (char *)Mem_Malloc( host.mempool, size ); +#endif FS_Read( f, pSaveData, size ); pData = pSaveData; // allocate a table for the strings, and parse the table if( tokenSize > 0 ) { +#if XASH_PSP + pTokenList = P5Ram_Alloc( tokenCount * sizeof( char* ), 1 ); + if( !pTokenList ) + Host_Error( "%s: P5Ram_Alloc (pTokenList) failed!", __FUNCTION__ ); +#else pTokenList = Mem_Calloc( host.mempool, tokenCount * sizeof( char* )); +#endif // make sure the token strings pointed to by the pToken hashtable. for( i = 0; i < tokenCount; i++ ) @@ -2352,22 +2394,40 @@ int GAME_EXPORT SV_GetSaveComment( const char *savename, char *comment ) else pTokenList = NULL; // short, short (size, index of field name) +#if XASH_PSP /* FIX Unaligned access! */ + short offpd; + memcpy(&offpd, pData, sizeof( short ) ); + nFieldSize = offpd; + pData += sizeof( short ); + + memcpy(&offpd, pData, sizeof( short )); + pFieldName = pTokenList[offpd]; +#else nFieldSize = *(short *)pData; pData += sizeof( short ); pFieldName = pTokenList[*(short *)pData]; - +#endif if( Q_stricmp( pFieldName, "GameHeader" )) { Q_strncpy( comment, "", MAX_STRING ); +#if XASH_PSP + if( pTokenList ) P5Ram_Free( pTokenList ); + if( pSaveData ) P5Ram_Free( pSaveData ); +#else if( pTokenList ) Mem_Free( pTokenList ); if( pSaveData ) Mem_Free( pSaveData ); +#endif FS_Close( f ); return 0; } // int (fieldcount) pData += sizeof( short ); +#if XASH_PSP /* FIX Unaligned access! */ + memcpy(&nNumberOfFields, pData, sizeof(int)); +#else nNumberOfFields = (int)*pData; +#endif pData += nFieldSize; // each field is a short (size), short (index of name), binary string of "size" bytes (data) @@ -2378,11 +2438,23 @@ int GAME_EXPORT SV_GetSaveComment( const char *savename, char *comment ) // Size // szName // Actual Data + + +#if XASH_PSP /* FIX Unaligned access! */ + memcpy(&offpd, pData, sizeof( short ) ); + nFieldSize = offpd; + pData += sizeof( short ); + + memcpy(&offpd, pData, sizeof( short )); + pFieldName = pTokenList[offpd]; + pData += sizeof( short ); +#else nFieldSize = *(short *)pData; pData += sizeof( short ); pFieldName = pTokenList[*(short *)pData]; pData += sizeof( short ); +#endif size = Q_min( nFieldSize, MAX_STRING ); @@ -2400,8 +2472,13 @@ int GAME_EXPORT SV_GetSaveComment( const char *savename, char *comment ) } // delete the string table we allocated +#if XASH_PSP + if( pTokenList ) P5Ram_Free( pTokenList ); + if( pSaveData ) P5Ram_Free( pSaveData ); +#else if( pTokenList ) Mem_Free( pTokenList ); if( pSaveData ) Mem_Free( pSaveData ); +#endif FS_Close( f ); // at least mapname should be filled diff --git a/engine/sprite.h b/engine/sprite.h index 72cbf2f6e..8c326fcfa 100644 --- a/engine/sprite.h +++ b/engine/sprite.h @@ -65,6 +65,8 @@ typedef enum SPR_CULL_NONE, // oriented sprite will be draw back face too } facetype_t; +#pragma pack(push, 1) + // generic helper typedef struct { @@ -133,4 +135,6 @@ typedef struct STATIC_ASSERT( sizeof( dframetype_t ) == 4, "invalid dframetype_t size" ); +#pragma pack(pop) + #endif//SPRITE_H diff --git a/engine/wscript b/engine/wscript index 424dff7b1..47eaa3409 100644 --- a/engine/wscript +++ b/engine/wscript @@ -67,6 +67,8 @@ def configure(conf): # then remove them to avoid them getting linked to shared objects for lib in extra_libs: conf.env.LDFLAGS.remove(lib) + elif conf.env.DEST_OS == 'psp': + conf.define('XASH_NO_NETWORK', 1) elif conf.options.FBDEV_SW: # unused, XASH_LINUX without XASH_SDL gives fbdev & alsa support # conf.define('XASH_FBDEV', 1) @@ -87,7 +89,8 @@ def configure(conf): if conf.options.STATIC: conf.env.STATIC = True - conf.define('XASH_NO_LIBDL',1) + if conf.env.DEST_OS != 'psp': + conf.define('XASH_NO_LIBDL',1) if not conf.env.DEST_OS in ['win32', 'android'] and not conf.options.NO_ASYNC_RESOLVE: conf.check_pthreads() @@ -132,16 +135,19 @@ def build(bld): 'common/*.c', 'common/imagelib/*.c', 'common/soundlib/*.c', - 'common/soundlib/libmpg/*.c', 'server/*.c']) + # PSP uses hw MP3 decoder + if bld.env.DEST_OS != 'psp': + source += bld.path.ant_glob(['common/soundlib/libmpg/*.c']) + if bld.env.ENGINE_TESTS: source += bld.path.ant_glob(['tests/*.c']) if bld.env.DEST_OS == 'win32': libs += ['USER32', 'SHELL32', 'GDI32', 'ADVAPI32', 'DBGHELP', 'PSAPI', 'WS2_32' ] source += bld.path.ant_glob(['platform/win32/*.c']) - elif bld.env.DEST_OS not in ['dos', 'nswitch', 'psvita']: #posix + elif bld.env.DEST_OS not in ['dos', 'nswitch', 'psvita', 'psp']: #posix libs += [ 'M', 'RT', 'PTHREAD', 'ASOUND'] if not bld.env.STATIC: libs += ['DL'] @@ -211,6 +217,10 @@ def build(bld): '-lSceKernelDmacMgr_stub' ] + if bld.env.DEST_OS == 'psp': + source += bld.path.ant_glob(['platform/psp/*.c']) + source += bld.path.ant_glob(['platform/psp/*/*.S']) + # add client files if not bld.env.DEDICATED: source += bld.path.ant_glob([ diff --git a/public/build.c b/public/build.c index 858e85187..ce63ee422 100644 --- a/public/build.c +++ b/public/build.c @@ -121,6 +121,8 @@ const char *Q_PlatformStringByID( const int platform ) return "nswitch"; case PLATFORM_PSVITA: return "psvita"; + case PLATFORM_PSP: + return "psp"; } assert( 0 ); diff --git a/public/build.h b/public/build.h index 2f8fffae3..d86660c8f 100644 --- a/public/build.h +++ b/public/build.h @@ -86,6 +86,7 @@ Then you can use another oneliner to query all variables: #undef XASH_X86 #undef XASH_NSWITCH #undef XASH_PSVITA +#undef XASH_PSP //================================================================ // @@ -98,6 +99,8 @@ Then you can use another oneliner to query all variables: #define XASH_EMSCRIPTEN 1 #elif defined __WATCOMC__ && defined __DOS__ #define XASH_DOS4GW 1 +#elif defined __psp__ + #define XASH_PSP 1 #else // POSIX compatible #define XASH_POSIX 1 #if defined __linux__ @@ -144,7 +147,7 @@ Then you can use another oneliner to query all variables: // but we still need XASH_MOBILE_PLATFORM for the engine. // So this macro is defined entirely in build-system: see main wscript // HLSDK/PrimeXT/other SDKs users note: you may ignore this macro -#if XASH_ANDROID || XASH_IOS || XASH_NSWITCH || XASH_PSVITA || XASH_SAILFISH +#if XASH_ANDROID || XASH_IOS || XASH_NSWITCH || XASH_PSVITA || XASH_SAILFISH || XASH_PSP #define XASH_MOBILE_PLATFORM 1 #endif diff --git a/public/buildenums.h b/public/buildenums.h index 9a895fc6f..2b4c1701b 100644 --- a/public/buildenums.h +++ b/public/buildenums.h @@ -42,6 +42,7 @@ GNU General Public License for more details. #define PLATFORM_NSWITCH 13 #define PLATFORM_PSVITA 14 #define PLATFORM_LINUX_UNKNOWN 15 +#define PLATFORM_PSP 16 #if XASH_WIN32 #define XASH_PLATFORM PLATFORM_WIN32 @@ -73,6 +74,8 @@ GNU General Public License for more details. #define XASH_PLATFORM PLATFORM_NSWITCH #elif XASH_PSVITA #define XASH_PLATFORM PLATFORM_PSVITA +#elif XASH_PSP + #define XASH_PLATFORM PLATFORM_PSP #else #error #endif diff --git a/public/matrixlib.c b/public/matrixlib.c index fc4718626..50df7e59e 100644 --- a/public/matrixlib.c +++ b/public/matrixlib.c @@ -35,13 +35,61 @@ const matrix3x4 m_matrix3x4_identity = */ void Matrix3x4_VectorTransform( const matrix3x4 in, const float v[3], float out[3] ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in[0] + "lv.q C110, 16 + %1\n" // C110 = in[1] + "lv.q C120, 32 + %1\n" // C120 = in[2] + "lv.s S130, 0 + %2\n" // S130 = v[0] + "lv.s S131, 4 + %2\n" // S131 = v[1] + "lv.s S132, 8 + %2\n" // S132 = v[2] + "vhdp.q S000, C130, C100\n" // S000 = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + in[0][3] + "vhdp.q S001, C130, C110\n" // S001 = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + in[1][3] + "vhdp.q S002, C130, C120\n" // S002 = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] + in[2][3] + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ), "m"( *v ) + ); +#else out[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + in[0][3]; out[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + in[1][3]; out[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] + in[2][3]; +#endif } void Matrix3x4_VectorITransform( const matrix3x4 in, const float v[3], float out[3] ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in[0] + "lv.q C110, 16 + %1\n" // C110 = in[1] + "lv.q C120, 32 + %1\n" // C120 = in[2] + "lv.s S130, 0 + %2\n" // S130 = v[0] + "lv.s S131, 4 + %2\n" // S131 = v[1] + "lv.s S132, 8 + %2\n" // S132 = v[2] + "vsub.t C130, C130, R103\n" // C130 = v - in[][3] +#if 1 + "vtfm3.t C000, E100, C130\n" // C000 = E100 * C130 +#else + "vdot.t S000, C130, R100\n" // S000 = dir[0] * in[0][0] + dir[1] * in[1][0] + dir[2] * in[2][0] + "vdot.t S001, C130, R101\n" // S001 = dir[0] * in[0][1] + dir[1] * in[1][1] + dir[2] * in[2][1] + "vdot.t S002, C130, R102\n" // S002 = dir[0] * in[0][2] + dir[1] * in[1][2] + dir[2] * in[2][2] +#endif + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ), "m"( *v ) + ); +#else vec3_t dir; dir[0] = v[0] - in[0][3]; @@ -51,24 +99,99 @@ void Matrix3x4_VectorITransform( const matrix3x4 in, const float v[3], float out out[0] = dir[0] * in[0][0] + dir[1] * in[1][0] + dir[2] * in[2][0]; out[1] = dir[0] * in[0][1] + dir[1] * in[1][1] + dir[2] * in[2][1]; out[2] = dir[0] * in[0][2] + dir[1] * in[1][2] + dir[2] * in[2][2]; +#endif } void Matrix3x4_VectorRotate( const matrix3x4 in, const float v[3], float out[3] ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in[0] + "lv.q C110, 16 + %1\n" // C110 = in[1] + "lv.q C120, 32 + %1\n" // C120 = in[2] + "lv.s S130, 0 + %2\n" // S130 = v[0] + "lv.s S131, 4 + %2\n" // S131 = v[1] + "lv.s S132, 8 + %2\n" // S132 = v[2] +#if 1 + "vtfm3.t C000, M100, C130\n" // C000 = M100 * C130 +#else + "vdot.t S000, C130, C100\n" // S000 = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + "vdot.t S001, C130, C110\n" // S001 = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + "vdot.t S002, C130, C120\n" // S002 = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] +#endif + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ), "m"( *v ) + ); + +#else out[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2]; out[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2]; out[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2]; +#endif } void Matrix3x4_VectorIRotate( const matrix3x4 in, const float v[3], float out[3] ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in[0] + "lv.q C110, 16 + %1\n" // C110 = in[1] + "lv.q C120, 32 + %1\n" // C120 = in[2] + "lv.s S130, 0 + %2\n" // S130 = v[0] + "lv.s S131, 4 + %2\n" // S131 = v[1] + "lv.s S132, 8 + %2\n" // S132 = v[2] +#if 1 + "vtfm3.t C000, E100, C130\n" // C000 = E100 * C130 +#else + "vdot.t S000, C130, R100\n" // S000 = v[0] * in[0][0] + v[1] * in[1][0] + v[2] * in[2][0] + "vdot.t S001, C130, R101\n" // S001 = v[0] * in[0][1] + v[1] * in[1][1] + v[2] * in[2][1] + "vdot.t S002, C130, R102\n" // S002 = v[0] * in[0][2] + v[1] * in[1][2] + v[2] * in[2][2] +#endif + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ), "m"( *v ) + ); +#else out[0] = v[0] * in[0][0] + v[1] * in[1][0] + v[2] * in[2][0]; out[1] = v[0] * in[0][1] + v[1] * in[1][1] + v[2] * in[2][1]; out[2] = v[0] * in[0][2] + v[1] * in[1][2] + v[2] * in[2][2]; +#endif } void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in1[0] + "lv.q C110, 16 + %1\n" // C110 = in1[1] + "lv.q C120, 32 + %1\n" // C120 = in1[2] + "vzero.q C130\n" // C130 = [0, 0, 0, 0] + "lv.q C200, 0 + %2\n" // C100 = in2[0] + "lv.q C210, 16 + %2\n" // C110 = in2[1] + "lv.q C220, 32 + %2\n" // C120 = in2[2] + "vidt.q C230\n" // C230 = [0, 0, 0, 1] + "vmmul.q E000, E100, E200\n" // E000 = E100 * E200 + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in1 ), "m"( *in2 ) + ); +#else out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0]; out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1]; out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2]; @@ -81,6 +204,7 @@ void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matri out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1]; out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2]; out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3]; +#endif } void Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out ) @@ -105,6 +229,31 @@ void Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out ) void Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C130, %1\n" // C130 = quaternion + "lv.s S300, 0 + %2\n" // S300 = origin[0] + "lv.s S301, 4 + %2\n" // S301 = origin[1] + "lv.s S302, 8 + %2\n" // S302 = origin[2] + "vmov.q C100, C130[ W, Z, -Y, -X]\n" // C100 = ( w, z, -y, -x) + "vmov.q C110, C130[-Z, W, X, -Y]\n" // C110 = (-z, w, x, -y) + "vmov.q C120, C130[ Y, -X, W, -Z]\n" // C120 = ( y, -x, w, -z) + "vmov.q C200, C130[ W, Z, -Y, X]\n" // C200 = ( w, z, -y, x) + "vmov.q C210, C130[-Z, W, X, Y]\n" // C210 = (-z, w, x, y) + "vmov.q C220, C130[ Y, -X, W, Z]\n" // C220 = ( y, -x, w, z) + "vmov.q C230, C130[-X, -Y, -Z, W]\n" // C230 = (-x, -y, -z, w) + "vmmul.q M000, E100, E200\n" // M000 = E100 * E200 + "vmov.t R003, C300\n" // out[x][3] = origin[x] + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *quaternion ), "m"( *origin ) + ); +#else out[0][0] = 1.0f - 2.0f * quaternion[1] * quaternion[1] - 2.0f * quaternion[2] * quaternion[2]; out[1][0] = 2.0f * quaternion[0] * quaternion[1] + 2.0f * quaternion[3] * quaternion[2]; out[2][0] = 2.0f * quaternion[0] * quaternion[2] - 2.0f * quaternion[3] * quaternion[1]; @@ -120,10 +269,166 @@ void Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec out[0][3] = origin[0]; out[1][3] = origin[1]; out[2][3] = origin[2]; +#endif } void Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_t origin, float scale ) { +#if 0/*XASH_PSP*/ /* performance not tested */ /* BUG */ + if( angles[ROLL] ) + { + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S100, 0 + %1\n" // S100 = angles[PITCH] + "lv.s S101, 4 + %1\n" // S101 = angles[YAW] + "lv.s S102, 8 + %1\n" // S102 = angles[ROLL] + "lv.s S003, 0 + %2\n" // S003 = out[0][3] = origin[0] + "lv.s S013, 4 + %2\n" // S013 = out[1][3] = origin[1] + "lv.s S023, 8 + %2\n" // S023 = out[2][3] = origin[2] + "lv.s S130, %3\n" // S130 = scale + /**/ + "vfim.s S120, 0.0111111111111111\n" // S121 = 0.0111111111111111 const ( 2 / 180 ) + "vscl.t C100, C100, S120\n" // C100 = C100 * S120 = angles * ( 2 / 180 ) + /**/ + "vsin.t C110, C100\n" // C110 = sin( C100 ) P Y R + "vcos.t C120, C100\n" // C120 = cos( C100 ) P Y R + "vneg.t C100, C110\n" // C100 = -C110 = -sin( C100 ) + /**/ + "vmul.s S000, S120, S121\n" // S000 = S120 * S121 = out[0][0] = ( cp * cy ) + "vmul.s S011, S112, S110\n" // S011 = S112 * S110 = ( sr * sp ) + "vmul.s S001, S011, S121\n" // S001 = S011 * S121 = ( sr * sp * cy ) + "vmul.s S103, S122, S101\n" // S001 = S122 * S101 = ( cr * -sy ) + "vadd.s S001, S001, S103\n" // S001 = S001 + S103 = out[0][1] = ( sr * sp * cy + cr * -sy ) + "vmul.s S012, S122, S110\n" // S002 = S122 * S110 = ( cr * sp ) + "vmul.s S002, S012, S121\n" // S002 = S012 * S121 = ( cr * sp * cy ) + "vmul.s S103, S102, S101\n" // S002 = S102 * S101 = ( -sr * -sy ) + "vadd.s S002, S002, S103\n" // S002 = S002 + S103 = out[0][2] = ( cr * sp * cy + -sr * -sy ) + /**/ + "vmul.s S010, S120, S111\n" // S010 = S120 * S111 = out[1][0] = ( cp * sy ) + "vmul.s S011, S011, S111\n" // S001 = S011 * S111 = ( sr * sp * sy ) + "vmul.s S103, S122, S121\n" // S001 = S122 * S121 = ( cr * cy ) + "vadd.s S011, S011, S103\n" // S011 = S011 + S103 = out[1][1] = ( sr * sp * sy + cr * cy ) + "vmul.s S012, S012, S111\n" // S012 = S012 * S111 = ( cr * sp * sy ) + "vmul.s S103, S102, S121\n" // S103 = S102 * S121 = ( -sr * cy ) + "vadd.s S012, S012, S103\n" // S012 = S012 + S103 = out[1][2] = ( cr * sp * sy + -sr * cy ) + /**/ + "vmov.s S020, S101\n" // S020 = S101 = out[2][0] = ( -sp ) + "vmul.s S021, S112, S120\n" // S021 = S112 * S120 = out[2][1] = ( sr * cp ) + "vmul.s S022, S122, S120\n" // S021 = S122 * S120 = out[2][2] = ( cr * cp ) + /**/ + "vmscl.t E000, E000, S130\n" // E000 = E000 * S103 = out(3) * scale + /**/ + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *angles ), "m"( *origin ), "m"( scale ) + ); + } + else if( angles[PITCH] ) + { + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S100, 0 + %1\n" // S100 = angles[PITCH] + "lv.s S101, 4 + %1\n" // S101 = angles[YAW] + "lv.s S003, 0 + %2\n" // S003 = out[0][3] = origin[0] + "lv.s S013, 4 + %2\n" // S013 = out[1][3] = origin[1] + "lv.s S023, 8 + %2\n" // S023 = out[2][3] = origin[2] + "lv.s S130, %3\n" // S130 = scale + /**/ + "vfim.s S120, 0.0111111111111111\n" // S121 = 0.0111111111111111 const ( 2 / 180 ) + "vscl.p C100, C100, S120\n" // C100 = C100 * S120 = angles * ( 2 / 180 ) + /**/ + "vsin.p C110, C100\n" // C110 = sin( C100 ) P Y + "vcos.p C120, C100\n" // C120 = cos( C100 ) P Y + "vneg.p C100, C110\n" // C100 = -C110 = -sin( C100 ) + /**/ + "vmul.s S000, S120, S121\n" // S000 = S120 * S121 = out[0][0] = ( cp * cy ) + "vmov.s S001, S101\n" // S001 = S101 = out[0][1] = ( -sy ) + "vmul.s S002, S110, S121\n" // S001 = S110 * S121 = out[0][2] = ( sp * cy ) + /**/ + "vmul.s S010, S120, S111\n" // S010 = S120 * S111 = out[1][0] = ( cp * sy ) + "vmov.s S011, S121\n" // S011 = S121 = out[1][1] = ( cy ) + "vmul.s S012, S110, S111\n" // S012 = S110 * S111 = out[1][2] = ( sp * sy ) + /**/ + "vmov.s S020, S100\n" // S020 = S100 = out[2][0] = ( -sp ) + "vzero.s S021\n" // S021 = out[2][1] = 0.0f + "vmov.s S022, S120\n" // S022 = S120 = out[2][2] = ( cp ) + /**/ + "vmscl.t E000, E000, S130\n" // E000 = E000 * S103 = out(3) * scale + /**/ + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *angles ), "m"( *origin ), "m"( scale ) + ); + } + else if( angles[YAW] ) + { + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S101, 4 + %1\n" // S101 = angles[YAW] + "lv.s S003, 0 + %2\n" // S003 = out[0][3] = origin[0] + "lv.s S013, 4 + %2\n" // S013 = out[1][3] = origin[1] + "lv.s S023, 8 + %2\n" // S023 = out[2][3] = origin[2] + "lv.s S130, %3\n" // S130 = scale + /**/ + "vfim.s S120, 0.0111111111111111\n" // S121 = 0.0111111111111111 const ( 2 / 180 ) + "vmul.s S101, S101, S120\n" // S101 = S101 * S120 = angles[YAW] * ( 2 / 180 ) + /**/ + "vsin.s S111, S101\n" // S111 = sin( S101 ) Y + "vcos.s S121, S101\n" // S121 = cos( S101 ) Y + /**/ + "vzero.p R002\n" // S002 = 0.0f S012 = 0.0f + "vzero.p C020\n" // S020 = 0.0f S021 = 0.0f + "vmov.s S000, S121\n" // S000 = S121 = out[0][0] = ( cy ) + "vneg.s S001, S111\n" // S001 = S111 = out[0][1] = ( -sy ) + "vmov.s S010, S111\n" // S010 = S111 = out[1][0] = ( sy ) + "vmov.s S011, S121\n" // S011 = S121 = out[1][1] = ( cy ) + "vone.s S022\n" // S022 = out[2][2] = 1.0f + /**/ + "vmscl.t E000, E000, S130\n" // E000 = E000 * S103 = out(3) * scale + /**/ + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *angles ), "m"( *origin ), "m"( scale ) + ); + } + else + { + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S003, 0 + %1\n" // S003 = out[0][3] = origin[0] + "lv.s S013, 4 + %1\n" // S013 = out[1][3] = origin[1] + "lv.s S023, 8 + %1\n" // S023 = out[2][3] = origin[2] + "lv.s S130, %2\n" // S130 = scale + /**/ + "vzero.t C000\n" // C000 = [0.0f, 0.0f, 0.0f] + "vzero.t C010\n" // C010 = [0.0f, 0.0f, 0.0f] + "vzero.t C020\n" // C020 = [0.0f, 0.0f, 0.0f] + "vmov.s S000, S130\n" // S000 = S130 = out[0][0] = scale + "vmov.s S011, S130\n" // S011 = S130 = out[1][1] = scale + "vmov.s S022, S130\n" // S022 = S130 = out[2][2] = scale + /**/ + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *origin ), "m"( scale ) + ); + } +#else float angle, sr, sp, sy, cr, cp, cy; if( angles[ROLL] ) @@ -201,6 +506,7 @@ void Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_ out[2][2] = scale; out[2][3] = origin[2]; } +#endif } /* @@ -242,13 +548,61 @@ const matrix4x4 m_matrix4x4_identity = */ void Matrix4x4_VectorTransform( const matrix4x4 in, const float v[3], float out[3] ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in[0] + "lv.q C110, 16 + %1\n" // C110 = in[1] + "lv.q C120, 32 + %1\n" // C120 = in[2] + "lv.s S130, 0 + %2\n" // S130 = v[0] + "lv.s S131, 4 + %2\n" // S131 = v[1] + "lv.s S132, 8 + %2\n" // S132 = v[2] + "vhdp.q S000, C130, C100\n" // S000 = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + in[0][3] + "vhdp.q S001, C130, C110\n" // S001 = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + in[1][3] + "vhdp.q S002, C130, C120\n" // S002 = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] + in[2][3] + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ), "m"( *v ) + ); +#else out[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + in[0][3]; out[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + in[1][3]; out[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] + in[2][3]; +#endif } void Matrix4x4_VectorITransform( const matrix4x4 in, const float v[3], float out[3] ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in[0] + "lv.q C110, 16 + %1\n" // C110 = in[1] + "lv.q C120, 32 + %1\n" // C120 = in[2] + "lv.s S130, 0 + %2\n" // S130 = v[0] + "lv.s S131, 4 + %2\n" // S131 = v[1] + "lv.s S132, 8 + %2\n" // S132 = v[2] + "vsub.t C130, C130, R103\n" // C130 = v - in[][3] +#if 1 + "vtfm3.t C000, E100, C130\n" // C000 = E100 * C130 +#else + "vdot.t S000, C130, R100\n" // S000 = dir[0] * in[0][0] + dir[1] * in[1][0] + dir[2] * in[2][0] + "vdot.t S001, C130, R101\n" // S001 = dir[0] * in[0][1] + dir[1] * in[1][1] + dir[2] * in[2][1] + "vdot.t S002, C130, R102\n" // S002 = dir[0] * in[0][2] + dir[1] * in[1][2] + dir[2] * in[2][2] +#endif + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ), "m"( *v ) + ); +#else vec3_t dir; dir[0] = v[0] - in[0][3]; @@ -258,24 +612,99 @@ void Matrix4x4_VectorITransform( const matrix4x4 in, const float v[3], float out out[0] = dir[0] * in[0][0] + dir[1] * in[1][0] + dir[2] * in[2][0]; out[1] = dir[0] * in[0][1] + dir[1] * in[1][1] + dir[2] * in[2][1]; out[2] = dir[0] * in[0][2] + dir[1] * in[1][2] + dir[2] * in[2][2]; +#endif } void Matrix4x4_VectorRotate( const matrix4x4 in, const float v[3], float out[3] ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in[0] + "lv.q C110, 16 + %1\n" // C110 = in[1] + "lv.q C120, 32 + %1\n" // C120 = in[2] + "lv.s S130, 0 + %2\n" // S130 = v[0] + "lv.s S131, 4 + %2\n" // S131 = v[1] + "lv.s S132, 8 + %2\n" // S132 = v[2] +#if 1 + "vtfm3.t C000, M100, C130\n" // C000 = M100 * C130 +#else + "vdot.t S000, C130, C100\n" // S000 = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + "vdot.t S001, C130, C110\n" // S001 = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + "vdot.t S002, C130, C120\n" // S002 = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] +#endif + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ), "m"( *v ) + ); +#else out[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2]; out[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2]; out[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2]; +#endif } void Matrix4x4_VectorIRotate( const matrix4x4 in, const float v[3], float out[3] ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in[0] + "lv.q C110, 16 + %1\n" // C110 = in[1] + "lv.q C120, 32 + %1\n" // C120 = in[2] + "lv.s S130, 0 + %2\n" // S130 = v[0] + "lv.s S131, 4 + %2\n" // S131 = v[1] + "lv.s S132, 8 + %2\n" // S132 = v[2] +#if 1 + "vtfm3.t C000, E100, C130\n" // C000 = E100 * C130 +#else + "vdot.t S000, C130, R100\n" // S000 = v[0] * in[0][0] + v[1] * in[1][0] + v[2] * in[2][0] + "vdot.t S001, C130, R101\n" // S001 = v[0] * in[0][1] + v[1] * in[1][1] + v[2] * in[2][1] + "vdot.t S002, C130, R102\n" // S002 = v[0] * in[0][2] + v[1] * in[1][2] + v[2] * in[2][2] +#endif + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ), "m"( *v ) + ); +#else out[0] = v[0] * in[0][0] + v[1] * in[1][0] + v[2] * in[2][0]; out[1] = v[0] * in[0][1] + v[1] * in[1][1] + v[2] * in[2][1]; out[2] = v[0] * in[0][2] + v[1] * in[1][2] + v[2] * in[2][2]; +#endif + } void Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in1[0] + "lv.q C110, 16 + %1\n" // C110 = in1[1] + "lv.q C120, 32 + %1\n" // C120 = in1[2] + "vzero.q C130\n" // C130 = [0, 0, 0, 0] + "lv.q C200, 0 + %2\n" // C100 = in2[0] + "lv.q C210, 16 + %2\n" // C110 = in2[1] + "lv.q C220, 32 + %2\n" // C120 = in2[2] + "vidt.q C230\n" // C230 = [0, 0, 0, 1] + "vmmul.q E000, E100, E200\n" // E000 = E100 * E200 + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in1 ), "m"( *in2 ) + ); +#else out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0]; out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1]; out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2]; @@ -288,10 +717,174 @@ void Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matri out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1]; out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2]; out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3]; +#endif } void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale ) { +#if 0/*XASH_PSP*/ /* performance not tested */ /* BUG */ + if( angles[ROLL] ) + { + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S100, 0 + %1\n" // S100 = angles[PITCH] + "lv.s S101, 4 + %1\n" // S101 = angles[YAW] + "lv.s S102, 8 + %1\n" // S102 = angles[ROLL] + "lv.s S003, 0 + %2\n" // S003 = out[0][3] = origin[0] + "lv.s S013, 4 + %2\n" // S013 = out[1][3] = origin[1] + "lv.s S023, 8 + %2\n" // S023 = out[2][3] = origin[2] + "lv.s S130, %3\n" // S130 = scale + /**/ + "vfim.s S120, 0.0111111111111111\n" // S121 = 0.0111111111111111 const ( 2 / 180 ) + "vscl.t C100, C100, S120\n" // C100 = C100 * S120 = angles * ( 2 / 180 ) + /**/ + "vsin.t C110, C100\n" // C110 = sin( C100 ) P Y R + "vcos.t C120, C100\n" // C120 = cos( C100 ) P Y R + "vneg.t C100, C110\n" // C100 = -C110 = -sin( C100 ) + /**/ + "vmul.s S000, S120, S121\n" // S000 = S120 * S121 = out[0][0] = ( cp * cy ) + "vmul.s S011, S112, S110\n" // S011 = S112 * S110 = ( sr * sp ) + "vmul.s S001, S011, S121\n" // S001 = S011 * S121 = ( sr * sp * cy ) + "vmul.s S103, S122, S101\n" // S001 = S122 * S101 = ( cr * -sy ) + "vadd.s S001, S001, S103\n" // S001 = S001 + S103 = out[0][1] = ( sr * sp * cy + cr * -sy ) + "vmul.s S012, S122, S110\n" // S002 = S122 * S110 = ( cr * sp ) + "vmul.s S002, S012, S121\n" // S002 = S012 * S121 = ( cr * sp * cy ) + "vmul.s S103, S102, S101\n" // S002 = S102 * S101 = ( -sr * -sy ) + "vadd.s S002, S002, S103\n" // S002 = S002 + S103 = out[0][2] = ( cr * sp * cy + -sr * -sy ) + /**/ + "vmul.s S010, S120, S111\n" // S010 = S120 * S111 = out[1][0] = ( cp * sy ) + "vmul.s S011, S011, S111\n" // S001 = S011 * S111 = ( sr * sp * sy ) + "vmul.s S103, S122, S121\n" // S001 = S122 * S121 = ( cr * cy ) + "vadd.s S011, S011, S103\n" // S011 = S011 + S103 = out[1][1] = ( sr * sp * sy + cr * cy ) + "vmul.s S012, S012, S111\n" // S012 = S012 * S111 = ( cr * sp * sy ) + "vmul.s S103, S102, S121\n" // S103 = S102 * S121 = ( -sr * cy ) + "vadd.s S012, S012, S103\n" // S012 = S012 + S103 = out[1][2] = ( cr * sp * sy + -sr * cy ) + /**/ + "vmov.s S020, S101\n" // S020 = S101 = out[2][0] = ( -sp ) + "vmul.s S021, S112, S120\n" // S021 = S112 * S120 = out[2][1] = ( sr * cp ) + "vmul.s S022, S122, S120\n" // S021 = S122 * S120 = out[2][2] = ( cr * cp ) + /**/ + "vmscl.t E000, E000, S130\n" // E000 = E000 * S103 = out(3) * scale + "vidt.q C030\n" // C030 = [0.0f, 0.0f, 0.0f, 1.0f] + /**/ + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + "sv.q C030, 48 + %0\n" // out[3] = C030 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *angles ), "m"( *origin ), "m"( scale ) + ); + } + else if( angles[PITCH] ) + { + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S100, 0 + %1\n" // S100 = angles[PITCH] + "lv.s S101, 4 + %1\n" // S101 = angles[YAW] + "lv.s S003, 0 + %2\n" // S003 = out[0][3] = origin[0] + "lv.s S013, 4 + %2\n" // S013 = out[1][3] = origin[1] + "lv.s S023, 8 + %2\n" // S023 = out[2][3] = origin[2] + "lv.s S130, %3\n" // S130 = scale + /**/ + "vfim.s S120, 0.0111111111111111\n" // S121 = 0.0111111111111111 const ( 2 / 180 ) + "vscl.p C100, C100, S120\n" // C100 = C100 * S120 = angles * ( 2 / 180 ) + /**/ + "vsin.p C110, C100\n" // C110 = sin( C100 ) P Y + "vcos.p C120, C100\n" // C120 = cos( C100 ) P Y + "vneg.p C100, C110\n" // C100 = -C110 = -sin( C100 ) + /**/ + "vmul.s S000, S120, S121\n" // S000 = S120 * S121 = out[0][0] = ( cp * cy ) + "vmov.s S001, S101\n" // S001 = S101 = out[0][1] = ( -sy ) + "vmul.s S002, S110, S121\n" // S001 = S110 * S121 = out[0][2] = ( sp * cy ) + /**/ + "vmul.s S010, S120, S111\n" // S010 = S120 * S111 = out[1][0] = ( cp * sy ) + "vmov.s S011, S121\n" // S011 = S121 = out[1][1] = ( cy ) + "vmul.s S012, S110, S111\n" // S012 = S110 * S111 = out[1][2] = ( sp * sy ) + /**/ + "vmov.s S020, S100\n" // S020 = S100 = out[2][0] = ( -sp ) + "vzero.s S021\n" // S021 = out[2][1] = 0.0f + "vmov.s S022, S120\n" // S022 = S120 = out[2][2] = ( cp ) + /**/ + "vmscl.t E000, E000, S130\n" // E000 = E000 * S103 = out(3) * scale + "vidt.q C030\n" // C030 = [0.0f, 0.0f, 0.0f, 1.0f] + /**/ + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + "sv.q C030, 48 + %0\n" // out[3] = C030 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *angles ), "m"( *origin ), "m"( scale ) + ); + } + else if( angles[YAW] ) + { + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S101, 4 + %1\n" // S101 = angles[YAW] + "lv.s S003, 0 + %2\n" // S003 = out[0][3] = origin[0] + "lv.s S013, 4 + %2\n" // S013 = out[1][3] = origin[1] + "lv.s S023, 8 + %2\n" // S023 = out[2][3] = origin[2] + "lv.s S130, %3\n" // S130 = scale + /**/ + "vfim.s S120, 0.0111111111111111\n" // S121 = 0.0111111111111111 const ( 2 / 180 ) + "vmul.s S101, S101, S120\n" // S101 = S101 * S120 = angles[YAW] * ( 2 / 180 ) + /**/ + "vsin.s S111, S101\n" // S111 = sin( S101 ) Y + "vcos.s S121, S101\n" // S121 = cos( S101 ) Y + /**/ + "vzero.p R002\n" // S002 = 0.0f S012 = 0.0f + "vzero.p C020\n" // S020 = 0.0f S021 = 0.0f + "vmov.s S000, S121\n" // S000 = S121 = out[0][0] = ( cy ) + "vneg.s S001, S111\n" // S001 = S111 = out[0][1] = ( -sy ) + "vmov.s S010, S111\n" // S010 = S111 = out[1][0] = ( sy ) + "vmov.s S011, S121\n" // S011 = S121 = out[1][1] = ( cy ) + "vone.s S022\n" // S022 = out[2][2] = 1.0f + /**/ + "vmscl.t E000, E000, S130\n" // E000 = E000 * S103 = out(3) * scale + "vidt.q C030\n" // C030 = [0.0f, 0.0f, 0.0f, 1.0f] + /**/ + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + "sv.q C030, 48 + %0\n" // out[3] = C030 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *angles ), "m"( *origin ), "m"( scale ) + ); + } + else + { + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S003, 0 + %1\n" // S003 = out[0][3] = origin[0] + "lv.s S013, 4 + %1\n" // S013 = out[1][3] = origin[1] + "lv.s S023, 8 + %1\n" // S023 = out[2][3] = origin[2] + "lv.s S130, %2\n" // S130 = scale + /**/ + "vzero.t C000\n" // C000 = [0.0f, 0.0f, 0.0f] + "vzero.t C010\n" // C010 = [0.0f, 0.0f, 0.0f] + "vzero.t C020\n" // C020 = [0.0f, 0.0f, 0.0f] + "vidt.q C030\n" // C030 = [0.0f, 0.0f, 0.0f, 1.0f] + "vmov.s S000, S130\n" // S000 = S130 = out[0][0] = scale + "vmov.s S011, S130\n" // S011 = S130 = out[1][1] = scale + "vmov.s S022, S130\n" // S022 = S130 = out[2][2] = scale + /**/ + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + "sv.q C030, 48 + %0\n" // out[3] = C030 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *origin ), "m"( scale ) + ); + } +#else float angle, sr, sp, sy, cr, cp, cy; if( angles[ROLL] ) @@ -385,6 +978,7 @@ void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_ out[3][2] = 0.0f; out[3][3] = 1.0f; } +#endif } void Matrix4x4_ConvertToEntity( const matrix4x4 in, vec3_t angles, vec3_t origin ) @@ -412,6 +1006,36 @@ void Matrix4x4_ConvertToEntity( const matrix4x4 in, vec3_t angles, vec3_t origin void Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "mfc1 $8, %4\n" // FPU->CPU + "mtv $8, S210\n" // CPU->VFPU S210 = d + "lv.q C100, 0 + %2\n" // C100 = in[0] + "lv.q C110, 16 + %2\n" // C110 = in[1] + "lv.q C120, 32 + %2\n" // C120 = in[2] + "lv.s S200, 0 + %3\n" // S200 = normal[0] + "lv.s S201, 4 + %3\n" // S201 = normal[1] + "lv.s S202, 8 + %3\n" // S202 = normal[2] + "vdot.t S211, C100, C100\n" // S211 = C100 * C100 + "vsqrt.s S211, S211\n" // S211 = sqrt( S211 ) + "vrcp.s S212, S211\n" // S212 = 1 / S211 + "vtfm3.t C000, M100, C200\n" // C000 = M100 * C200 + "vscl.t C000, C000, S212\n" // C000 = C000 * S211 + "vmul.s S003, S210, S211\n" // S003 = S210 * S211 + "vdot.t S010, R103, C000\n" // S010 = R103 * C000 + "vadd.s S003, S003, S010\n" // S003 = S003 + S010 + "sv.s S000, 0 + %0\n" // out[0] = S000 + "sv.s S001, 4 + %0\n" // out[1] = S001 + "sv.s S002, 8 + %0\n" // out[2] = S002 + "sv.s S003, %1\n" // dist = S003 + ".set pop\n" // restore assembler option + : "=m"( *out ), "=m"( *dist ) + : "m"( *in ), "m"( *normal ), "f"( d ) + : "$8" + ); +#else float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); float iscale = 1.0f / scale; @@ -419,6 +1043,7 @@ void Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, out[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale; out[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale; *dist = d * scale + ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); +#endif } void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ) diff --git a/public/rbtree.h b/public/rbtree.h new file mode 100644 index 000000000..e69de29bb diff --git a/public/wscript b/public/wscript index 4f7b24f4e..76fe36323 100644 --- a/public/wscript +++ b/public/wscript @@ -16,7 +16,7 @@ def configure(conf): def build(bld): bld(name = 'sdk_includes', export_includes = '. ../common ../pm_shared ../engine') - bld.stlib(source = bld.path.ant_glob('*.c'), + bld.stlib(source = bld.path.ant_glob('*.c') + bld.path.ant_glob('*.S'), target = 'public', features = 'c', use = 'sdk_includes', diff --git a/public/xash3d_mathlib.c b/public/xash3d_mathlib.c index 36705c3c1..f0b791244 100644 --- a/public/xash3d_mathlib.c +++ b/public/xash3d_mathlib.c @@ -209,6 +209,30 @@ rsqrt */ float rsqrt( float number ) { +#if XASH_PSP +#if 0 /* experimental */ + if( number == 0.0f ) return 0.0f; + return ( 1.0 / __builtin_allegrex_sqrt_s( number )); +#else + float result; + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "mfc1 $8, %1\n" // FPU->CPU + "mtv $8, S000\n" // CPU->VFPU S000 = number + "vzero.s S001\n" // S100 = 0 + "vcmp.s EZ, S000\n" // CC[0] = ( S000 == 0.0f ) + "vrsq.s S000, S000\n" // S000 = 1.0 / sqrt( S000 ) + "vcmovt.s S000, S001, 0\n" // if ( CC[0] ) S000 = S001 + "sv.s S000, %0\n" // result = S000 + ".set pop\n" // restore assembler option + : "=m"( result ) + : "f"( number ) + : "$8" + ); + return result; +#endif +#else int i; float x, y; @@ -222,6 +246,7 @@ float rsqrt( float number ) y = y * (1.5f - (x * y * y)); // first iteration return y; +#endif } /* @@ -384,6 +409,7 @@ void VectorsAngles( const vec3_t forward, const vec3_t right, const vec3_t up, v // // bounds operations // + /* ================= AddPointToBounds @@ -485,6 +511,46 @@ AngleQuaternion */ void AngleQuaternion( const vec3_t angles, vec4_t q, qboolean studio ) { +#if XASH_PSP + vec4_t dst_angles; + if( studio ) + { + dst_angles[0] = angles[PITCH]; + dst_angles[1] = angles[YAW]; + dst_angles[2] = angles[ROLL]; + } + else + { + dst_angles[0] = DEG2RAD( angles[ROLL] ); + dst_angles[1] = DEG2RAD( angles[PITCH] ); + dst_angles[2] = DEG2RAD( angles[YAW] ); + } + + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C000, %1\n" // C000 = [PITCH, YAW, ROLL] + "vcst.s S010, VFPU_1_PI\n" // S010 = VFPU_1_PI = 1 / PI + "vscl.t C000, C000, S010\n" // C000 = C000 * S010 = C000 * 0.5f * ( 2 / PI ) + "vcos.t C010, C000\n" // C010 = cos( C000 * 0.5f ) + "vsin.t C000, C000\n" // C000 = sin( C000 * 0.5f ) + "vcrs.t C020, C010, C010\n" // C020 = ( cp*cy, cy*cr, cr*cp ) + "vcrs.t C030, C000, C000\n" // C030 = ( sp*sy, sy*sr, sr*sp ) + "vmul.s S003, S020, S010\n" // S003 = S020 * S010 = cp*cy*cr + "vmul.s S013, S030, S000\n" // S013 = S030 * S000 = sp*sy*sr + "vmul.t C020, C020, C000\n" // C020 = C020 * C000 = ( cp*cy*sr, cy*cr*sp, cr*cp*sy ) + "vmul.t C030, C030, C010\n" // C030 = C030 * C010 = ( sp*sy*cr, sy*sr*cp, sr*sp*cy ) + "vadd.s S003, S003, S013\n" // S003 = S003 + S013 = cp*cy*cr + cp*cy*cr + "vadd.t C000, C020, C030[-X, Y, -Z]\n" + // S000 = S020 - C030 = cp*cy*sr - sp*sy*cr + // S001 = S021 + C031 = cy*cr*sp + sy*sr*cp + // S002 = S022 - C032 = cr*cp*sy - sr*sp*cy + "sv.q C000, %0\n" // *q = C000 + ".set pop\n" // restore assembler option + : "=m"( *q ) + : "m"( dst_angles ) + ); +#else float sr, sp, sy, cr, cp, cy; if( studio ) @@ -504,6 +570,7 @@ void AngleQuaternion( const vec3_t angles, vec4_t q, qboolean studio ) q[1] = cr * sp * cy + sr * cp * sy; // Y q[2] = cr * cp * sy - sr * sp * cy; // Z q[3] = cr * cp * cy + sr * sp * sy; // W +#endif } /* @@ -527,6 +594,7 @@ make sure quaternions are within 180 degrees of one another, if not, reverse q ==================== */ +#if !XASH_PSP void QuaternionAlign( const vec4_t p, const vec4_t q, vec4_t qt ) { // decide if one of the quaternions is backwards @@ -615,9 +683,9 @@ void QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt ) // 0.0 returns p, 1.0 return q. // decide if one of the quaternions is backwards QuaternionAlign( p, q, q2 ); - QuaternionSlerpNoAlign( p, q2, t, qt ); } +#endif // XASH_PSP /* ================== @@ -626,10 +694,11 @@ BoxOnPlaneSide Returns 1, 2, or 1 + 2 ================== */ +#if !XASH_PSP int BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p ) { - float dist1, dist2; int sides = 0; + float dist1, dist2; // general case switch( p->signbits ) @@ -679,6 +748,7 @@ int BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p ) return sides; } +#endif // !XASH_PSP /* ==================== diff --git a/public/xash3d_mathlib.h b/public/xash3d_mathlib.h index ce0a58c52..94c49bb21 100644 --- a/public/xash3d_mathlib.h +++ b/public/xash3d_mathlib.h @@ -175,8 +175,26 @@ static inline float UintAsFloat( uint32_t u ) static inline void SinCos( float radians, float *sine, float *cosine ) { +#if XASH_PSP + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "mfc1 $8, %2\n" // FPU->CPU + "mtv $8, S000\n" // CPU->VFPU S000 = radians + "vcst.s S001, VFPU_2_PI\n" // S001 = VFPU_2_PI = 2 / PI + "vmul.s S000, S000, S001\n" // S000 = S000 * S001 + "vrot.p C002, S000, [s, c]\n" // S002 = sin( radians ), S003 = cos( radians ) + "sv.s S002, %0\n" // sine = S002 + "sv.s S003, %1\n" // cosine = S003 + ".set pop\n" // restore assembler option + : "=m"( *sine), "=m"( *cosine ) + : "f"( radians ) + : "$8" + ); +#else *sine = sin(radians); *cosine = cos(radians); +#endif } float rsqrt( float number ); diff --git a/public/xash3d_mathlib_asm.S b/public/xash3d_mathlib_asm.S new file mode 100644 index 000000000..833d03e71 --- /dev/null +++ b/public/xash3d_mathlib_asm.S @@ -0,0 +1,218 @@ +/* +xash3d_mathlib_asm.S - internal mathlib ASM ver. +Copyright (C) 2022 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include "build.h" +#if XASH_PSP +#include "as_reg_compat.h" + .set noreorder + .set noat + + .text + .align 4 + .global QuaternionSlerp + .global BoxOnPlaneSide + +// void QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt ); + .ent QuaternionSlerp +QuaternionSlerp: + ############################ + # a0 - IN *p # + # a1 - IN *q # + # f12 - IN t # + # a2 - IN *qt # + ############################ + mfc1 $t0, $f12 + mtv $t0, S031 // S031 = sclq = t + li $t0, 0x358637bd // t0 = EPSILON = 0.000001f + mtv $t0, S033 // S002 = t0 = EPSILON + lv.q C010, 0($a0) // C010 = p + lv.q C020, 0($a1) // C020 = q + + // QuaternionAlign + vsub.q C100, C010, C020 // C100 = p[*] - q[*] + vadd.q C110, C010, C020 // C110 = p[*] + q[*] + vdot.q S000, C100, C100 // S000 = a += (p[*] - q[*]) * (p[*] - q[*]) + vdot.q S001, C110, C110 // S001 = b += (p[*] + q[*]) * (p[*] + q[*]) + vcmp.s GT, S000, S001 // CC[0] = a > b + vcmovt.q C020, C020[-X,-Y,-Z,-W], 0 // if CC[0] q = -q + // *** + + // QuaternionSlerpNoAlign + vdot.q S000, C010, C020 // S000 = cosom += p[*] * q[*] + vadd.s S001, S000[1], S000 // S002 = 1.0f + cosom + vcmp.s LE, S001, S033 // CC[0] = ( 1.0f + cosom ) <= 0.000001f + bvt 0, Lqs2 // if CC[0] goto Lqs2 + vocp.s S030, S031 // S030 = sclp = 1.0f - t (delay slot) + vsub.s S001, S000[1], S000 // S002 = 1.0f - cosom + vcmp.s LE, S001, S033 // CC[0] = ( 1.0f - cosom ) <= 0.000001f + bvt 0, Lqs1 + nop + + // acos + vcst.s S001, VFPU_SQRT1_2 // S001 = VFPU_SQRT1_2 = 1 / sqrt(2) + vcmp.s LT, S000[|x|], S001 // CC[0] = abs(cosom) < (1 / sqrt(2)) + vasin.s S032, S000[|x|] // S032 = asin(abs(cosom)) + bvtl 0, Lqs0 // if CC[0] goto Lqs0 + vocp.s S032, S032 // S032 = 1 - S032 = acos(abs(cosom)) = omega (bvtl delay slot) + vmul.s S001, S000, S000 // S001 = cosom * cosom + vocp.s S001[0:1], S001 // S001 = 1 - S001[0:1] + vsqrt.s S001, S001 // S001 = sqrt(S001) + vasin.s S032, S001 // S032 = asin(S001) = acos(abs(cosom)) = omega + // *** +Lqs0: + vscl.p C030, C030, S032 // S030 = S030 * S032 = sclp * omega + // S031 = S031 * S032 = sclq * omega + vsin.t C030, C030 // S030 = sin(S030) = sin(sclp * omega) = sclp + // S031 = sin(S031) = sin(sclq * omega) = sclq + // S032 = sin(S032) = sin(omega) + vrcp.s S032, S032 // S032 = 1.0f / S032 = 1 / sin(omega) = sinom + vscl.p C030, C030, S032 // S030 = S030 * S032 = sin(sclp * omega) / sinom + // S031 = S031 * S032 = sin(sclq * omega) / sinom +Lqs1: + vscl.q C010, C010, S030 // C010 = p[*4] * sclp + vscl.q C020, C020, S031 // C020 = qt[*4] * sclq + b LqsEnd // goto LqsEnd + vadd.q C000, C010, C020 // S000 = qt[0] = sclp * p[0] + sclq * qt[0] (delay slot) + // S001 = qt[1] = sclp * p[1] + sclq * qt[1] + // S002 = qt[2] = sclp * p[2] + sclq * qt[2] + // S003 = qt[3] = sclp * p[3] + sclq * qt[3] +Lqs2: + vmov.q C000, C020[-Y,X,-W,Z] // S000 = qt[0] = -q[1]; + // S001 = qt[1] = q[0]; + // S002 = qt[2] = -q[3]; + // S003 = qt[3] = q[2]; + vsin.p C030, C030 // S030 = sclp = sin(( 1.0f - t ) * ( 0.5f * M_PI_F )) + // S031 = sclq = sin( t * ( 0.5f * M_PI_F )) + vscl.t C010, C010, S030 // C000 = p[*3] * sclp + vscl.t C020, C000, S031 // C030 = qt[*3] * sclq + vadd.t C000, C010, C020 // S000 = qt[0] = sclp * p[0] + sclq * qt[0] + // S001 = qt[1] = sclp * p[1] + sclq * qt[1] + // S002 = qt[2] = sclp * p[2] + sclq * qt[2] + // S003 = qt[3] +LqsEnd: + sv.q C000, 0($a2) + jr $ra + .end QuaternionSlerp + + + + +// int BoxOnPlaneSide( vec3_t emins, vec3_t emaxs, mplane_t *p ); + .ent BoxOnPlaneSide +BoxOnPlaneSide: + ############################ + # a0 - IN *emins # + # a1 - IN *emaxs # + # a2 - IN *p # + # v0 - OUT sides # + ############################ + lbu $v0, 17($a2) // p->signbits + sltiu $v1, $v0, 8 // if(signbits > 8) + beq $v1, $zero, LSetSides // jump to LSetSides + vzero.p C030 // set zero vector + sll $v1, $v0, 2 + la $v0, Ljmptab + addu $v0, $v0, $v1 + lw $v0, 0($v0) + lv.s S000, 0($a2) // p->normal[0] + lv.s S001, 4($a2) // p->normal[1] + jr $v0 + lv.s S002, 8($a2) // p->normal[2] +Lcase0: + lv.s S010, 0($a1) // emaxs[0] + lv.s S011, 4($a1) // emaxs[1] + lv.s S012, 8($a1) // emaxs[2] + lv.s S020, 0($a0) // emins[0] + lv.s S021, 4($a0) // emins[1] + b LDotProduct + lv.s S022, 8($a0) // emins[2] +Lcase1: + lv.s S010, 0($a0) // emins[0] + lv.s S011, 4($a1) // emaxs[1] + lv.s S012, 8($a1) // emaxs[2] + lv.s S020, 0($a1) // emaxs[0] + lv.s S021, 4($a0) // emins[1] + b LDotProduct + lv.s S022, 8($a0) // emins[2] +Lcase2: + lv.s S010, 0($a1) // emaxs[0] + lv.s S011, 4($a0) // emins[1] + lv.s S012, 8($a1) // emaxs[2] + lv.s S020, 0($a0) // emins[0] + lv.s S021, 4($a1) // emaxs[1] + b LDotProduct + lv.s S022, 8($a0) // emins[2] +Lcase3: + lv.s S010, 0($a0) // emins[0] + lv.s S011, 4($a0) // emins[1] + lv.s S012, 8($a1) // emaxs[2] + lv.s S020, 0($a1) // emaxs[0] + lv.s S021, 4($a1) // emaxs[1] + b LDotProduct + lv.s S022, 8($a0) // emins[2] +Lcase4: + lv.s S010, 0($a1) // emaxs[0] + lv.s S011, 4($a1) // emaxs[1] + lv.s S012, 8($a0) // emins[2] + lv.s S020, 0($a0) // emins[0] + lv.s S021, 4($a0) // emins[1] + b LDotProduct + lv.s S022, 8($a1) // emaxs[2] +Lcase5: + lv.s S010, 0($a0) // emins[0] + lv.s S011, 4($a1) // emaxs[1] + lv.s S012, 8($a0) // emins[2] + lv.s S020, 0($a1) // emaxs[0] + lv.s S021, 4($a0) // emins[1] + b LDotProduct + lv.s S022, 8($a1) // emaxs[2] +Lcase6: + lv.s S010, 0($a1) // emaxs[0] + lv.s S011, 4($a0) // emins[1] + lv.s S012, 8($a0) // emins[2] + lv.s S020, 0($a0) // emins[0] + lv.s S021, 4($a1) // emaxs[1] + b LDotProduct + lv.s S022, 8($a1) // emaxs[2] +Lcase7: + lv.s S010, 0($a0) // emins[0] + lv.s S011, 4($a0) // emins[1] + lv.s S012, 8($a0) // emins[2] + lv.s S020, 0($a1) // emaxs[0] + lv.s S021, 4($a1) // emaxs[1] + lv.s S022, 8($a1) // emaxs[2] +LDotProduct: + vdot.t S030, C000, C010 // S030 = C000 * C010 + vdot.t S031, C000, C020 // S031 = C000 * C020 +LSetSides: + lv.s S013, 12($a2) // p->dist + vcmp.s LT, S030, S013 // S030 < S013 + bvt 0, LDist2 // if ( CC[0] == 1 ) jump to LDist2 + li $v0, 0 // sides = 0 + li $v0, 1 // sides = 1 +LDist2: + vcmp.s GE, S031, S013 // S031 >= S013 + bvt 0, LEnd // if ( CC[0] == 1 ) jump to LEnd + nop + ori $v0, $v0, 2 +LEnd: + jr $ra + nop + .end BoxOnPlaneSide + + .section .rodata + .align 4 +Ljmptab: + .word Lcase0, Lcase1, Lcase2, Lcase3, Lcase4, Lcase5, Lcase6, Lcase7 +#endif // XASH_PSP diff --git a/ref/gl/gl_triapi.c b/ref/gl/gl_triapi.c index 85ce753b2..a984bb284 100644 --- a/ref/gl/gl_triapi.c +++ b/ref/gl/gl_triapi.c @@ -142,7 +142,6 @@ void _TriColor4ub( byte r, byte g, byte b, byte a ) pglColor4ub( r, g, b, a ); } - /* ============= TriColor4ub @@ -365,4 +364,3 @@ void TriBrightness( float brightness ) _TriColor4f( r, g, b, 1.0f ); } - diff --git a/ref/soft/exports.txt b/ref/soft/exports.txt new file mode 100644 index 000000000..c37cfd3c4 --- /dev/null +++ b/ref/soft/exports.txt @@ -0,0 +1 @@ +GetRefAPI diff --git a/ref/soft/r_decals.c b/ref/soft/r_decals.c index 57653be96..c0b29eb32 100644 --- a/ref/soft/r_decals.c +++ b/ref/soft/r_decals.c @@ -201,6 +201,19 @@ void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int textu // Build the initial list of vertices from the surface verts into the global array, 'verts'. void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts ) { +#if XASH_PSP + int i; + + if( !surf->polys ) + return; + for( i = 0; i < surf->polys->numverts; i++, verts += VERTEXSIZE ) + { + VectorCopy( surf->polys->verts[i].xyz, verts ); // copy model space coordinates + verts[3] = DotProduct( verts, textureSpaceBasis[0] ) - pDecal->dx + 0.5f; + verts[4] = DotProduct( verts, textureSpaceBasis[1] ) - pDecal->dy + 0.5f; + verts[5] = verts[6] = 0.0f; + } +#else float *v; int i; @@ -213,6 +226,7 @@ void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t tex verts[4] = DotProduct( verts, textureSpaceBasis[1] ) - pDecal->dy + 0.5f; verts[5] = verts[6] = 0.0f; } +#endif } // Figure out where the decal maps onto the surface. @@ -524,7 +538,26 @@ glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &lnumverts ); if( !lnumverts ) return NULL; // probably this never happens +#if XASH_PSP + // allocate glpoly + // REFTODO: com_studiocache pool! + poly = Mem_Calloc( r_temppool, sizeof( glpoly_t ) + ( lnumverts * 2 - 1 ) * sizeof( gu_vert_t )); + poly->next = pdecal->polys; + poly->flags = surf->flags; + pdecal->polys = poly; + poly->numverts = lnumverts; + for( i = 0; i < lnumverts; i++, v += VERTEXSIZE ) + { + poly->verts[i].uv[0] = v[3]; + poly->verts[i].uv[1] = v[4]; + VectorCopy( v, poly->verts[i].xyz ); + + poly->verts[i + lnumverts].uv[0] = v[5]; + poly->verts[i + lnumverts].uv[1] = v[6]; + VectorCopy( v, poly->verts[i + lnumverts].xyz ); + } +#else // allocate glpoly // REFTODO: com_studiocache pool! poly = Mem_Calloc( r_temppool, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float )); @@ -541,7 +574,7 @@ glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t poly->verts[i][5] = v[5]; poly->verts[i][6] = v[6]; } - +#endif return poly; } @@ -861,8 +894,18 @@ float * GAME_EXPORT R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int te { v = g_DecalClipVerts[0]; count = p->numverts; +#if XASH_PSP + // if we have mesh so skip clipping and just copy vertexes out (perf) + for( i = 0; i < count; i++, v += VERTEXSIZE ) + { + VectorCopy( p->verts[i].xyz, v ); + v[3] = p->verts[i].uv[0]; + v[4] = p->verts[i].uv[1]; + v[5] = p->verts[i + count].uv[0]; + v[6] = p->verts[i + count].uv[1]; + } +#else v2 = p->verts[0]; - // if we have mesh so skip clipping and just copy vertexes out (perf) for( i = 0; i < count; i++, v += VERTEXSIZE, v2 += VERTEXSIZE ) { @@ -872,7 +915,7 @@ float * GAME_EXPORT R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int te v[5] = v2[5]; v[6] = v2[6]; } - +#endif // restore pointer v = g_DecalClipVerts[0]; } diff --git a/ref/soft/r_triapi.c b/ref/soft/r_triapi.c index 88c696953..383e92c0b 100644 --- a/ref/soft/r_triapi.c +++ b/ref/soft/r_triapi.c @@ -492,4 +492,3 @@ void TriBrightness( float brightness ) _TriColor4f( r, g, b, 1.0f ); } - diff --git a/ref_gu/exports.txt b/ref_gu/exports.txt new file mode 100644 index 000000000..b79f96006 --- /dev/null +++ b/ref_gu/exports.txt @@ -0,0 +1,2 @@ +GetRefAPI +GetRefHumanReadableName \ No newline at end of file diff --git a/ref_gu/gu_alias.c b/ref_gu/gu_alias.c new file mode 100644 index 000000000..8ee371fa3 --- /dev/null +++ b/ref_gu/gu_alias.c @@ -0,0 +1,1551 @@ +/* +gu_alias.c - alias model renderer +Copyright (C) 2017 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include "gu_local.h" +#include "xash3d_mathlib.h" +#include "const.h" +#include "r_studioint.h" +#include "triangleapi.h" +#include "alias.h" +#include "pm_local.h" +#include "pmtrace.h" + +// Quake 1 +#undef MAXALIASVERTS +#undef MAXALIASFRAMES +#undef MAXALIASTRIS +#define MAXALIASVERTS 1024 +#define MAXALIASFRAMES 256 +#define MAXALIASTRIS 2048 + +extern cvar_t r_shadows; + +typedef struct +{ + double time; + double frametime; + int framecount; // alias framecount + qboolean interpolate; + + float ambientlight; + float shadelight; + vec3_t lightvec; // averaging light direction + vec3_t lightvec_local; // light direction in local space + vec3_t lightspot; // shadow spot + vec3_t lightcolor; // averaging lightcolor + int oldpose; // shadow used + int newpose; // shadow used + float lerpfrac; // lerp frames +} alias_draw_state_t; + +static alias_draw_state_t g_alias; // global alias state + +/* +================================================================= + +ALIAS MODEL DISPLAY LIST GENERATION + +================================================================= +*/ +static qboolean m_fDoRemap; +static aliashdr_t *m_pAliasHeader; +static trivertex_t *g_poseverts[MAXALIASFRAMES]; +static dtriangle_t g_triangles[MAXALIASTRIS]; +static stvert_t g_stverts[MAXALIASVERTS]; +static byte g_used[8192]; + +// a pose is a single set of vertexes. a frame may be +// an animating sequence of poses +int g_posenum; + +// the command list holds counts and s/t values that are valid for +// every frame +static int g_commands[8192]; +static int g_numcommands; + +// all frames will have their vertexes rearranged and expanded +// so they are in the order expected by the command list +static int g_vertexorder[8192]; +static int g_numorder; + +static int g_stripverts[128]; +static int g_striptris[128]; +static int g_stripcount; + +/* +==================== +R_StudioInit + +==================== +*/ +void R_AliasInit( void ) +{ + g_alias.interpolate = true; + m_fDoRemap = false; +} + +/* +================ +StripLength +================ +*/ +static int StripLength( int starttri, int startv ) +{ + int m1, m2, j, k; + dtriangle_t *last, *check; + + g_used[starttri] = 2; + + last = &g_triangles[starttri]; + + g_stripverts[0] = last->vertindex[(startv+0) % 3]; + g_stripverts[1] = last->vertindex[(startv+1) % 3]; + g_stripverts[2] = last->vertindex[(startv+2) % 3]; + + g_striptris[0] = starttri; + g_stripcount = 1; + + m1 = last->vertindex[(startv+2)%3]; + m2 = last->vertindex[(startv+1)%3]; +nexttri: + // look for a matching triangle + for( j = starttri + 1, check = &g_triangles[starttri + 1]; j < m_pAliasHeader->numtris; j++, check++ ) + { + if( check->facesfront != last->facesfront ) + continue; + + for( k = 0; k < 3; k++ ) + { + if( check->vertindex[k] != m1 ) + continue; + if( check->vertindex[(k+1) % 3] != m2 ) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if( g_used[j] ) goto done; + + // the new edge + if( g_stripcount & 1 ) + m2 = check->vertindex[(k+2) % 3]; + else m1 = check->vertindex[(k+2) % 3]; + + g_stripverts[g_stripcount+2] = check->vertindex[(k+2) % 3]; + g_striptris[g_stripcount] = j; + g_stripcount++; + + g_used[j] = 2; + goto nexttri; + } + } +done: + // clear the temp used flags + for( j = starttri + 1; j < m_pAliasHeader->numtris; j++ ) + { + if( g_used[j] == 2 ) + g_used[j] = 0; + } + + return g_stripcount; +} + +/* +=========== +FanLength +=========== +*/ +static int FanLength( int starttri, int startv ) +{ + int m1, m2, j, k; + dtriangle_t *last, *check; + + g_used[starttri] = 2; + + last = &g_triangles[starttri]; + + g_stripverts[0] = last->vertindex[(startv+0) % 3]; + g_stripverts[1] = last->vertindex[(startv+1) % 3]; + g_stripverts[2] = last->vertindex[(startv+2) % 3]; + + g_striptris[0] = starttri; + g_stripcount = 1; + + m1 = last->vertindex[(startv+0) % 3]; + m2 = last->vertindex[(startv+2) % 3]; + +nexttri: + // look for a matching triangle + for( j = starttri + 1, check = &g_triangles[starttri + 1]; j < m_pAliasHeader->numtris; j++, check++ ) + { + if( check->facesfront != last->facesfront ) + continue; + + for( k = 0; k < 3; k++ ) + { + if( check->vertindex[k] != m1 ) + continue; + if( check->vertindex[(k+1) % 3] != m2 ) + continue; + + // this is the next part of the fan + // if we can't use this triangle, this tristrip is done + if( g_used[j] ) goto done; + + // the new edge + m2 = check->vertindex[(k+2) % 3]; + + g_stripverts[g_stripcount + 2] = m2; + g_striptris[g_stripcount] = j; + g_stripcount++; + + g_used[j] = 2; + goto nexttri; + } + } +done: + // clear the temp used flags + for( j = starttri + 1; j < m_pAliasHeader->numtris; j++ ) + { + if( g_used[j] == 2 ) + g_used[j] = 0; + } + + return g_stripcount; +} + +/* +================ +BuildTris + +Generate a list of trifans or strips +for the model, which holds for all frames +================ +*/ +void BuildTris( void ) +{ + int len, bestlen, besttype = 0; + int bestverts[1024]; + int besttris[1024]; + int type, startv; + int i, j, k; + float s, t; + + // + // build tristrips + // + memset( g_used, 0, sizeof( g_used )); + g_numcommands = 0; + g_numorder = 0; + + for( i = 0; i < m_pAliasHeader->numtris; i++ ) + { + // pick an unused triangle and start the trifan + if( g_used[i] ) continue; + + bestlen = 0; + for( type = 0; type < 2; type++ ) + { + for( startv = 0; startv < 3; startv++ ) + { + if( type == 1 ) len = StripLength( i, startv ); + else len = FanLength( i, startv ); + + if( len > bestlen ) + { + besttype = type; + bestlen = len; + + for( j = 0; j < bestlen + 2; j++ ) + bestverts[j] = g_stripverts[j]; + + for( j = 0; j < bestlen; j++ ) + besttris[j] = g_striptris[j]; + } + } + } + + // mark the tris on the best strip as used + for( j = 0; j < bestlen; j++ ) + g_used[besttris[j]] = 1; + + if( besttype == 1 ) + g_commands[g_numcommands++] = (bestlen + 2); + else g_commands[g_numcommands++] = -(bestlen + 2); + + for( j = 0; j < bestlen + 2; j++ ) + { + // emit a vertex into the reorder buffer + k = bestverts[j]; + g_vertexorder[g_numorder++] = k; + + // emit s/t coords into the commands stream + s = g_stverts[k].s; + t = g_stverts[k].t; + + if( !g_triangles[besttris[0]].facesfront && g_stverts[k].onseam ) + s += m_pAliasHeader->skinwidth / 2; // on back side + s = (s + 0.5f) / m_pAliasHeader->skinwidth; + t = (t + 0.5f) / m_pAliasHeader->skinheight; + + // Carmack use floats and Valve use shorts here... + *(float *)&g_commands[g_numcommands++] = s; + *(float *)&g_commands[g_numcommands++] = t; + } + } + + g_commands[g_numcommands++] = 0; // end of list marker +} + +/* +================ +GL_MakeAliasModelDisplayLists +================ +*/ +void GL_MakeAliasModelDisplayLists( model_t *m ) +{ + trivertex_t *verts; + int i, j; + + BuildTris( ); + + // save the data out + m_pAliasHeader->poseverts = g_numorder; + + m_pAliasHeader->commands = Mem_Malloc( m->mempool, g_numcommands * 4 ); + memcpy( m_pAliasHeader->commands, g_commands, g_numcommands * 4 ); + + m_pAliasHeader->posedata = Mem_Malloc( m->mempool, m_pAliasHeader->numposes * m_pAliasHeader->poseverts * sizeof( trivertex_t )); + verts = m_pAliasHeader->posedata; + + for( i = 0; i < m_pAliasHeader->numposes; i++ ) + { + for( j = 0; j < g_numorder; j++ ) + *verts++ = g_poseverts[i][g_vertexorder[j]]; + } +} + +/* +============================================================================== + +ALIAS MODELS + +============================================================================== +*/ +/* +================= +Mod_LoadAliasFrame +================= +*/ +void *Mod_LoadAliasFrame( void *pin, maliasframedesc_t *frame ) +{ + daliasframe_t *pdaliasframe; + trivertex_t *pinframe; + int i; + + pdaliasframe = (daliasframe_t *)pin; + + Q_strncpy( frame->name, pdaliasframe->name, sizeof( frame->name )); + frame->firstpose = g_posenum; + frame->numposes = 1; + + for( i = 0; i < 3; i++ ) + { + frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i]; + frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i]; + } + + pinframe = (trivertex_t *)(pdaliasframe + 1); + + g_poseverts[g_posenum] = (trivertex_t *)pinframe; + pinframe += m_pAliasHeader->numverts; + g_posenum++; + + return (void *)pinframe; +} + +/* +================= +Mod_LoadAliasGroup +================= +*/ +void *Mod_LoadAliasGroup( void *pin, maliasframedesc_t *frame ) +{ + daliasgroup_t *pingroup; + int i, numframes; + daliasinterval_t *pin_intervals; + void *ptemp; + + pingroup = (daliasgroup_t *)pin; + numframes = pingroup->numframes; + + frame->firstpose = g_posenum; + frame->numposes = numframes; + + for( i = 0; i < 3; i++ ) + { + frame->bboxmin.v[i] = pingroup->bboxmin.v[i]; + frame->bboxmax.v[i] = pingroup->bboxmax.v[i]; + } + + pin_intervals = (daliasinterval_t *)(pingroup + 1); + + // all the intervals are always equal 0.1 so we don't care about them + frame->interval = pin_intervals->interval; + pin_intervals += numframes; + ptemp = (void *)pin_intervals; + + for( i = 0; i < numframes; i++ ) + { + g_poseverts[g_posenum] = (trivertex_t *)((daliasframe_t *)ptemp + 1); + ptemp = (trivertex_t *)((daliasframe_t *)ptemp + 1) + m_pAliasHeader->numverts; + g_posenum++; + } + + return ptemp; +} + +/* +=============== +Mod_CreateSkinData +=============== +*/ +rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) +{ + static rgbdata_t skin; + char name[MAX_QPATH]; + int i; + model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + + skin.width = width; + skin.height = height; + skin.depth = 1; + skin.type = PF_INDEXED_24; + skin.flags = IMAGE_HAS_COLOR|IMAGE_QUAKEPAL; + skin.encode = DXT_ENCODE_DEFAULT; + skin.numMips = 1; + skin.buffer = data; + skin.palette = (byte *)gEngfuncs.CL_GetPaletteColor( 0 ); + skin.size = width * height; + + if( !gEngfuncs.Image_CustomPalette() ) + { + for( i = 0; i < skin.width * skin.height; i++ ) + { + if( data[i] > 224 && data[i] != 255 ) + { + SetBits( skin.flags, IMAGE_HAS_LUMA ); + break; + } + } + } + + COM_FileBase( loadmodel->name, name ); + + // for alias models only player can have remap textures + if( mod != NULL && !Q_stricmp( name, "player" )) + { + texture_t *tx = NULL; + int i, size; + + i = mod->numtextures; + mod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* )); + size = width * height + 768; + tx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size ); + mod->textures[i] = tx; + + Q_strncpy( tx->name, "DM_Skin", sizeof( tx->name )); + tx->anim_min = SHIRT_HUE_START; // topcolor start + tx->anim_max = SHIRT_HUE_END; // topcolor end + // bottomcolor start always equal is (topcolor end + 1) + tx->anim_total = PANTS_HUE_END;// bottomcolor end + + tx->width = width; + tx->height = height; + + // the pixels immediately follow the structures + memcpy( (tx+1), data, width * height ); + memcpy( ((byte *)(tx+1)+(width * height)), skin.palette, 768 ); + mod->numtextures++; // done + } + + // make an copy + return gEngfuncs.FS_CopyImage( &skin ); +} + +void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) +{ + string name, lumaname; + string checkname; + rgbdata_t *pic; + model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + + Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); + Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); + Q_snprintf( checkname, sizeof( checkname ), "%s_%i.tga", loadmodel->name, skinnum ); + if( !gEngfuncs.FS_FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) + pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + + m_pAliasHeader->gl_texturenum[skinnum][0] = + m_pAliasHeader->gl_texturenum[skinnum][1] = + m_pAliasHeader->gl_texturenum[skinnum][2] = + m_pAliasHeader->gl_texturenum[skinnum][3] = GL_LoadTextureInternal( name, pic, 0 ); + gEngfuncs.FS_FreeImage( pic ); + + if( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][0] )->flags & TF_HAS_LUMA ) + { + pic = Mod_CreateSkinData( NULL, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + m_pAliasHeader->fb_texturenum[skinnum][0] = + m_pAliasHeader->fb_texturenum[skinnum][1] = + m_pAliasHeader->fb_texturenum[skinnum][2] = + m_pAliasHeader->fb_texturenum[skinnum][3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA ); + gEngfuncs.FS_FreeImage( pic ); + } + + return ((byte *)(pskintype + 1) + size); +} + +void *Mod_LoadGroupSkin( daliasskintype_t *pskintype, int skinnum, int size ) +{ + daliasskininterval_t *pinskinintervals; + daliasskingroup_t *pinskingroup; + string name, lumaname; + rgbdata_t *pic; + int i, j; + model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + + // animating skin group. yuck. + pskintype++; + pinskingroup = (daliasskingroup_t *)pskintype; + pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1); + pskintype = (void *)(pinskinintervals + pinskingroup->numskins); + + for( i = 0; i < pinskingroup->numskins; i++ ) + { + Q_snprintf( name, sizeof( name ), "%s_%i_%i", loadmodel->name, skinnum, i ); + pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + m_pAliasHeader->gl_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( name, pic, 0 ); + gEngfuncs.FS_FreeImage( pic ); + + if( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][i & 3] )->flags & TF_HAS_LUMA ) + { + Q_snprintf( lumaname, sizeof( lumaname ), "%s_%i_%i_luma", loadmodel->name, skinnum, i ); + pic = Mod_CreateSkinData( NULL, (byte *)(pskintype), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + m_pAliasHeader->fb_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA ); + gEngfuncs.FS_FreeImage( pic ); + } + + pskintype = (daliasskintype_t *)((byte *)(pskintype) + size); + } + + for( j = i; i < 4; i++ ) + { + m_pAliasHeader->gl_texturenum[skinnum][i & 3] = m_pAliasHeader->gl_texturenum[skinnum][i - j]; + m_pAliasHeader->fb_texturenum[skinnum][i & 3] = m_pAliasHeader->fb_texturenum[skinnum][i - j]; + } + + return pskintype; +} + +/* +=============== +Mod_LoadAllSkins +=============== +*/ +void *Mod_LoadAllSkins( int numskins, daliasskintype_t *pskintype ) +{ + int i, size; + + if( numskins < 1 || numskins > MAX_SKINS ) + gEngfuncs.Host_Error( "Mod_LoadAliasModel: Invalid # of skins: %d\n", numskins ); + + size = m_pAliasHeader->skinwidth * m_pAliasHeader->skinheight; + + for( i = 0; i < numskins; i++ ) + { + if( pskintype->type == ALIAS_SKIN_SINGLE ) + { + pskintype = (daliasskintype_t *)Mod_LoadSingleSkin( pskintype, i, size ); + } + else + { + pskintype = (daliasskintype_t *)Mod_LoadGroupSkin( pskintype, i, size ); + } + } + + return (void *)pskintype; +} + +//========================================================================= +/* +================= +Mod_CalcAliasBounds +================= +*/ +void Mod_CalcAliasBounds( model_t *mod ) +{ + int i, j, k; + float radius; + float dist; + vec3_t v; + + ClearBounds( mod->mins, mod->maxs ); + radius = 0.0f; + + // process verts + for( i = 0; i < m_pAliasHeader->numposes; i++ ) + { + for( j = 0; j < m_pAliasHeader->numverts; j++ ) + { + for( k = 0; k < 3; k++ ) + v[k] = g_poseverts[i][j].v[k] * m_pAliasHeader->scale[k] + m_pAliasHeader->scale_origin[k]; + + AddPointToBounds( v, mod->mins, mod->maxs ); + dist = DotProduct( v, v ); + + if( radius < dist ) + radius = dist; + } + } + + mod->radius = sqrt( radius ); +} + +/* +================= +Mod_LoadAliasModel +================= +*/ +void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) +{ + daliashdr_t *pinmodel; + stvert_t *pinstverts; + dtriangle_t *pintriangles; + daliasframetype_t *pframetype; + daliasskintype_t *pskintype; + int i, j, size; + + if( loaded ) *loaded = false; + pinmodel = (daliashdr_t *)buffer; + i = pinmodel->version; + + if( i != ALIAS_VERSION ) + { + gEngfuncs.Con_DPrintf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, i, ALIAS_VERSION ); + return; + } + + if( pinmodel->numverts <= 0 || pinmodel->numtris <= 0 || pinmodel->numframes <= 0 ) + return; // how to possible is make that? + + mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + + // allocate space for a working header, plus all the data except the frames, + // skin and group info + size = sizeof( aliashdr_t ) + (pinmodel->numframes - 1) * sizeof( maliasframedesc_t ); + + m_pAliasHeader = Mem_Calloc( mod->mempool, size ); + mod->flags = pinmodel->flags; // share effects flags + + // endian-adjust and copy the data, starting with the alias model header + m_pAliasHeader->boundingradius = pinmodel->boundingradius; + m_pAliasHeader->numskins = pinmodel->numskins; + m_pAliasHeader->skinwidth = pinmodel->skinwidth; + m_pAliasHeader->skinheight = pinmodel->skinheight; + m_pAliasHeader->numverts = pinmodel->numverts; + m_pAliasHeader->numtris = pinmodel->numtris; + m_pAliasHeader->numframes = pinmodel->numframes; + + if( m_pAliasHeader->numverts > MAXALIASVERTS ) + { + gEngfuncs.Con_DPrintf( S_ERROR "model %s has too many vertices\n", mod->name ); + return; + } + + m_pAliasHeader->size = pinmodel->size; +// mod->synctype = pinmodel->synctype; + mod->numframes = m_pAliasHeader->numframes; + + for( i = 0; i < 3; i++ ) + { + m_pAliasHeader->scale[i] = pinmodel->scale[i]; + m_pAliasHeader->scale_origin[i] = pinmodel->scale_origin[i]; + m_pAliasHeader->eyeposition[i] = pinmodel->eyeposition[i]; + } + + // load the skins + pskintype = (daliasskintype_t *)&pinmodel[1]; + pskintype = Mod_LoadAllSkins( m_pAliasHeader->numskins, pskintype ); + + // load base s and t vertices + pinstverts = (stvert_t *)pskintype; + + for( i = 0; i < m_pAliasHeader->numverts; i++ ) + { + g_stverts[i].onseam = pinstverts[i].onseam; + g_stverts[i].s = pinstverts[i].s; + g_stverts[i].t = pinstverts[i].t; + } + + // load triangle lists + pintriangles = (dtriangle_t *)&pinstverts[m_pAliasHeader->numverts]; + + for( i = 0; i < m_pAliasHeader->numtris; i++ ) + { + g_triangles[i].facesfront = pintriangles[i].facesfront; + + for( j = 0; j < 3; j++ ) + g_triangles[i].vertindex[j] = pintriangles[i].vertindex[j]; + } + + // load the frames + pframetype = (daliasframetype_t *)&pintriangles[m_pAliasHeader->numtris]; + g_posenum = 0; + + for( i = 0; i < m_pAliasHeader->numframes; i++ ) + { + aliasframetype_t frametype = pframetype->type; + + if( frametype == ALIAS_SINGLE ) + pframetype = (daliasframetype_t *)Mod_LoadAliasFrame( pframetype + 1, &m_pAliasHeader->frames[i] ); + else pframetype = (daliasframetype_t *)Mod_LoadAliasGroup( pframetype + 1, &m_pAliasHeader->frames[i] ); + } + + m_pAliasHeader->numposes = g_posenum; + + Mod_CalcAliasBounds( mod ); + mod->type = mod_alias; + + // build the draw lists + GL_MakeAliasModelDisplayLists( mod ); + + // move the complete, relocatable alias model to the cache + gEngfuncs.Mod_GetCurrentLoadingModel()->cache.data = m_pAliasHeader; + + if( loaded ) *loaded = true; // done +} + +void Mod_AliasUnloadTextures( void *data ) +{ + aliashdr_t *palias; + int i, j; + + palias = data; + if( !palias ) return; // already freed + + for( i = 0; i < MAX_SKINS; i++ ) + { + if( !palias->gl_texturenum[i][0] ) + break; + + for( j = 0; j < 4; j++ ) + { + GL_FreeTexture( palias->gl_texturenum[i][j] ); + GL_FreeTexture( palias->fb_texturenum[i][j] ); + } + } +} + +/* +============================================================= + + ALIAS MODELS + +============================================================= +*/ + +/* +=============== +R_AliasDynamicLight + +similar to R_StudioDynamicLight +=============== +*/ +void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) +{ + movevars_t *mv = gEngfuncs.pfnGetMoveVars(); + vec3_t lightDir, vecSrc, vecEnd; + vec3_t origin, dist, finalLight; + float add, radius, total; + colorVec light; + uint lnum; + dlight_t *dl; + + if( !plight || !ent ) + return; + + if( !RI.drawWorld || r_fullbright->value || FBitSet( ent->curstate.effects, EF_FULLBRIGHT )) + { + plight->shadelight = 0; + plight->ambientlight = 192; + + VectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f ); + VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + return; + } + + // determine plane to get lightvalues from: ceil or floor + if( FBitSet( ent->curstate.effects, EF_INVLIGHT )) + VectorSet( lightDir, 0.0f, 0.0f, 1.0f ); + else VectorSet( lightDir, 0.0f, 0.0f, -1.0f ); + + VectorCopy( ent->origin, origin ); + + VectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f ); + light.r = light.g = light.b = light.a = 0; + + if(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 ) + { + msurface_t *psurf = NULL; + pmtrace_t trace; + + if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_WRITE_LARGE_COORD )) + { + vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f; + vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f; + vecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f; + } + else + { + vecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f; + vecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f; + vecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f; + } + + trace = gEngfuncs.CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_IGNORE ); + if( trace.ent > 0 ) psurf = gEngfuncs.EV_TraceSurface( trace.ent, vecSrc, vecEnd ); + else psurf = gEngfuncs.EV_TraceSurface( 0, vecSrc, vecEnd ); + + if( psurf && FBitSet( psurf->flags, SURF_DRAWSKY )) + { + VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z ); + + light.r = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_r, 255 )); + light.g = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_g, 255 )); + light.b = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_b, 255 )); + } + } + + if(( light.r + light.g + light.b ) == 0 ) + { + colorVec gcolor; + float grad[4]; + + VectorScale( lightDir, 2048.0f, vecEnd ); + VectorAdd( vecEnd, vecSrc, vecEnd ); + + light = R_LightVec( vecSrc, vecEnd, g_alias.lightspot, g_alias.lightvec ); + + if( VectorIsNull( g_alias.lightvec )) + { + vecSrc[0] -= 16.0f; + vecSrc[1] -= 16.0f; + vecEnd[0] -= 16.0f; + vecEnd[1] -= 16.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); + grad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[0] += 32.0f; + vecEnd[0] += 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); + grad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[1] += 32.0f; + vecEnd[1] += 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); + grad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[0] -= 32.0f; + vecEnd[0] -= 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); + grad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + lightDir[0] = grad[0] - grad[1] - grad[2] + grad[3]; + lightDir[1] = grad[1] + grad[0] - grad[2] - grad[3]; + VectorNormalize( lightDir ); + } + else + { + VectorCopy( g_alias.lightvec, lightDir ); + } + } + + VectorSet( finalLight, light.r, light.g, light.b ); + ent->cvFloorColor = light; + + total = Q_max( Q_max( light.r, light.g ), light.b ); + if( total == 0.0f ) total = 1.0f; + + // scale lightdir by light intentsity + VectorScale( lightDir, total, lightDir ); + + for( lnum = 0, dl = gEngfuncs.GetDynamicLight( 0 ); lnum < MAX_DLIGHTS; lnum++, dl++ ) + { + if( dl->die < g_alias.time || !r_dynamic->value ) + continue; + + VectorSubtract( origin, dl->origin, dist ); + + radius = VectorLength( dist ); + add = dl->radius - radius; + + if( add > 0.0f ) + { + total += add; + + if( radius > 1.0f ) + VectorScale( dist, ( add / radius ), dist ); + else VectorScale( dist, add, dist ); + + VectorAdd( lightDir, dist, lightDir ); + + finalLight[0] += gEngfuncs.LightToTexGamma( dl->color.r ) * ( add / 256.0f ) * 2.0f; + finalLight[1] += gEngfuncs.LightToTexGamma( dl->color.g ) * ( add / 256.0f ) * 2.0f; + finalLight[2] += gEngfuncs.LightToTexGamma( dl->color.b ) * ( add / 256.0f ) * 2.0f; + } + } + + VectorScale( lightDir, 0.9f, lightDir ); + + plight->shadelight = VectorLength( lightDir ); + plight->ambientlight = total - plight->shadelight; + + total = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] ); + + if( total > 0.0f ) + { + plight->color[0] = finalLight[0] * ( 1.0f / total ); + plight->color[1] = finalLight[1] * ( 1.0f / total ); + plight->color[2] = finalLight[2] * ( 1.0f / total ); + } + else VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + + if( plight->ambientlight > 128 ) + plight->ambientlight = 128; + + if( plight->ambientlight + plight->shadelight > 255 ) + plight->shadelight = 255 - plight->ambientlight; + + VectorNormalize2( lightDir, plight->plightvec ); +} + +/* +=============== +R_AliasSetupLighting + +=============== +*/ +void R_AliasSetupLighting( alight_t *plight ) +{ + if( !m_pAliasHeader || !plight ) + return; + + g_alias.ambientlight = plight->ambientlight; + g_alias.shadelight = plight->shadelight; + VectorCopy( plight->plightvec, g_alias.lightvec ); + VectorCopy( plight->color, g_alias.lightcolor ); + + // transform back to local space + Matrix4x4_VectorIRotate( RI.objectMatrix, g_alias.lightvec, g_alias.lightvec_local ); + VectorNormalize( g_alias.lightvec_local ); +} + +/* +=============== +R_AliasLighting + +=============== +*/ +void R_AliasLighting( float *lv, const vec3_t normal ) +{ + float illum = g_alias.ambientlight; + float r, lightcos; + + lightcos = DotProduct( normal, g_alias.lightvec_local ); // -1 colinear, 1 opposite + if( lightcos > 1.0f ) lightcos = 1.0f; + + illum += g_alias.shadelight; + + r = SHADE_LAMBERT; + + // do modified hemispherical lighting + if( r <= 1.0f ) + { + r += 1.0f; + lightcos = (( r - 1.0f ) - lightcos) / r; + if( lightcos > 0.0f ) + illum += g_alias.shadelight * lightcos; + } + else + { + lightcos = (lightcos + ( r - 1.0f )) / r; + if( lightcos > 0.0f ) + illum -= g_alias.shadelight * lightcos; + } + + illum = Q_max( illum, 0.0f ); + illum = Q_min( illum, 255.0f ); + *lv = illum * (1.0f / 255.0f); +} + +/* +=============== +R_AliasSetRemapColors + +=============== +*/ +void R_AliasSetRemapColors( int newTop, int newBottom ) +{ + gEngfuncs.CL_AllocRemapInfo( RI.currententity, newTop, newBottom ); + + if( gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )) + { + gEngfuncs.CL_UpdateRemapInfo( RI.currententity, newTop, newBottom ); + m_fDoRemap = true; + } +} + +/* +============= +GL_DrawAliasFrame +============= +*/ +void GL_DrawAliasFrame( aliashdr_t *paliashdr ) +{ + float lv_tmp; + trivertex_t *verts0; + trivertex_t *verts1; + vec3_t vert, norm; + int *order; + int count; + + verts0 = verts1 = paliashdr->posedata; + verts0 += g_alias.oldpose * paliashdr->poseverts; + verts1 += g_alias.newpose * paliashdr->poseverts; + order = paliashdr->commands; + + while( 1 ) + { + // get the vertex count and primitive type + count = *order++; + if( !count ) break; // done + + int prim; + if( count < 0 ) + { + prim = GU_TRIANGLE_FAN; + count = -count; + } + else + { + prim = GU_TRIANGLE_STRIP; + } + + gu_vert_ftcv_t* const out = ( gu_vert_ftcv_t* )sceGuGetMemory( sizeof( gu_vert_ftcv_t ) * count ); + for( int index = 0; index < count; index++ ) + { + out[index].u = ((float *)order)[0]; + out[index].v = ((float *)order)[1]; + order += 2; + + VectorLerp( m_bytenormals[verts0->lightnormalindex], g_alias.lerpfrac, m_bytenormals[verts1->lightnormalindex], norm ); + VectorNormalize( norm ); + R_AliasLighting( &lv_tmp, norm ); + out[index].c = GUCOLOR4F( g_alias.lightcolor[0] * lv_tmp, g_alias.lightcolor[1] * lv_tmp, g_alias.lightcolor[2] * lv_tmp, tr.blend ); + VectorLerp( verts0->v, g_alias.lerpfrac, verts1->v, vert ); + out[index].x = vert[0]; + out[index].y = vert[1]; + out[index].z = vert[2]; + verts0++, verts1++; + } + sceGuDrawArray( prim, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF, count, 0, out ); + } +} + +/* +============= +GL_DrawAliasShadow +============= +*/ +void GL_DrawAliasShadow( aliashdr_t *paliashdr ) +{ + trivertex_t *verts0; + trivertex_t *verts1; + float vec_x, vec_y; + vec3_t av, point; + int *order; + float height; + int count; + + if( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW )) + return; + + if( glState.stencilEnabled ) + sceGuEnable( GU_STENCIL_TEST ); + + height = g_alias.lightspot[2] + 1.0f; + vec_x = -g_alias.lightvec[0] * 8.0f; + vec_y = -g_alias.lightvec[1] * 8.0f; + + r_stats.c_alias_polys += paliashdr->numtris; + + verts0 = verts1 = paliashdr->posedata; + verts0 += g_alias.oldpose * paliashdr->poseverts; + verts1 += g_alias.newpose * paliashdr->poseverts; + order = paliashdr->commands; + + while( 1 ) + { + // get the vertex count and primitive type + count = *order++; + if( !count ) break; // done + + int prim; + if( count < 0 ) + { + prim = GU_TRIANGLE_FAN; + count = -count; + } + else + { + prim = GU_TRIANGLE_STRIP; + } + + gu_vert_fv_t* const out = ( gu_vert_fv_t* )sceGuGetMemory( sizeof( gu_vert_fv_t ) * count ); + for( int index = 0; index < count; index++ ) + { + // texture coordinates come from the draw list + // (skipped for shadows) pglTexCoord2fv ((float *)order); + order += 2; + + // normals and vertexes come from the frame list + VectorLerp( verts0->v, g_alias.lerpfrac, verts1->v, av ); + point[0] = av[0] * paliashdr->scale[0] + paliashdr->scale_origin[0]; + point[1] = av[1] * paliashdr->scale[1] + paliashdr->scale_origin[1]; + point[2] = av[2] * paliashdr->scale[2] + paliashdr->scale_origin[2]; + Matrix3x4_VectorTransform( RI.objectMatrix, point, av ); + + point[0] = av[0] - (vec_x * ( av[2] - g_alias.lightspot[2] )); + point[1] = av[1] - (vec_y * ( av[2] - g_alias.lightspot[2] )); + point[2] = g_alias.lightspot[2] + 1.0f; + + out[index].x = point[0]; + out[index].y = point[1]; + out[index].z = point[2]; + + verts0++, verts1++; + } + sceGuDrawArray( prim, GU_VERTEX_32BITF, count, 0, out ); + } + + if( glState.stencilEnabled ) + sceGuDisable( GU_STENCIL_TEST ); +} + +/* +==================== +R_AliasLerpMovement + +==================== +*/ +void R_AliasLerpMovement( cl_entity_t *e ) +{ + float f = 1.0f; + + // don't do it if the goalstarttime hasn't updated in a while. + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + if( g_alias.interpolate && ( g_alias.time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime )) + f = ( g_alias.time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime ); + + if( ENGINE_GET_PARM( PARM_PLAYING_DEMO ) == DEMO_QUAKE1 ) + f = f + 1.0f; + + g_alias.lerpfrac = bound( 0.0f, f, 1.0f ); + + if( e->player || e->curstate.movetype != MOVETYPE_STEP ) + return; // monsters only + + // Con_Printf( "%4.2f %.2f %.2f\n", f, e->curstate.animtime, g_alias.time ); + VectorLerp( e->latched.prevorigin, f, e->curstate.origin, e->origin ); + + if( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON )) + { + vec4_t q, q1, q2; + + AngleQuaternion( e->curstate.angles, q1, false ); + AngleQuaternion( e->latched.prevangles, q2, false ); + QuaternionSlerp( q2, q1, f, q ); + QuaternionAngle( q, e->angles ); + } + else VectorCopy( e->curstate.angles, e->angles ); + + // NOTE: this completely over control about angles and don't broke interpolation + if( FBitSet( e->model->flags, ALIAS_ROTATE )) + e->angles[1] = anglemod( 100.0f * g_alias.time ); +} + +/* +================= +R_SetupAliasFrame + +================= +*/ +void R_SetupAliasFrame( cl_entity_t *e, aliashdr_t *paliashdr ) +{ + int newpose, oldpose; + int newframe, oldframe; + int numposes, cycle; + float interval; + + oldframe = e->latched.prevframe; + newframe = e->curstate.frame; + + if( newframe < 0 ) + { + newframe = 0; + } + else if( newframe >= paliashdr->numframes ) + { + if( newframe > paliashdr->numframes ) + gEngfuncs.Con_Reportf( S_WARN "R_GetAliasFrame: no such frame %d (%s)\n", newframe, e->model->name ); + newframe = paliashdr->numframes - 1; + } + + if(( oldframe >= paliashdr->numframes ) || ( oldframe < 0 )) + oldframe = newframe; + + numposes = paliashdr->frames[newframe].numposes; + + if( numposes > 1 ) + { + oldpose = newpose = paliashdr->frames[newframe].firstpose; + interval = 1.0f / paliashdr->frames[newframe].interval; + cycle = (int)(g_alias.time * interval); + oldpose += (cycle + 0) % numposes; // lerpframe from + newpose += (cycle + 1) % numposes; // lerpframe to + g_alias.lerpfrac = ( g_alias.time * interval ); + g_alias.lerpfrac -= (int)g_alias.lerpfrac; + } + else + { + oldpose = paliashdr->frames[oldframe].firstpose; + newpose = paliashdr->frames[newframe].firstpose; + } + + g_alias.oldpose = oldpose; + g_alias.newpose = newpose; + + GL_DrawAliasFrame( paliashdr ); +} + +/* +=============== +R_StudioDrawAbsBBox + +=============== +*/ +static void R_AliasDrawAbsBBox( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ) +{ + vec3_t p[8]; + int i; + + // looks ugly, skip + if( r_drawentities->value != 5 || e == gEngfuncs.GetViewModel() ) + return; + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + p[i][0] = ( i & 1 ) ? absmin[0] : absmax[0]; + p[i][1] = ( i & 2 ) ? absmin[1] : absmax[1]; + p[i][2] = ( i & 4 ) ? absmin[2] : absmax[2]; + } + + GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); + sceGuColor( GUCOLOR4F( 0.5f, 0.5f, 1.0f, 0.5f ) ); + TriRenderMode( kRenderTransAdd ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + + unsigned int color = getTriBrightness( g_alias.shadelight / 255.0f ); + + gu_vert_fcv_t* const out = ( gu_vert_fcv_t* )sceGuGetMemory( sizeof( gu_vert_fcv_t ) * 24 ); + for( i = 0; i < 6; i++ ) + { + out[i * 4 + 0].c = color; + out[i * 4 + 0].x = p[boxpnt[i][0]][0]; + out[i * 4 + 0].y = p[boxpnt[i][0]][1]; + out[i * 4 + 0].z = p[boxpnt[i][0]][2]; + out[i * 4 + 1].c = color; + out[i * 4 + 1].x = p[boxpnt[i][1]][0]; + out[i * 4 + 1].y = p[boxpnt[i][1]][1]; + out[i * 4 + 1].z = p[boxpnt[i][1]][2]; + out[i * 4 + 2].c = color; + out[i * 4 + 2].x = p[boxpnt[i][2]][0]; + out[i * 4 + 2].y = p[boxpnt[i][2]][1]; + out[i * 4 + 2].z = p[boxpnt[i][2]][2]; + out[i * 4 + 3].c = color; + out[i * 4 + 3].x = p[boxpnt[i][3]][0]; + out[i * 4 + 3].y = p[boxpnt[i][3]][1]; + out[i * 4 + 3].z = p[boxpnt[i][3]][2]; + } + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, 24, 0, out ); + + TriRenderMode( kRenderNormal ); +} + +static void R_AliasDrawLightTrace( cl_entity_t *e ) +{ + if( r_drawentities->value == 7 ) + { + vec3_t origin; + + sceGuDisable( GU_TEXTURE_2D ); + sceGuDisable( GU_DEPTH_TEST ); + + gu_vert_fcv_t* out = ( gu_vert_fcv_t* )sceGuGetMemory( sizeof( gu_vert_fcv_t ) * 4 ); + out[0].c = GU_COLOR( 1.0f, 0.5f, 0.0f, 1.0f ); + out[0].x = e->origin[0]; + out[0].y = e->origin[1]; + out[0].z = e->origin[2]; + out[1].c = out[0].c; + out[1].x = g_alias.lightspot[0]; + out[1].y = g_alias.lightspot[1]; + out[1].z = g_alias.lightspot[2]; + VectorMA( g_alias.lightspot, -64.0f, g_alias.lightvec, origin ); + out[2].c = GU_COLOR( 0.0f, 0.5f, 1.0f, 1.0f ); + out[2].x = g_alias.lightspot[0]; + out[2].y = g_alias.lightspot[1]; + out[2].z = g_alias.lightspot[2]; + out[3].c = out[2].c; + out[3].x = origin[0]; + out[3].y = origin[1]; + out[3].z = origin[2]; + sceGuDrawArray( GU_LINES, GU_COLOR_8888 | GU_VERTEX_32BITF, 4, 0, out ); + + out = ( gu_vert_fcv_t* )sceGuGetMemory( sizeof( gu_vert_fcv_t ) ); + out->c = GU_COLOR( 1.0f, 0.0f, 0.0f, 1.0f ); + out->x = g_alias.lightspot[0]; + out->y = g_alias.lightspot[1]; + out->z = g_alias.lightspot[2]; + sceGuDrawArray( GU_POINTS, GU_COLOR_8888 | GU_VERTEX_32BITF, 1, 0, out ); + + sceGuEnable( GU_DEPTH_TEST ); + sceGuEnable( GU_TEXTURE_2D ); + } +} + +/* +================ +R_AliasSetupTimings + +init current time for a given model +================ +*/ +static void R_AliasSetupTimings( void ) +{ + if( RI.drawWorld ) + { + // synchronize with server time + g_alias.time = gpGlobals->time; + } + else + { + // menu stuff + g_alias.time = gpGlobals->realtime; + } + + m_fDoRemap = false; +} + +/* +================= +R_DrawAliasModel + +================= +*/ +void R_DrawAliasModel( cl_entity_t *e ) +{ + model_t *clmodel; + vec3_t absmin, absmax; + remap_info_t *pinfo = NULL; + int anim, skin; + alight_t lighting; + player_info_t *playerinfo; + vec3_t dir, angles; + + clmodel = RI.currententity->model; + + VectorAdd( e->origin, clmodel->mins, absmin ); + VectorAdd( e->origin, clmodel->maxs, absmax ); + + if( R_CullModel( e, absmin, absmax )) + return; + + // + // locate the proper data + // + m_pAliasHeader = (aliashdr_t *)gEngfuncs.Mod_Extradata( mod_alias, RI.currententity->model ); + if( !m_pAliasHeader ) return; + + // init time + R_AliasSetupTimings(); + + // angles will be modify below keep original + VectorCopy( e->angles, angles ); + + R_AliasLerpMovement( e ); + + if( !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPENSATE_QUAKE_BUG )) + e->angles[PITCH] = -e->angles[PITCH]; // stupid quake bug + + // don't rotate clients, only aim + if( e->player ) e->angles[PITCH] = 0.0f; + + // + // get lighting information + // + lighting.plightvec = dir; + R_AliasDynamicLight( e, &lighting ); + + r_stats.c_alias_polys += m_pAliasHeader->numtris; + r_stats.c_alias_models_drawn++; + + // + // draw all the triangles + // + + R_RotateForEntity( e ); + + // model and frame independant + R_AliasSetupLighting( &lighting ); + GL_SetRenderMode( e->curstate.rendermode ); + + // setup remapping only for players + if( e->player && ( playerinfo = pfnPlayerInfo( e->curstate.number - 1 )) != NULL ) + { + // get remap colors + int topcolor = bound( 0, playerinfo->topcolor, 13 ); + int bottomcolor = bound( 0, playerinfo->bottomcolor, 13 ); + R_AliasSetRemapColors( topcolor, bottomcolor ); + } + + if( tr.fFlipViewModel ) + { + const ScePspFVector3 translation = + { + m_pAliasHeader->scale_origin[0], + -m_pAliasHeader->scale_origin[1], + m_pAliasHeader->scale_origin[2] + }; + sceGumTranslate( &translation ); + + const ScePspFVector3 scaling = + { + m_pAliasHeader->scale[0], + -m_pAliasHeader->scale[1], + m_pAliasHeader->scale[2] + }; + sceGumScale( &scaling ); + } + else + { + const ScePspFVector3 translation = + { + m_pAliasHeader->scale_origin[0], + m_pAliasHeader->scale_origin[1], + m_pAliasHeader->scale_origin[2] + }; + sceGumTranslate( &translation ); + + const ScePspFVector3 scaling = + { + m_pAliasHeader->scale[0], + m_pAliasHeader->scale[1], + m_pAliasHeader->scale[2] + }; + sceGumScale( &scaling ); + } + + + anim = (int)( g_alias.time * 10 ) & 3; + skin = bound( 0, RI.currententity->curstate.skin, m_pAliasHeader->numskins - 1 ); + if( m_fDoRemap ) pinfo = gEngfuncs.CL_GetRemapInfoForEntity( e ); + + if( r_lightmap->value && !r_fullbright->value ) + GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); + else if( pinfo != NULL && pinfo->textures[skin] != 0 ) + GL_Bind( XASH_TEXTURE0, pinfo->textures[skin] ); // FIXME: allow remapping for skingroups someday + else + { + GL_Bind( XASH_TEXTURE0, m_pAliasHeader->gl_texturenum[skin][anim] ); + + if( FBitSet( R_GetTexture( m_pAliasHeader->gl_texturenum[skin][anim] )->flags, TF_HAS_ALPHA )) + { + sceGuEnable( GU_ALPHA_TEST ); + sceGuAlphaFunc( GU_GREATER, 0x00, 0xff ); + tr.blend = 1.0f; + } + } + + sceGumUpdateMatrix(); //Apply a matrix transformation + + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + + sceGuShadeModel( GU_SMOOTH ); + R_SetupAliasFrame( e, m_pAliasHeader ); + + sceGuShadeModel( GU_FLAT ); + + R_LoadIdentity(); + + // get lerped origin + VectorAdd( e->origin, clmodel->mins, absmin ); + VectorAdd( e->origin, clmodel->maxs, absmax ); + + R_AliasDrawAbsBBox( e, absmin, absmax ); + R_AliasDrawLightTrace( e ); + + sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB); + sceGuAlphaFunc( GU_GREATER, DEFAULT_ALPHATEST, 0xff ); + sceGuDisable( GU_ALPHA_TEST ); + if( r_shadows.value ) + { + // need to compute transformation matrix + Matrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, 1.0f ); + sceGuDisable( GU_TEXTURE_2D ); + sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0); + sceGuEnable(GU_BLEND); + sceGuColor( GUCOLOR4F( 0.0f, 0.0f, 0.0f, 0.5f ) ); + sceGuDepthFunc( GU_LESS ); + + GL_DrawAliasShadow( m_pAliasHeader ); + + sceGuDepthFunc( GU_LEQUAL ); + sceGuEnable( GU_TEXTURE_2D ); + sceGuDisable( GU_BLEND ); + sceGuColor( GUCOLOR4F( 1.0f, 1.0f, 1.0f, 1.0f ) ); + R_LoadIdentity(); + } + + // restore original angles + VectorCopy( angles, e->angles ); +} + +//================================================================================== diff --git a/ref_gu/gu_backend.c b/ref_gu/gu_backend.c new file mode 100644 index 000000000..18a1a79e6 --- /dev/null +++ b/ref_gu/gu_backend.c @@ -0,0 +1,683 @@ +/* +gl_backend.c - rendering backend +Copyright (C) 2010 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + + +#include "gu_local.h" +#include "xash3d_mathlib.h" + +char r_speeds_msg[MAX_SYSPATH]; +ref_speeds_t r_stats; // r_speeds counters + +/* +=============== +R_SpeedsMessage +=============== +*/ +qboolean R_SpeedsMessage( char *out, size_t size ) +{ + if( gEngfuncs.drawFuncs->R_SpeedsMessage != NULL ) + { + if( gEngfuncs.drawFuncs->R_SpeedsMessage( out, size )) + return true; + // otherwise pass to default handler + } + + if( r_speeds->value <= 0 ) return false; + if( !out || !size ) return false; + + Q_strncpy( out, r_speeds_msg, size ); + + return true; +} + +/* +============== +R_Speeds_Printf + +helper to print into r_speeds message +============== +*/ +void R_Speeds_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[2048]; + + va_start( argptr, msg ); + Q_vsprintf( text, msg, argptr ); + va_end( argptr ); + + Q_strncat( r_speeds_msg, text, sizeof( r_speeds_msg )); +} + +/* +============== +GL_BackendStartFrame +============== +*/ +void GL_BackendStartFrame( void ) +{ + r_speeds_msg[0] = '\0'; +} + +/* +============== +GL_BackendEndFrame +============== +*/ +void GL_BackendEndFrame( void ) +{ + mleaf_t *curleaf; + + if( r_speeds->value <= 0 || !RI.drawWorld ) + return; + + if( !RI.viewleaf ) + curleaf = WORLDMODEL->leafs; + else curleaf = RI.viewleaf; + + R_Speeds_Printf( "Renderer: ^1Engine^7\n\n" ); + + switch( (int)r_speeds->value ) + { + case 1: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i wpoly, %3i apoly\n%3i epoly, %3i spoly", + r_stats.c_world_polys, r_stats.c_alias_polys, r_stats.c_studio_polys, r_stats.c_sprite_polys ); + break; + case 2: + R_Speeds_Printf( "visible leafs:\n%3i leafs\ncurrent leaf %3i\n", r_stats.c_world_leafs, curleaf - WORLDMODEL->leafs ); + R_Speeds_Printf( "ReciusiveWorldNode: %3lf secs\nDrawTextureChains %lf\n", r_stats.t_world_node, r_stats.t_world_draw ); + break; + case 3: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i alias models drawn\n%3i studio models drawn\n%3i sprites drawn", + r_stats.c_alias_models_drawn, r_stats.c_studio_models_drawn, r_stats.c_sprite_models_drawn ); + break; + case 4: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i static entities\n%3i normal entities\n%3i server entities", + r_numStatics, r_numEntities - r_numStatics, ENGINE_GET_PARM( PARM_NUMENTITIES )); + break; + case 5: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i tempents\n%3i viewbeams\n%3i particles", + r_stats.c_active_tents_count, r_stats.c_view_beams_count, r_stats.c_particle_count ); + break; + } + + memset( &r_stats, 0, sizeof( r_stats )); +} + +/* +================= +GL_LoadTexMatrix +================= +*/ +void GL_LoadTexMatrix( const matrix4x4 m ) +{ + sceGumMatrixMode( GU_TEXTURE ); + GL_LoadMatrix( m ); + sceGumUpdateMatrix(); + + glState.texIdentityMatrix = false; +} + +/* +================= +GL_LoadTexMatrixExt +================= +*/ +void GL_LoadTexMatrixExt( const float *glmatrix ) +{ + Assert( glmatrix != NULL ); + + sceGumMatrixMode( GU_TEXTURE ); + sceGumLoadMatrix( ( const ScePspFMatrix4 * ) glmatrix ); + sceGumUpdateMatrix(); + + glState.texIdentityMatrix = false; +} + +/* +================= +GL_LoadMatrix +================= +*/ +void GL_LoadMatrix( const matrix4x4 source ) +{ + ScePspFMatrix4 dest; + + Matrix4x4_ToFMatrix4( source, &dest ); + sceGumLoadMatrix( &dest ); +} + +/* +================= +GL_LoadIdentityTexMatrix +================= +*/ +void GL_LoadIdentityTexMatrix( void ) +{ + if( glState.texIdentityMatrix ) + return; + + sceGumMatrixMode( GU_TEXTURE ); + sceGumLoadIdentity(); + sceGumUpdateMatrix(); + + glState.texIdentityMatrix = true; +} + +/* +================= +GL_SelectTexture +================= +*/ +void GL_SelectTexture( GLint tmu ) +{ + +} + +/* +============== +GL_DisableAllTexGens +============== +*/ +void GL_DisableAllTexGens( void ) +{ + +} + +/* +============== +GL_CleanUpTextureUnits +============== +*/ +void GL_CleanUpTextureUnits( int last ) +{ + +} + +/* +============== +GL_CleanupAllTextureUnits +============== +*/ +void GL_CleanupAllTextureUnits( void ) +{ + +} + +/* +================= +GL_MultiTexCoord2f +================= +*/ +void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t ) +{ + +} + +/* +================= +GL_TextureTarget +================= +*/ +void GL_TextureTarget( uint target ) +{ + +} + +/* +================= +GL_TexGen +================= +*/ +void GL_TexGen( GLenum coord, GLenum mode ) +{ + +} + +/* +================= +GL_SetTexCoordArrayMode +================= +*/ +void GL_SetTexCoordArrayMode( GLenum mode ) +{ + +} + +/* +================= +GL_Cull +================= +*/ +void GL_Cull( GLenum cull ) +{ + if( glState.faceCull == cull ) + return; + + if( !cull ) + { + sceGuDisable( GU_CULL_FACE ); + glState.faceCull = 0; + return; + } + + sceGuEnable( GU_CULL_FACE ); + sceGuFrontFace( cull - 1 ); + glState.faceCull = cull; +} + +void GL_SetRenderMode( int mode ) +{ + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + + switch( mode ) + { + case kRenderNormal: + default: + sceGuDisable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + break; + case kRenderTransColor: + case kRenderTransTexture: + sceGuEnable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + break; + case kRenderTransAlpha: + sceGuDisable( GU_BLEND ); + sceGuEnable( GU_ALPHA_TEST ); + break; + case kRenderGlow: + case kRenderTransAdd: + sceGuEnable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, GUBLEND1 ); + break; + } +} + +/* +================= +GL_SetColor4ub +================= +*/ +void GL_SetColor4ub( byte r, byte g, byte b, byte a ) +{ + sceGuColor( GU_RGBA( r, g, b, a )); +} + +/* +============================================================================== + +SCREEN SHOTS + +============================================================================== +*/ +// used for 'env' and 'sky' shots +typedef struct envmap_s +{ + vec3_t angles; + int flags; +} envmap_t; + +const envmap_t r_skyBoxInfo[6] = +{ +{{ 0, 270, 180}, IMAGE_FLIP_X }, +{{ 0, 90, 180}, IMAGE_FLIP_X }, +{{ -90, 0, 180}, IMAGE_FLIP_X }, +{{ 90, 0, 180}, IMAGE_FLIP_X }, +{{ 0, 0, 180}, IMAGE_FLIP_X }, +{{ 0, 180, 180}, IMAGE_FLIP_X }, +}; + +const envmap_t r_envMapInfo[6] = +{ +{{ 0, 0, 90}, 0 }, +{{ 0, 180, -90}, 0 }, +{{ 0, 90, 0}, 0 }, +{{ 0, 270, 180}, 0 }, +{{-90, 180, -90}, 0 }, +{{ 90, 0, 90}, 0 } +}; + + + +qboolean VID_ScreenShot( const char *filename, int shot_type ) +{ + rgbdata_t *r_shot; + uint flags = 0; + int width = 0, height = 0; + qboolean result; + int bpp; + + r_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t )); + r_shot->width = (gpGlobals->width + 3) & ~3; + r_shot->height = (gpGlobals->height + 3) & ~3; + r_shot->flags = IMAGE_HAS_COLOR; + r_shot->type = PF_RGB_24; + bpp = gEngfuncs.Image_GetPFDesc( r_shot->type )->bpp; + r_shot->size = r_shot->width * r_shot->height * bpp; + r_shot->palette = NULL; + r_shot->buffer = Mem_Malloc( r_temppool, r_shot->size ); + + // get screen frame + byte *src = ( byte* )guRender.disp_buffer; + byte *dst = r_shot->buffer; + int cheight = r_shot->height; + + // stride copy + while( cheight-- ) + { + GL_PixelConverter( dst, src, r_shot->width, PC_HWF( guRender.buffer_format ), PC_SWF( r_shot->type ) ); + dst += r_shot->width * bpp; + src += guRender.buffer_width * guRender.buffer_bpp; + } + + switch( shot_type ) + { + case VID_SCREENSHOT: + break; + case VID_SNAPSHOT: + gEngfuncs.FS_AllowDirectPaths( true ); + break; + case VID_LEVELSHOT: + flags |= IMAGE_RESAMPLE; + if( gpGlobals->wideScreen ) + { + height = 480; + width = 800; + } + else + { + height = 480; + width = 640; + } + break; + case VID_MINISHOT: + flags |= IMAGE_RESAMPLE; + height = 200; + width = 320; + break; + case VID_MAPSHOT: + flags |= IMAGE_RESAMPLE|IMAGE_QUANTIZE; // GoldSrc request overviews in 8-bit format + height = 768; + width = 1024; + break; + } + + gEngfuncs.Image_Process( &r_shot, width, height, flags, 0.0f ); + + // write image + result = gEngfuncs.FS_SaveImage( filename, r_shot ); + gEngfuncs.FS_AllowDirectPaths( false ); // always reset after store screenshot + gEngfuncs.FS_FreeImage( r_shot ); + + return result; +} + +/* +================= +VID_CubemapShot +================= +*/ +qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot ) +{ +#if 0 + rgbdata_t *r_shot, *r_side; + byte *temp = NULL; + byte *buffer = NULL; + string basename; + int i = 1, flags, result; + + if( !RI.drawWorld || !WORLDMODEL ) + return false; + + // make sure the specified size is valid + while( i < size ) i<<=1; + + if( i != size ) return false; + if( size > gpGlobals->width || size > gpGlobals->height ) + return false; + + // alloc space + temp = Mem_Malloc( r_temppool, size * size * 3 ); + buffer = Mem_Malloc( r_temppool, size * size * 3 * 6 ); + r_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t )); + r_side = Mem_Calloc( r_temppool, sizeof( rgbdata_t )); + + // use client vieworg + if( !vieworg ) vieworg = RI.vieworg; + + R_CheckGamma(); + + for( i = 0; i < 6; i++ ) + { + // go into 3d mode + R_Set2DMode( false ); + + if( skyshot ) + { + R_DrawCubemapView( vieworg, r_skyBoxInfo[i].angles, size ); + flags = r_skyBoxInfo[i].flags; + } + else + { + R_DrawCubemapView( vieworg, r_envMapInfo[i].angles, size ); + flags = r_envMapInfo[i].flags; + } + + pglReadPixels( 0, 0, size, size, GL_RGB, GL_UNSIGNED_BYTE, temp ); + r_side->flags = IMAGE_HAS_COLOR; + r_side->width = r_side->height = size; + r_side->type = PF_RGB_24; + r_side->size = r_side->width * r_side->height * 3; + r_side->buffer = temp; + + if( flags ) gEngfuncs.Image_Process( &r_side, 0, 0, flags, 0.0f ); + memcpy( buffer + (size * size * 3 * i), r_side->buffer, size * size * 3 ); + } + + r_shot->flags = IMAGE_HAS_COLOR; + r_shot->flags |= (skyshot) ? IMAGE_SKYBOX : IMAGE_CUBEMAP; + r_shot->width = size; + r_shot->height = size; + r_shot->type = PF_RGB_24; + r_shot->size = r_shot->width * r_shot->height * 3 * 6; + r_shot->palette = NULL; + r_shot->buffer = buffer; + + // make sure what we have right extension + Q_strncpy( basename, base, MAX_STRING ); + COM_StripExtension( basename ); + COM_DefaultExtension( basename, ".tga" ); + + // write image as 6 sides + result = gEngfuncs.FS_SaveImage( basename, r_shot ); + gEngfuncs.FS_FreeImage( r_shot ); + gEngfuncs.FS_FreeImage( r_side ); + + return result; +#else + return 0; +#endif +} + +//======================================================= + +/* +=============== +R_ShowTextures + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. +=============== +*/ +void R_ShowTextures( void ) +{ +#if 0 + gl_texture_t *image; + float x, y, w, h; + int total, start, end; + int i, j, k, base_w, base_h; + rgba_t color = { 192, 192, 192, 255 }; + int charHeight, numTries = 0; + static qboolean showHelp = true; + string shortname; + + if( !CVAR_TO_BOOL( gl_showtextures )) + return; + + if( showHelp ) + { + gEngfuncs.CL_CenterPrint( "use '<-' and '->' keys to change atlas page, ESC to quit", 0.25f ); + showHelp = false; + } + + GL_SetRenderMode( kRenderNormal ); + pglClear( GL_COLOR_BUFFER_BIT ); + pglFinish(); + + base_w = 8; // textures view by horizontal + base_h = 6; // textures view by vertical + +rebuild_page: + total = base_w * base_h; + start = total * (gl_showtextures->value - 1); + end = total * gl_showtextures->value; + if( end > MAX_TEXTURES ) end = MAX_TEXTURES; + + w = gpGlobals->width / base_w; + h = gpGlobals->height / base_h; + + gEngfuncs.Con_DrawStringLen( NULL, NULL, &charHeight ); + + for( i = j = 0; i < MAX_TEXTURES; i++ ) + { + image = R_GetTexture( i ); + if( j == start ) break; // found start + if( pglIsTexture( image->texnum )) j++; + } + + if( i == MAX_TEXTURES && gl_showtextures->value != 1 ) + { + // bad case, rewind to one and try again + gEngfuncs.Cvar_SetValue( "r_showtextures", max( 1, gl_showtextures->value - 1 )); + if( ++numTries < 2 ) goto rebuild_page; // to prevent infinite loop + } + + for( k = 0; i < MAX_TEXTURES; i++ ) + { + if( j == end ) break; // page is full + + image = R_GetTexture( i ); + if( !pglIsTexture( image->texnum )) + continue; + + x = k % base_w * w; + y = k / base_w * h; + + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + GL_Bind( XASH_TEXTURE0, i ); // NOTE: don't use image->texnum here, because skybox has a 'wrong' indexes + + if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE )) + pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE ); + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0, 0 ); + pglVertex2f( x, y ); + if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + pglTexCoord2f( image->width, 0 ); + else pglTexCoord2f( 1, 0 ); + pglVertex2f( x + w, y ); + if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + pglTexCoord2f( image->width, image->height ); + else pglTexCoord2f( 1, 1 ); + pglVertex2f( x + w, y + h ); + if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + pglTexCoord2f( 0, image->height ); + else pglTexCoord2f( 0, 1 ); + pglVertex2f( x, y + h ); + pglEnd(); + + if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE )) + pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB ); + + COM_FileBase( image->name, shortname ); + if( Q_strlen( shortname ) > 18 ) + { + // cutoff too long names, it looks ugly + shortname[16] = '.'; + shortname[17] = '.'; + shortname[18] = '\0'; + } + gEngfuncs.Con_DrawString( x + 1, y + h - charHeight, shortname, color ); + j++, k++; + } + + gEngfuncs.CL_DrawCenterPrint (); + pglFinish(); +#endif +} + +/* +================ +SCR_TimeRefresh_f + +timerefresh [noflip] +================ +*/ +void SCR_TimeRefresh_f( void ) +{ + int i; + double start, stop; + double time; + + if( ENGINE_GET_PARM( PARM_CONNSTATE ) != ca_active ) + return; + + start = gEngfuncs.pfnTime(); + + // run without page flipping like GoldSrc + if( gEngfuncs.Cmd_Argc() == 1 ) + { +#if 0 + pglDrawBuffer( GL_FRONT ); +#endif + for( i = 0; i < 128; i++ ) + { + gpGlobals->viewangles[1] = i / 128.0f * 360.0f; + R_RenderScene(); + } +#if 0 + pglFinish(); +#endif + R_EndFrame(); + } + else + { + for( i = 0; i < 128; i++ ) + { + R_BeginFrame( true ); + gpGlobals->viewangles[1] = i / 128.0f * 360.0f; + R_RenderScene(); + R_EndFrame(); + } + } + + stop = gEngfuncs.pfnTime (); + time = (stop - start); + gEngfuncs.Con_Printf( "%f seconds (%f fps)\n", time, 128 / time ); +} diff --git a/ref_gu/gu_beams.c b/ref_gu/gu_beams.c new file mode 100644 index 000000000..7e9d83571 --- /dev/null +++ b/ref_gu/gu_beams.c @@ -0,0 +1,1410 @@ +/* +gl_beams.c - beams rendering +Copyright (C) 2009 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "r_efx.h" +#include "event_flags.h" +#include "entity_types.h" +#include "triangleapi.h" +#include "customentity.h" +#include "cl_tent.h" +#include "pm_local.h" +#include "studio.h" + +#define NOISE_DIVISIONS 64 // don't touch - many tripmines cause the crash when it equal 128 + +typedef struct +{ + vec3_t pos; + float texcoord; // Y texture coordinate + float width; +} beamseg_t; + +/* +============================================================== + +FRACTAL NOISE + +============================================================== +*/ +static float rgNoise[NOISE_DIVISIONS+1]; // global noise array + +// freq2 += step * 0.1; +// Fractal noise generator, power of 2 wavelength +static void FracNoise( float *noise, int divs ) +{ + int div2; + + div2 = divs >> 1; + if( divs < 2 ) return; + + // noise is normalized to +/- scale + noise[div2] = ( noise[0] + noise[divs] ) * 0.5f + divs * gEngfuncs.COM_RandomFloat( -0.125f, 0.125f ); + + if( div2 > 1 ) + { + FracNoise( &noise[div2], div2 ); + FracNoise( noise, div2 ); + } +} + +static void SineNoise( float *noise, int divs ) +{ + float freq = 0; + float step = M_PI_F / (float)divs; + int i; + + for( i = 0; i < divs; i++ ) + { + noise[i] = sin( freq ); + freq += step; + } +} + + +/* +============================================================== + +BEAM MATHLIB + +============================================================== +*/ +static void R_BeamComputePerpendicular( const vec3_t vecBeamDelta, vec3_t pPerp ) +{ + // direction in worldspace of the center of the beam + vec3_t vecBeamCenter; + + VectorNormalize2( vecBeamDelta, vecBeamCenter ); + CrossProduct( RI.vforward, vecBeamCenter, pPerp ); + VectorNormalize( pPerp ); +} + +static void R_BeamComputeNormal( const vec3_t vStartPos, const vec3_t vNextPos, vec3_t pNormal ) +{ + // vTangentY = line vector for beam + vec3_t vTangentY, vDirToBeam; + + VectorSubtract( vStartPos, vNextPos, vTangentY ); + + // vDirToBeam = vector from viewer origin to beam + VectorSubtract( vStartPos, RI.vieworg, vDirToBeam ); + + // get a vector that is perpendicular to us and perpendicular to the beam. + // this is used to fatten the beam. + CrossProduct( vTangentY, vDirToBeam, pNormal ); + VectorNormalizeFast( pNormal ); +} + + +/* +============== +R_BeamCull + +Cull the beam by bbox +============== +*/ +qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly ) +{ + vec3_t mins, maxs; + int i; + + for( i = 0; i < 3; i++ ) + { + if( start[i] < end[i] ) + { + mins[i] = start[i]; + maxs[i] = end[i]; + } + else + { + mins[i] = end[i]; + maxs[i] = start[i]; + } + + // don't let it be zero sized + if( mins[i] == maxs[i] ) + maxs[i] += 1.0f; + } + + // check bbox + if( gEngfuncs.Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( ))) + { + if( pvsOnly || !R_CullBox( mins, maxs )) + { + // beam is visible + return false; + } + } + + // beam is culled + return true; +} + +/* +================ +CL_AddCustomBeam + +Add the beam that encoded as custom entity +================ +*/ +void CL_AddCustomBeam( cl_entity_t *pEnvBeam ) +{ + if( tr.draw_list->num_beam_entities >= MAX_VISIBLE_PACKET ) + { + gEngfuncs.Con_Printf( S_ERROR "Too many beams %d!\n", tr.draw_list->num_beam_entities ); + return; + } + + if( pEnvBeam ) + { + tr.draw_list->beam_entities[tr.draw_list->num_beam_entities] = pEnvBeam; + tr.draw_list->num_beam_entities++; + } +} + + +/* +============================================================== + +BEAM DRAW METHODS + +============================================================== +*/ +/* +================ +R_DrawSegs + +general code for drawing beams +================ +*/ +static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, int flags ) +{ + int noiseIndex, noiseStep; + int i, total_segs, segs_drawn; + float div, length, fraction, factor; + float flMaxWidth, vLast, vStep, brightness; + vec3_t perp1, vLastNormal; + beamseg_t curSeg; + uint vert_count; + uint vert_color; + + if( segments < 2 ) return; + + length = VectorLength( delta ); + flMaxWidth = width * 0.5f; + div = 1.0f / ( segments - 1 ); + + if( length * div < flMaxWidth * 1.414f ) + { + // here, we have too many segments; we could get overlap... so lets have less segments + segments = (int)( length / ( flMaxWidth * 1.414f )) + 1.0f; + if( segments < 2 ) segments = 2; + } + + if( segments > NOISE_DIVISIONS ) + segments = NOISE_DIVISIONS; + + div = 1.0f / (segments - 1); + length *= 0.01f; + vStep = length * div; // Texture length texels per space pixel + + // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1 ); + + if( flags & FBEAM_SINENOISE ) + { + if( segments < 16 ) + { + segments = 16; + div = 1.0f / ( segments - 1 ); + } + scale *= 100.0f; + length = segments * 0.1f; + } + else + { + scale *= length * 2.0f; + } + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ); + brightness = 1.0f; + noiseIndex = 0; + + if( FBitSet( flags, FBEAM_SHADEIN )) + brightness = 0; + + // Choose two vectors that are perpendicular to the beam + R_BeamComputePerpendicular( delta, perp1 ); + + total_segs = segments; + segs_drawn = 0; + + gu_vert_ftcnv_t* const out = ( gu_vert_ftcnv_t* )extGuBeginPacket( NULL ); + + vert_count = 0; + vert_color = 0; + + // specify all the segments. + for( i = 0; i < segments; i++ ) + { + beamseg_t nextSeg; + vec3_t vPoint1, vPoint2; + + Assert( noiseIndex < ( NOISE_DIVISIONS << 16 )); + + fraction = i * div; + + VectorMA( source, fraction, delta, nextSeg.pos ); + + // distort using noise + if( scale != 0 ) + { + factor = rgNoise[noiseIndex>>16] * scale; + + if( FBitSet( flags, FBEAM_SINENOISE )) + { + float s, c; + + SinCos( fraction * M_PI_F * length + freq, &s, &c ); + VectorMA( nextSeg.pos, (factor * s), RI.vup, nextSeg.pos ); + + // rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + VectorMA( nextSeg.pos, (factor * c), RI.vright, nextSeg.pos ); + } + else + { + VectorMA( nextSeg.pos, factor, perp1, nextSeg.pos ); + } + } + + // specify the next segment. + nextSeg.width = width * 2.0f; + nextSeg.texcoord = vLast; + + if( segs_drawn > 0 ) + { + // Get a vector that is perpendicular to us and perpendicular to the beam. + // This is used to fatten the beam. + vec3_t vNormal, vAveNormal; + + R_BeamComputeNormal( curSeg.pos, nextSeg.pos, vNormal ); + + if( segs_drawn > 1 ) + { + // Average this with the previous normal + VectorAdd( vNormal, vLastNormal, vAveNormal ); + VectorScale( vAveNormal, 0.5f, vAveNormal ); + VectorNormalizeFast( vAveNormal ); + } + else + { + VectorCopy( vNormal, vAveNormal ); + } + + VectorCopy( vNormal, vLastNormal ); + + // draw regular segment + VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vAveNormal, vPoint1 ); + VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vAveNormal, vPoint2 ); + + vert_color = getTriBrightness( brightness ); + out[vert_count].u = 0.0f; + out[vert_count].v = curSeg.texcoord; + out[vert_count].c = vert_color; + out[vert_count].nx = vAveNormal[0]; + out[vert_count].ny = vAveNormal[1]; + out[vert_count].nz = vAveNormal[2]; + out[vert_count].x = vPoint1[0]; + out[vert_count].y = vPoint1[1]; + out[vert_count].z = vPoint1[2]; + vert_count++; + out[vert_count].u = 1.0f; + out[vert_count].v = curSeg.texcoord; + out[vert_count].c = vert_color; + out[vert_count].nx = vAveNormal[0]; + out[vert_count].ny = vAveNormal[1]; + out[vert_count].nz = vAveNormal[2]; + out[vert_count].x = vPoint2[0]; + out[vert_count].y = vPoint2[1]; + out[vert_count].z = vPoint2[2]; + vert_count++; + } + + curSeg = nextSeg; + segs_drawn++; + + if( FBitSet( flags, FBEAM_SHADEIN ) && FBitSet( flags, FBEAM_SHADEOUT )) + { + if( fraction < 0.5f ) brightness = fraction; + else brightness = ( 1.0f - fraction ); + } + else if( FBitSet( flags, FBEAM_SHADEIN )) + { + brightness = fraction; + } + else if( FBitSet( flags, FBEAM_SHADEOUT )) + { + brightness = 1.0f - fraction; + } + + if( segs_drawn == total_segs ) + { + // draw the last segment + VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vLastNormal, vPoint1 ); + VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vLastNormal, vPoint2 ); + + // specify the points. + vert_color = getTriBrightness( brightness ); + out[vert_count].u = 0.0f; + out[vert_count].v = curSeg.texcoord; + out[vert_count].c = vert_color; + out[vert_count].nx = vLastNormal[0]; + out[vert_count].ny = vLastNormal[1]; + out[vert_count].nz = vLastNormal[2]; + out[vert_count].x = vPoint1[0]; + out[vert_count].y = vPoint1[1]; + out[vert_count].z = vPoint1[2]; + vert_count++; + out[vert_count].u = 1.0f; + out[vert_count].v = curSeg.texcoord; + out[vert_count].c = vert_color; + out[vert_count].nx = vLastNormal[0]; + out[vert_count].ny = vLastNormal[1]; + out[vert_count].nz = vLastNormal[2]; + out[vert_count].x = vPoint2[0]; + out[vert_count].y = vPoint2[1]; + out[vert_count].z = vPoint2[2]; + vert_count++; + } + + vLast += vStep; // Advance texture scroll (v axis only) + noiseIndex += noiseStep; + } + + extGuEndPacket(( void * )( out + vert_count )); + sceGuDrawArray( GU_TRIANGLE_STRIP, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_NORMAL_32BITF | GU_VERTEX_32BITF, vert_count, 0, out ); +} + +/* +================ +R_DrawTorus + +Draw beamtours +================ +*/ +void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +{ + int i, noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep; + vec3_t last1, last2, point, screen, screenLast, tmp, normal; + uint vert_count = 0; + + if( segments < 2 ) + return; + + if( segments > NOISE_DIVISIONS ) + segments = NOISE_DIVISIONS; + + length = VectorLength( delta ) * 0.01f; + if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams + + div = 1.0f / (segments - 1); + + vStep = length * div; // Texture length texels per space pixel + + // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1 ); + scale = scale * length; + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ); + noiseIndex = 0; + + gu_vert_ftv_t* const out = ( gu_vert_ftv_t* )extGuBeginPacket( NULL ); + + vert_count = 0; + + for( i = 0; i < segments; i++ ) + { + float s, c; + + fraction = i * div; + SinCos( fraction * M_PI2_F, &s, &c ); + + point[0] = s * freq * delta[2] + source[0]; + point[1] = c * freq * delta[2] + source[1]; + point[2] = source[2]; + + // distort using noise + if( scale != 0 ) + { + if(( noiseIndex >> 16 ) < NOISE_DIVISIONS ) + { + factor = rgNoise[noiseIndex>>16] * scale; + VectorMA( point, factor, RI.vup, point ); + + // rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + factor = rgNoise[noiseIndex>>16] * scale * cos( fraction * M_PI_F * 3 + freq ); + VectorMA( point, factor, RI.vright, point ); + } + } + + // Transform point into screen space + TriWorldToScreen( point, screen ); + + if( i != 0 ) + { + // build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + VectorScale( RI.vup, -tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorMA( normal, tmp[1], RI.vright, normal ); + + // Make a wide line + VectorMA( point, width, normal, last1 ); + VectorMA( point, -width, normal, last2 ); + + vLast += vStep; // advance texture scroll (v axis only) + + out[vert_count].u = 1.0f; + out[vert_count].v = vLast; + out[vert_count].x = last2[0]; + out[vert_count].y = last2[1]; + out[vert_count].z = last2[2]; + vert_count++; + out[vert_count].u = 0.0f; + out[vert_count].v = vLast; + out[vert_count].x = last1[0]; + out[vert_count].y = last1[1]; + out[vert_count].z = last1[2]; + vert_count++; + } + + VectorCopy( screen, screenLast ); + noiseIndex += noiseStep; + } + + extGuEndPacket(( void * )( out + vert_count )); + sceGuDrawArray( GU_TRIANGLE_STRIP, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, vert_count, 0, out ); +} + +/* +================ +R_DrawDisk + +Draw beamdisk +================ +*/ +void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +{ + float div, length, fraction; + float w, vLast, vStep; + int i; + uint vert_count; + uint vert_color; + + if( segments < 2 ) + return; + + if( segments > NOISE_DIVISIONS ) // UNDONE: Allow more segments? + segments = NOISE_DIVISIONS; + + length = VectorLength( delta ) * 0.01f; + if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams + + div = 1.0f / (segments - 1); + vStep = length * div; // Texture length texels per space pixel + + // scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1 ); + scale = scale * length; + + // clamp the beam width + w = fmod( freq, width * 0.1f ) * delta[2]; + + gu_vert_ftcv_t* const out = ( gu_vert_ftcv_t* )extGuBeginPacket( NULL ); + + vert_count = 0; + vert_color = getTriBrightness( 1.0f ); + + // NOTE: we must force the degenerate triangles to be on the edge + for( i = 0; i < segments; i++ ) + { + float s, c; + + fraction = i * div; + + out[vert_count].u = 1.0f; + out[vert_count].v = vLast; + out[vert_count].c = vert_color; + out[vert_count].x = source[0]; + out[vert_count].y = source[1]; + out[vert_count].z = source[2]; + vert_count++; + + SinCos( fraction * M_PI2_F, &s, &c ); + + out[vert_count].u = 0.0f; + out[vert_count].v = vLast; + out[vert_count].c = vert_color; + out[vert_count].x = s * w + source[0]; + out[vert_count].y = c * w + source[1]; + out[vert_count].z = source[2]; + vert_count++; + + vLast += vStep; // advance texture scroll (v axis only) + } + + extGuEndPacket(( void * )( out + vert_count )); + sceGuDrawArray( GU_TRIANGLE_STRIP, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF, vert_count, 0, out ); +} + +/* +================ +R_DrawCylinder + +Draw beam cylinder +================ +*/ +void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +{ + float div, length, fraction; + float vLast, vStep; + int i; + uint vert_count; + uint vert_color0; + uint vert_color1; + + if( segments < 2 ) + return; + + if( segments > NOISE_DIVISIONS ) + segments = NOISE_DIVISIONS; + + length = VectorLength( delta ) * 0.01f; + if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams + + div = 1.0f / (segments - 1); + vStep = length * div; // texture length texels per space pixel + + // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1 ); + scale = scale * length; + + gu_vert_ftcv_t* const out = ( gu_vert_ftcv_t* )extGuBeginPacket( NULL ); + + vert_count = 0; + vert_color0 = getTriBrightness( 0.0f ); + vert_color1 = getTriBrightness( 1.0f ); + + for ( i = 0; i < segments; i++ ) + { + float s, c; + + fraction = i * div; + SinCos( fraction * M_PI2_F, &s, &c ); + + out[vert_count].u = 1.0f; + out[vert_count].v = vLast; + out[vert_count].c = vert_color0; + out[vert_count].x = s * freq * delta[2] + source[0]; + out[vert_count].y = c * freq * delta[2] + source[1]; + out[vert_count].z = source[2] + width; + vert_count++; + out[vert_count].u = 0.0f; + out[vert_count].v = vLast; + out[vert_count].c = vert_color1; + out[vert_count].x = s * freq * ( delta[2] + width ) + source[0]; + out[vert_count].y = c * freq * ( delta[2] + width ) + source[1]; + out[vert_count].z = source[2] - width; + vert_count++; + + vLast += vStep; // Advance texture scroll (v axis only) + } + + extGuEndPacket(( void * )( out + vert_count )); + sceGuDrawArray( GU_TRIANGLE_STRIP, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF, vert_count, 0, out ); +} + +/* +============== +R_DrawBeamFollow + +drawi followed beam +============== +*/ +void R_DrawBeamFollow( BEAM *pbeam, float frametime ) +{ + particle_t *pnew, *particles; + float fraction, div, vLast, vStep; + vec3_t last1, last2, tmp, screen; + vec3_t delta, screenLast, normal; + uint vert_count; + uint vert_color; + + gEngfuncs.R_FreeDeadParticles( &pbeam->particles ); + + particles = pbeam->particles; + pnew = NULL; + + div = 0; + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY )) + { + if( particles ) + { + VectorSubtract( particles->org, pbeam->source, delta ); + div = VectorLength( delta ); + + if( div >= 32 ) + { + pnew = gEngfuncs.CL_AllocParticleFast(); + } + } + else + { + pnew = gEngfuncs.CL_AllocParticleFast(); + } + } + + if( pnew ) + { + VectorCopy( pbeam->source, pnew->org ); + pnew->die = gpGlobals->time + pbeam->amplitude; + VectorClear( pnew->vel ); + + pnew->next = particles; + pbeam->particles = pnew; + particles = pnew; + } + + // nothing to draw + if( !particles ) return; + + if( !pnew && div != 0 ) + { + VectorCopy( pbeam->source, delta ); + TriWorldToScreen( pbeam->source, screenLast ); + TriWorldToScreen( particles->org, screen ); + } + else if( particles && particles->next ) + { + VectorCopy( particles->org, delta ); + TriWorldToScreen( particles->org, screenLast ); + TriWorldToScreen( particles->next->org, screen ); + particles = particles->next; + } + else + { + return; + } + + // UNDONE: This won't work, screen and screenLast must be extrapolated here to fix the + // first beam segment for this trail + + // build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + + // Build point along noraml line (normal is -y, x) + VectorScale( RI.vup, tmp[0], normal ); // Build point along normal line (normal is -y, x) + VectorMA( normal, tmp[1], RI.vright, normal ); + + // Make a wide line + VectorMA( delta, pbeam->width, normal, last1 ); + VectorMA( delta, -pbeam->width, normal, last2 ); + + div = 1.0f / pbeam->amplitude; + fraction = ( pbeam->die - gpGlobals->time ) * div; + + vLast = 0.0f; + vStep = 1.0f; + + gu_vert_ftcv_t* const out = ( gu_vert_ftcv_t* )extGuBeginPacket( NULL ); + + vert_count = 0; + vert_color = 0; + + vert_color = getTriBrightness( fraction ); + out[vert_count].u = 1.0f; + out[vert_count].v = vLast; + out[vert_count].c = vert_color; + out[vert_count].x = last2[0]; + out[vert_count].y = last2[1]; + out[vert_count].z = last2[2]; + vert_count++; + out[vert_count].u = 0.0f; + out[vert_count].v = vLast; + out[vert_count].c = vert_color; + out[vert_count].x = last1[0]; + out[vert_count].y = last1[1]; + out[vert_count].z = last1[2]; + vert_count++; + + while( particles ) + { + // Transform point into screen space + TriWorldToScreen( particles->org, screen ); + // Build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + VectorScale( RI.vup, tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorMA( normal, tmp[1], RI.vright, normal ); + + // Make a wide line + VectorMA( particles->org, pbeam->width, normal, last1 ); + VectorMA( particles->org, -pbeam->width, normal, last2 ); + + vLast += vStep; // Advance texture scroll (v axis only) + + if( particles->next != NULL ) + { + fraction = (particles->die - gpGlobals->time) * div; + } + else + { + fraction = 0.0; + } + + vert_color = getTriBrightness( fraction ); + out[vert_count].u = 1.0f; + out[vert_count].v = vLast; + out[vert_count].c = vert_color; + out[vert_count].x = last2[0]; + out[vert_count].y = last2[1]; + out[vert_count].z = last2[2]; + vert_count++; + out[vert_count].u = 0.0f; + out[vert_count].v = vLast; + out[vert_count].c = vert_color; + out[vert_count].x = last1[0]; + out[vert_count].y = last1[1]; + out[vert_count].z = last1[2]; + vert_count++; + + VectorCopy( screen, screenLast ); + + particles = particles->next; + } + + extGuEndPacket(( void * )( out + vert_count )); + sceGuDrawArray( GU_TRIANGLE_STRIP, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF, vert_count, 0, out ); + + // drift popcorn trail if there is a velocity + particles = pbeam->particles; + + while( particles ) + { + VectorMA( particles->org, frametime, particles->vel, particles->org ); + particles = particles->next; + } +} + +/* +================ +R_DrawRing + +Draw beamring +================ +*/ +void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments ) +{ + int i, j, noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep; + vec3_t last1, last2, point, screen, screenLast; + vec3_t tmp, normal, center, xaxis, yaxis; + float radius, x, y, scale; + uint vert_count; + + if( segments < 2 ) + return; + + VectorClear( screenLast ); + segments = segments * M_PI_F; + + if( segments > NOISE_DIVISIONS * 8 ) + segments = NOISE_DIVISIONS * 8; + + length = VectorLength( delta ) * 0.01f * M_PI_F; + if( length < 0.5f ) length = 0.5f; // Don't lose all of the noise/texture on short beams + + div = 1.0f / ( segments - 1 ); + + vStep = length * div / 8.0f; // texture length texels per space pixel + + // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1.0f ); + scale = amplitude * length / 8.0f; + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ) * 8; + noiseIndex = 0; + + VectorScale( delta, 0.5f, delta ); + VectorAdd( source, delta, center ); + + VectorCopy( delta, xaxis ); + radius = VectorLength( xaxis ); + + // cull beamring + // -------------------------------- + // Compute box center +/- radius + VectorSet( last1, radius, radius, scale ); + VectorAdd( center, last1, tmp ); // maxs + VectorSubtract( center, last1, screen ); // mins + + if( !WORLDMODEL ) + return; + + // is that box in PVS && frustum? + if( !gEngfuncs.Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( )) || R_CullBox( screen, tmp )) + { + return; + } + + VectorSet( yaxis, xaxis[1], -xaxis[0], 0.0f ); + VectorNormalize( yaxis ); + VectorScale( yaxis, radius, yaxis ); + + j = segments / 8; + + gu_vert_ftv_t* const out = ( gu_vert_ftv_t* )extGuBeginPacket( NULL ); + + vert_count = 0; + + for( i = 0; i < segments + 1; i++ ) + { + fraction = i * div; + SinCos( fraction * M_PI2_F, &x, &y ); + + VectorMAMAM( x, xaxis, y, yaxis, 1.0f, center, point ); + + // distort using noise + factor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale; + VectorMA( point, factor, RI.vup, point ); + + // Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + factor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale; + factor *= cos( fraction * M_PI_F * 24 + freq ); + VectorMA( point, factor, RI.vright, point ); + + // Transform point into screen space + TriWorldToScreen( point, screen ); + + if( i != 0 ) + { + // build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + + // Build point along normal line (normal is -y, x) + VectorScale( RI.vup, tmp[0], normal ); + VectorMA( normal, tmp[1], RI.vright, normal ); + + // Make a wide line + VectorMA( point, width, normal, last1 ); + VectorMA( point, -width, normal, last2 ); + + vLast += vStep; // Advance texture scroll (v axis only) + + out[vert_count].u = 1.0f; + out[vert_count].v = vLast; + out[vert_count].x = last2[0]; + out[vert_count].y = last2[1]; + out[vert_count].z = last2[2]; + vert_count++; + out[vert_count].u = 0.0f; + out[vert_count].v = vLast; + out[vert_count].x = last1[0]; + out[vert_count].y = last1[1]; + out[vert_count].z = last1[2]; + vert_count++; + } + + VectorCopy( screen, screenLast ); + noiseIndex += noiseStep; + j--; + + if( j == 0 && amplitude != 0 ) + { + j = segments / 8; + FracNoise( rgNoise, NOISE_DIVISIONS ); + } + } + + extGuEndPacket(( void * )( out + vert_count )); + sceGuDrawArray( GU_TRIANGLE_STRIP, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, vert_count, 0, out ); + +} + +/* +============== +R_BeamComputePoint + +compute attachment point for beam +============== +*/ +static qboolean R_BeamComputePoint( int beamEnt, vec3_t pt ) +{ + cl_entity_t *ent; + int attach; + + ent = gEngfuncs.R_BeamGetEntity( beamEnt ); + + if( beamEnt < 0 ) + attach = BEAMENT_ATTACHMENT( -beamEnt ); + else attach = BEAMENT_ATTACHMENT( beamEnt ); + + if( !ent ) + { + gEngfuncs.Con_DPrintf( S_ERROR "R_BeamComputePoint: invalid entity %i\n", BEAMENT_ENTITY( beamEnt )); + VectorClear( pt ); + return false; + } + + // get attachment + if( attach > 0 ) + VectorCopy( ent->attachment[attach - 1], pt ); + else if( ent->index == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) ) + { + vec3_t simorg; + gEngfuncs.GetPredictedOrigin( simorg ); + VectorCopy( simorg, pt ); + } + else VectorCopy( ent->origin, pt ); + + return true; +} + +/* +============== +R_BeamRecomputeEndpoints + +Recomputes beam endpoints.. +============== +*/ +qboolean R_BeamRecomputeEndpoints( BEAM *pbeam ) +{ + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY )) + { + cl_entity_t *start = gEngfuncs.R_BeamGetEntity( pbeam->startEntity ); + + if( R_BeamComputePoint( pbeam->startEntity, pbeam->source )) + { + if( !pbeam->pFollowModel ) + pbeam->pFollowModel = start->model; + SetBits( pbeam->flags, FBEAM_STARTVISIBLE ); + } + else if( !FBitSet( pbeam->flags, FBEAM_FOREVER )) + { + ClearBits( pbeam->flags, FBEAM_STARTENTITY ); + } + } + + if( FBitSet( pbeam->flags, FBEAM_ENDENTITY )) + { + cl_entity_t *end = gEngfuncs.R_BeamGetEntity( pbeam->endEntity ); + + if( R_BeamComputePoint( pbeam->endEntity, pbeam->target )) + { + if( !pbeam->pFollowModel ) + pbeam->pFollowModel = end->model; + SetBits( pbeam->flags, FBEAM_ENDVISIBLE ); + } + else if( !FBitSet( pbeam->flags, FBEAM_FOREVER )) + { + ClearBits( pbeam->flags, FBEAM_ENDENTITY ); + pbeam->die = gpGlobals->time; + return false; + } + else + { + return false; + } + } + + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY ) && !FBitSet( pbeam->flags, FBEAM_STARTVISIBLE )) + return false; + return true; +} + + +/* +============== +R_BeamDraw + +Update beam vars and draw it +============== +*/ +void R_BeamDraw( BEAM *pbeam, float frametime ) +{ + model_t *model; + vec3_t delta; + + model = gEngfuncs.pfnGetModelByIndex( pbeam->modelIndex ); + SetBits( pbeam->flags, FBEAM_ISACTIVE ); + + if( !model || model->type != mod_sprite ) + { + pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore + pbeam->die = gpGlobals->time; + return; + } + + // update frequency + pbeam->freq += frametime; + + // generate fractal noise + if( frametime != 0.0f ) + { + rgNoise[0] = 0; + rgNoise[NOISE_DIVISIONS] = 0; + } + + if( pbeam->amplitude != 0 && frametime != 0.0f ) + { + if( FBitSet( pbeam->flags, FBEAM_SINENOISE )) + SineNoise( rgNoise, NOISE_DIVISIONS ); + else FracNoise( rgNoise, NOISE_DIVISIONS ); + } + + // update end points + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY|FBEAM_ENDENTITY )) + { + // makes sure attachment[0] + attachment[1] are valid + if( !R_BeamRecomputeEndpoints( pbeam )) + { + ClearBits( pbeam->flags, FBEAM_ISACTIVE ); // force to ignore + return; + } + + // compute segments from the new endpoints + VectorSubtract( pbeam->target, pbeam->source, delta ); + VectorClear( pbeam->delta ); + + if( VectorLength( delta ) > 0.0000001f ) + VectorCopy( delta, pbeam->delta ); + + if( pbeam->amplitude >= 0.50f ) + pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels + else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels + } + + if( pbeam->type == TE_BEAMPOINTS && R_BeamCull( pbeam->source, pbeam->target, 0 )) + { + ClearBits( pbeam->flags, FBEAM_ISACTIVE ); + return; + } + + // don't draw really short or inactive beams + if( !FBitSet( pbeam->flags, FBEAM_ISACTIVE ) || VectorLength( pbeam->delta ) < 0.1f ) + { + return; + } + + if( pbeam->flags & ( FBEAM_FADEIN|FBEAM_FADEOUT )) + { + // update life cycle + pbeam->t = pbeam->freq + ( pbeam->die - gpGlobals->time ); + if( pbeam->t != 0.0f ) pbeam->t = 1.0f - pbeam->freq / pbeam->t; + } + + if( pbeam->type == TE_BEAMHOSE ) + { + float flDot; + + VectorSubtract( pbeam->target, pbeam->source, delta ); + VectorNormalize( delta ); + + flDot = DotProduct( delta, RI.vforward ); + + // abort if the player's looking along it away from the source + if( flDot > 0 ) + { + return; + } + else + { + float flFade = pow( flDot, 10 ); + vec3_t localDir, vecProjection, tmp; + float flDistance; + + // fade the beam if the player's not looking at the source + VectorSubtract( RI.vieworg, pbeam->source, localDir ); + flDot = DotProduct( delta, localDir ); + VectorScale( delta, flDot, vecProjection ); + VectorSubtract( localDir, vecProjection, tmp ); + flDistance = VectorLength( tmp ); + + if( flDistance > 30 ) + { + flDistance = 1.0f - (( flDistance - 30.0f ) / 64.0f ); + if( flDistance <= 0 ) flFade = 0; + else flFade *= pow( flDistance, 3 ); + } + + if( flFade < ( 1.0f / 255.0f )) + return; + + // FIXME: needs to be testing + pbeam->brightness *= flFade; + } + } + + TriRenderMode( FBitSet( pbeam->flags, FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd ); + + if( !TriSpriteTexture( model, (int)(pbeam->frame + pbeam->frameRate * gpGlobals->time) % pbeam->frameCount )) + { + ClearBits( pbeam->flags, FBEAM_ISACTIVE ); + return; + } + + if( pbeam->type == TE_BEAMFOLLOW ) + { + cl_entity_t *pStart; + + // XASH SPECIFIC: get brightness from head entity + pStart = gEngfuncs.R_BeamGetEntity( pbeam->startEntity ); + if( pStart && pStart->curstate.rendermode != kRenderNormal ) + pbeam->brightness = CL_FxBlend( pStart ) / 255.0f; + } + + if( FBitSet( pbeam->flags, FBEAM_FADEIN )) + TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->t * pbeam->brightness ); + else if( FBitSet( pbeam->flags, FBEAM_FADEOUT )) + TriColor4f( pbeam->r, pbeam->g, pbeam->b, ( 1.0f - pbeam->t ) * pbeam->brightness ); + else TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->brightness ); + + switch( pbeam->type ) + { + case TE_BEAMTORUS: + GL_Cull( GL_NONE ); + R_DrawTorus( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); + break; + case TE_BEAMDISK: + GL_Cull( GL_NONE ); + R_DrawDisk( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); + break; + case TE_BEAMCYLINDER: + GL_Cull( GL_NONE ); + R_DrawCylinder( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); + break; + case TE_BEAMPOINTS: + case TE_BEAMHOSE: + R_DrawSegs( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, pbeam->flags ); + break; + case TE_BEAMFOLLOW: + R_DrawBeamFollow( pbeam, frametime ); + break; + case TE_BEAMRING: + GL_Cull( GL_NONE ); + R_DrawRing( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); + break; + } + + GL_Cull( GL_FRONT ); + r_stats.c_view_beams_count++; +} + +/* +============== +R_BeamSetAttributes + +set beam attributes +============== +*/ +static void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame ) +{ + pbeam->frame = (float)startFrame; + pbeam->frameRate = framerate; + pbeam->r = r; + pbeam->g = g; + pbeam->b = b; +} + +/* +============== +R_BeamSetup + +generic function. all beams must be +passed through this +============== +*/ +static void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) +{ + model_t *sprite = gEngfuncs.pfnGetModelByIndex( modelIndex ); + + if( !sprite ) return; + + pbeam->type = BEAM_POINTS; + pbeam->modelIndex = modelIndex; + pbeam->frame = 0; + pbeam->frameRate = 0; + pbeam->frameCount = sprite->numframes; + + VectorCopy( start, pbeam->source ); + VectorCopy( end, pbeam->target ); + VectorSubtract( end, start, pbeam->delta ); + + pbeam->freq = speed * gpGlobals->time; + pbeam->die = life + gpGlobals->time; + pbeam->amplitude = amplitude; + pbeam->brightness = brightness; + pbeam->width = width; + pbeam->speed = speed; + + if( amplitude >= 0.50f ) + pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels + else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels + + pbeam->pFollowModel = NULL; + pbeam->flags = 0; +} + + + +/* +============== +R_BeamDrawCustomEntity + +initialize beam from server entity +============== +*/ +void R_BeamDrawCustomEntity( cl_entity_t *ent ) +{ + BEAM beam; + float amp = ent->curstate.body / 100.0f; + float blend = CL_FxBlend( ent ) / 255.0f; + float r, g, b; + int beamFlags; + + r = ent->curstate.rendercolor.r / 255.0f; + g = ent->curstate.rendercolor.g / 255.0f; + b = ent->curstate.rendercolor.b / 255.0f; + + R_BeamSetup( &beam, ent->origin, ent->angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); + R_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame ); + beam.pFollowModel = NULL; + + switch( ent->curstate.rendermode & 0x0F ) + { + case BEAM_ENTPOINT: + beam.type = TE_BEAMPOINTS; + if( ent->curstate.sequence ) + { + SetBits( beam.flags, FBEAM_STARTENTITY ); + beam.startEntity = ent->curstate.sequence; + } + if( ent->curstate.skin ) + { + SetBits( beam.flags, FBEAM_ENDENTITY ); + beam.endEntity = ent->curstate.skin; + } + break; + case BEAM_ENTS: + beam.type = TE_BEAMPOINTS; + SetBits( beam.flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); + beam.startEntity = ent->curstate.sequence; + beam.endEntity = ent->curstate.skin; + break; + case BEAM_HOSE: + beam.type = TE_BEAMHOSE; + break; + case BEAM_POINTS: + // already set up + break; + } + + beamFlags = ( ent->curstate.rendermode & 0xF0 ); + + if( FBitSet( beamFlags, BEAM_FSINE )) + SetBits( beam.flags, FBEAM_SINENOISE ); + + if( FBitSet( beamFlags, BEAM_FSOLID )) + SetBits( beam.flags, FBEAM_SOLID ); + + if( FBitSet( beamFlags, BEAM_FSHADEIN )) + SetBits( beam.flags, FBEAM_SHADEIN ); + + if( FBitSet( beamFlags, BEAM_FSHADEOUT )) + SetBits( beam.flags, FBEAM_SHADEOUT ); + + // draw it + R_BeamDraw( &beam, tr.frametime ); +} + + +/* +============== +CL_DrawBeams + +draw beam loop +============== +*/ +void CL_DrawBeams( int fTrans, BEAM *active_beams ) +{ + BEAM *pBeam; + int i, flags; + + sceGuShadeModel( GU_SMOOTH ); + sceGuDepthMask( fTrans ? GU_TRUE : GU_FALSE ); + + // server beams don't allocate beam chains + // all params are stored in cl_entity_t + for( i = 0; i < tr.draw_list->num_beam_entities; i++ ) + { + RI.currentbeam = tr.draw_list->beam_entities[i]; + flags = RI.currentbeam->curstate.rendermode & 0xF0; + + if( fTrans && FBitSet( flags, FBEAM_SOLID )) + continue; + + if( !fTrans && !FBitSet( flags, FBEAM_SOLID )) + continue; + + R_BeamDrawCustomEntity( RI.currentbeam ); + r_stats.c_view_beams_count++; + } + + RI.currentbeam = NULL; + + // draw temporary entity beams + for( pBeam = active_beams; pBeam; pBeam = pBeam->next ) + { + if( fTrans && FBitSet( pBeam->flags, FBEAM_SOLID )) + continue; + + if( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID )) + continue; + + R_BeamDraw( pBeam, gpGlobals->time - gpGlobals->oldtime ); + } + + sceGuShadeModel( GU_FLAT ); + sceGuDepthMask( GU_FALSE ); + +} diff --git a/ref_gu/gu_clipping.c b/ref_gu/gu_clipping.c new file mode 100644 index 000000000..84c359f62 --- /dev/null +++ b/ref_gu/gu_clipping.c @@ -0,0 +1,337 @@ +/* +gu_clipping.c - software clipping implimentation +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +/* + +Based on clipping system from PSP Quake by Peter Mackay and Chris Swindle. + +*/ +/* + VFPU REGS: + + M700 - current frustum + M600 - world frustum + C010 - current plane ( GU_Clip2Plane ) +*/ + +#include "gu_local.h" + +#define MAX_CLIPPED_VERTICES 32 + +// Cache +static ScePspFMatrix4 projection_view_matrix; + +// The temporary working buffers. +static gu_vert_t work_buffer[2][MAX_CLIPPED_VERTICES] __attribute__(( aligned( 16 ))); + +/* +================= +GU_ClipGetFrustum +================= +*/ +_inline void GU_ClipGetAndStoreFrustum( const ScePspFMatrix4 *matrix ) +{ + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vzero.q C210\n" // set zero vector + "lv.q C100, 0(%0)\n" // C000 = matrix->x + "lv.q C110, 16(%0)\n" // C010 = matrix->y + "lv.q C120, 32(%0)\n" // C020 = matrix->z + "lv.q C130, 48(%0)\n" // C030 = matrix->w + "vadd.q C000, R103, R101\n" // C000 = R103 + R101 ( BOTTOM ) + "vadd.q C010, R103, R100\n" // C010 = R103 + R100 ( LEFT ) + "vsub.q C020, R103, R100\n" // C020 = R103 - R100 ( RIGHT ) + "vsub.q C030, R103, R101\n" // C030 = R103 - R101 ( TOP ) + "vdot.q S200, C000, C000\n" // S110 = S100*S100 + S101*S101 + S102*S102 + S103*S103 ( BOTTOM ) + "vdot.q S201, C010, C010\n" // S110 = S100*S100 + S101*S101 + S102*S102 + S103*S103 ( LEFT ) + "vdot.q S202, C020, C020\n" // S110 = S100*S100 + S101*S101 + S102*S102 + S103*S103 ( RIGHT ) + "vdot.q S203, C030, C030\n" // S110 = S100*S100 + S101*S101 + S102*S102 + S103*S103 ( TOP ) + "vcmp.q EZ, C200\n" // CC[*] = ( C200 == 0.0f ) + "vrsq.q C200, C200\n" // C200 = 1.0 / sqrt( C200 ) + "vcmovt.q C200, C210, 6\n" // if ( CC[*] ) C200 = C210 + "vscl.q C700, C000, S200\n" // C700 = C000 * S200 ( BOTTOM ) + "vscl.q C710, C010, S201\n" // C710 = C010 * S201 ( LEFT ) + "vscl.q C720, C020, S202\n" // C720 = C020 * S202 ( RIGHT ) + "vscl.q C730, C030, S203\n" // C730 = C030 * S203 ( TOP ) + ".set pop\n" // Restore assembler option + :: "r"( matrix ) + ); +} + +/* +================= +GU_ClipBeginFrame + +Calculate the clipping frustum for static objects +================= +*/ +void GU_ClipSetWorldFrustum( const matrix4x4 in ) +{ + // Get matrix. + Matrix4x4_ToFMatrix4( in, &projection_view_matrix ); + + // Calculate and cache the clipping frustum. + GU_ClipGetAndStoreFrustum( &projection_view_matrix ); + + // Save the clipping frustum. + __asm__ volatile( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vmmov.q M600, M700\n" // Save frustum + ".set pop\n" // Restore assembler option + ); +} + +/* +================= +GU_ClipRestoreWorldFrustum + +Restore the clipping frustum +================= +*/ +void GU_ClipRestoreWorldFrustum( void ) +{ + // Restore the clipping frustum. + __asm__ volatile( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vmmov.q M700, M600\n" // Restore frustum + ".set pop\n" // Restore assembler option + ); +} + +/* +================= +GU_ClipSetModelFrustum + +Calculate the clipping frustum for dynamic objects +================= +*/ +void GU_ClipSetModelFrustum( const matrix4x4 in ) +{ + ScePspFMatrix4 model_matrix; + ScePspFMatrix4 projection_view_model_matrix; + + // Get matrix. + Matrix4x4_ToFMatrix4( in, &model_matrix ); + + // Combine the matrices (multiply projection-view by model). + gumMultMatrix( &projection_view_model_matrix, &projection_view_matrix, &model_matrix ); + + // Calculate and cache the clipping frustum. + GU_ClipGetAndStoreFrustum( &projection_view_model_matrix ); +} + +/* +================= +GU_ClipLoadFrustum + +Load the clipping frustum ( native ) +================= +*/ +#if 0 +void GU_ClipLoadFrustum( const mplane_t *plane ) +{ + __asm__ volatile( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "ulv.q C700, %0\n" // Load plane into register + "ulv.q C710, %1\n" // Load plane into register + "ulv.q C720, %2\n" // Load plane into register + "ulv.q C730, %3\n" // Load plane into register + "vneg.q R703, R703\n" // R703 = -R703 = -dist + ".set pop\n" // Restore assembler option + :: "m"( plane[FRUSTUM_BOTTOM] ), + "m"( plane[FRUSTUM_LEFT] ), + "m"( plane[FRUSTUM_RIGHT] ), + "m"( plane[FRUSTUM_TOP] ) + + ); +} +#endif + +/* +================= +GU_ClipIsRequired + +Is clipping required? +================= +*/ +int GU_ClipIsRequired( gu_vert_t* uv, int uvc ) +{ + int result = 1; + gu_vert_t *uv_end = uv + ( uvc - 1 ); + + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vzero.q C000\n" // C000 = [0.0f, 0.0f, 0.0f. 0.0f] + "0:\n" // loop + "lv.s S010, 8(%1)\n" // S010 = v[i].xyz[0] + "lv.s S011, 12(%1)\n" // S011 = v[i].xyz[1] + "lv.s S012, 16(%1)\n" // S012 = v[i].xyz[2] + "vhtfm4.q C020, M700, C010\n" // C020 = frustrum * v[i].xyz + "vcmp.q LT, C020, C000\n" // S020 < 0.0f || S021 < 0.0f || S022 < 0.0f || S023 < 0.0f + "bvt 4, 1f\n" // if ( CC[4] == 1 ) jump to exit + "nop\n" // ( delay slot ) + "bne %1, %2, 0b\n" // if ( $10 != $8 ) jump to loop + "addiu %1, %1, %3\n" // $8 = $8 + sizeof( gu_vert_t ) ( delay slot ) + "move %0, $0\n" // res = 0 + "1:\n" // exit + ".set pop\n" // Restore assembler option + : "=r"( result ), "+r"( uv ) + : "r"( uv_end ), + "n"(sizeof( gu_vert_t )) + : "$8" + ); + return result; +} + +/* +================= +GU_Clip2Plane + +Clips a polygon against a plane. +================= +*/ +_inline void GU_Clip2Plane( gu_vert_t *uv, int uvc, gu_vert_t *cv, int *cvc ) +{ + gu_vert_t *uv_end = uv + ( uvc - 1 ); + gu_vert_t *cv_start = cv; + + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vzero.q C000\n" // set zero vector + "lv.s S200, 8(%2)\n" // Load vertex P XYZ(4b) X into register + "lv.s S201, 12(%2)\n" // Load vertex P XYZ(4b) Y into register + "lv.s S202, 16(%2)\n" // Load vertex P XYZ(4b) Z into register + "lv.s S210, 0(%2)\n" // Load vertex P TEX(4b) U into register + "lv.s S211, 4(%2)\n" // Load vertex P TEX(4b) V into register + "vhdp.q S212, C200, C010\n" // distance P -> dP + "0:\n" + "lv.s S220, 8(%1)\n" // Load vertex S XYZ(4b) X into register + "lv.s S221, 12(%1)\n" // Load vertex S XYZ(4b) Y into register + "lv.s S222, 16(%1)\n" // Load vertex S XYZ(4b) Z into register + "lv.s S230, 0(%1)\n" // Load vertex S TEX(4b) U into register + "lv.s S231, 4(%1)\n" // Load vertex S TEX(4b) V into register + "vhdp.q S232, C220, C010\n" // distance S -> dS + "vcmp.s GT, S212, S000\n" // if (dP <= 0) + "bvf 0, 1f\n" // goto 1: + "nop\n" + "sv.s S210, 0(%0)\n" // cv->uv[0] = C020 U + "sv.s S211, 4(%0)\n" // cv->uv[1] = C021 V + "sv.s S200, 8(%0)\n" // cv->xyz[0] = C030 X + "sv.s S201, 12(%0)\n" // cv->xyz[1] = C031 Y + "sv.s S202, 16(%0)\n" // cv->xyz[2] = C032 Z + "addiu %0, %0, %3\n" // cv + sizeof(gu_vert_t) + "1:\n" + "vmul.s S020, S232, S212\n" // (dS * dP) + "vcmp.s LT, S020, S000\n" // if (dS * dP < 0) + "bvf 0, 2f\n" // goto 2: + "vsub.s S021, S232, S212\n" // (dS - dP) + "vrcp.s S021, S021\n" + "vmul.s S021, S021, S232\n" // R = dS / ( dS - dP ) +#if 0 + "vsub.t C200, C220, C200\n" // (S - P) XYZ + "vsub.p C210, C230, C210\n" // (S - P) UV + "vscl.t C200, C200, S021\n" // ((S - P) * R) XYZ + "vscl.p C210, C210, S021\n" // ((S - P) * R) UV + "vsub.t C200, C220, C200\n" // (S - (S - P) * R) XYZ + "vsub.p C210, C230, C210\n" // (S - (S - P) * R) UV +#else + "vsub.t C200, C200, C220\n" // (P - S) XYZ + "vsub.p C210, C210, C230\n" // (P - S) UV + "vscl.t C200, C200, S021\n" // ((P - S) * R) XYZ + "vscl.p C210, C210, S021\n" // ((P - S) * R) UV + "vadd.t C200, C220, C200\n" // (S + (P - S) * R) XYZ + "vadd.p C210, C230, C210\n" // (S + (P - S) * R) UV +#endif + "sv.s S210, 0(%0)\n" // cv->uv[0] = S210 U + "sv.s S211, 4(%0)\n" // cv->uv[1] = S211 V + "sv.s S200, 8(%0)\n" // cv->xyz[0] = S200 X + "sv.s S201, 12(%0)\n" // cv->xyz[1] = S201 Y + "sv.s S202, 16(%0)\n" // cv->xyz[2] = S202 Z + "addiu %0, %0, %3\n" // cv + sizeof(gu_vert_t) + "2:\n" + "vmov.t C200, C220\n" // P = S XYZ + "vmov.t C210, C230\n" // P = S UV and dS + "bne %1, %2, 0b\n" // if (uv != uv_end) goto 0: + "addiu %1, %1, %3\n" // uv + sizeof(gu_vert_t) ( delay slot ) + ".set pop\n" // suppress reordering + : "+r"( cv ), "+r"( uv ) + : "r"( uv_end ), + "n"( sizeof( gu_vert_t )) + : "memory" + ) ; + *cvc = ( cv - cv_start ); +} + +/* +================= +GU_Clip + +Clips a polygon against the frustum +================= +*/ +void GU_Clip( gu_vert_t *uv, int uvc, gu_vert_t **cv, int* cvc ) +{ + size_t vc; + + if ( !uvc ) // no vertices to clip? + gEngfuncs.Host_Error( "GU_Clip: calling clip with zero vertices!" ); + + vc = uvc; + *cvc = 0; + + __asm__ volatile( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vmov.q C010, C700\n" // CPLANE_BOTTOM + ".set pop\n" // suppress reordering + ); + GU_Clip2Plane( uv, vc, work_buffer[0], &vc ); + if ( !vc ) return; + + __asm__ volatile( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vmov.q C010, C710\n" // CPLANE_LEFT + ".set pop\n" // suppress reordering + ); + GU_Clip2Plane( work_buffer[0], vc, work_buffer[1], &vc ); + if ( !vc ) return; + + __asm__ volatile( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vmov.q C010, C720\n" // CPLANE_RIGHT + ".set pop\n" // suppress reordering + ); + GU_Clip2Plane( work_buffer[1], vc, work_buffer[0], &vc ); + if ( !vc ) return; + + __asm__ volatile( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "vmov.q C010, C730\n" // CPLANE_TOP + ".set pop\n" // suppress reordering + ); + *cv = extGuBeginPacket( NULL ); // uncached + GU_Clip2Plane( work_buffer[0], vc, *cv, cvc ); + if (!( *cvc )) return; + extGuEndPacket(( void * )( *cv + *cvc )); +} diff --git a/ref_gu/gu_context.c b/ref_gu/gu_context.c new file mode 100644 index 000000000..3118ceaed --- /dev/null +++ b/ref_gu/gu_context.c @@ -0,0 +1,503 @@ +/* +vid_sdl.c - SDL vid component +Copyright (C) 2018 a1batross +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +// GL API function pointers, if any, reside in this translation unit +#define APIENTRY_LINKAGE +#include "gu_local.h" + +ref_api_t gEngfuncs; +ref_globals_t *gpGlobals; + +static void R_ClearScreen( void ) +{ + sceGuClearColor( 0x00000000 ); + sceGuClear( GU_COLOR_BUFFER_BIT ); +} + +static const byte *R_GetTextureOriginalBuffer( unsigned int idx ) +{ + gl_texture_t *glt = R_GetTexture( idx ); + + if( !glt || !glt->original || !glt->original->buffer ) + return NULL; + + return glt->original->buffer; +} + +/* +============= +CL_FillRGBA + +============= +*/ +static void CL_FillRGBA( float _x, float _y, float _w, float _h, int r, int g, int b, int a ) +{ + sceGuDisable( GU_TEXTURE_2D ); + sceGuEnable( GU_BLEND ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, GUBLEND1 ); + sceGuColor( GUCOLOR4UB( r, g, b, a ) ); + + gu_vert_hv_t* const out = ( gu_vert_hv_t* )sceGuGetMemory( sizeof( gu_vert_hv_t ) * 2 ); + out[0].x = _x; + out[0].y = _y; + out[0].z = 0; + out[1].x = _x + _w; + out[1].y = _y + _h; + out[1].z = 0; + sceGuDrawArray( GU_SPRITES, GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, out ); + + sceGuColor( 0xffffffff ); + sceGuEnable( GU_TEXTURE_2D ); + sceGuDisable( GU_BLEND ); +} + +/* +============= +pfnFillRGBABlend + +============= +*/ +static void GAME_EXPORT CL_FillRGBABlend( float _x, float _y, float _w, float _h, int r, int g, int b, int a ) +{ + sceGuDisable( GU_TEXTURE_2D ); + sceGuEnable( GU_BLEND ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_DST_ALPHA, 0, 0 ); + sceGuColor( GUCOLOR4UB( r, g, b, a ) ); + + gu_vert_hv_t* const out = ( gu_vert_hv_t* )sceGuGetMemory( sizeof( gu_vert_hv_t ) * 2 ); + out[0].x = _x; + out[0].y = _y; + out[0].z = 0; + out[1].x = _x + _w; + out[1].y = _y + _h; + out[1].z = 0; + sceGuDrawArray( GU_SPRITES, GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, out ); + + sceGuColor( 0xffffffff ); + sceGuEnable( GU_TEXTURE_2D ); + sceGuDisable( GU_BLEND ); +} + +void Mod_BrushUnloadTextures( model_t *mod ) +{ + int i; + + for( i = 0; i < mod->numtextures; i++ ) + { + texture_t *tx = mod->textures[i]; + if( !tx || tx->gl_texturenum == tr.defaultTexture ) + continue; // free slot + + GL_FreeTexture( tx->gl_texturenum ); // main texture + GL_FreeTexture( tx->fb_texturenum ); // luma texture + } +} + +void Mod_UnloadTextures( model_t *mod ) +{ + Assert( mod != NULL ); + + switch( mod->type ) + { + case mod_studio: + Mod_StudioUnloadTextures( mod->cache.data ); + break; + case mod_alias: + Mod_AliasUnloadTextures( mod->cache.data ); + break; + case mod_brush: + Mod_BrushUnloadTextures( mod ); + break; + case mod_sprite: + Mod_SpriteUnloadTextures( mod->cache.data ); + break; + default: + ASSERT( 0 ); + break; + } +} + +qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buf ) +{ + qboolean loaded = true; + + if( create ) + { + switch( mod->type ) + { + case mod_studio: + // Mod_LoadStudioModel( mod, buf, loaded ); + break; + case mod_sprite: + Mod_LoadSpriteModel( mod, buf, &loaded, mod->numtexinfo ); + break; + case mod_alias: + Mod_LoadAliasModel( mod, buf, &loaded ); + break; + case mod_brush: + // Mod_LoadBrushModel( mod, buf, loaded ); + break; + default: gEngfuncs.Host_Error( "Mod_LoadModel: unsupported type %d\n", mod->type ); + } + } + + if( loaded && gEngfuncs.drawFuncs->Mod_ProcessUserData ) + gEngfuncs.drawFuncs->Mod_ProcessUserData( mod, create, buf ); + + if( !create ) + Mod_UnloadTextures( mod ); + + return loaded; +} + +static int GL_RefGetParm( int parm, int arg ) +{ + gl_texture_t *glt; + + switch( parm ) + { + case PARM_TEX_WIDTH: + glt = R_GetTexture( arg ); + return glt->width; + case PARM_TEX_HEIGHT: + glt = R_GetTexture( arg ); + return glt->height; + case PARM_TEX_SRC_WIDTH: + glt = R_GetTexture( arg ); + return glt->srcWidth; + case PARM_TEX_SRC_HEIGHT: + glt = R_GetTexture( arg ); + return glt->srcHeight; + case PARM_TEX_GLFORMAT: + glt = R_GetTexture( arg ); + return glt->format; + case PARM_TEX_ENCODE: + /*glt = R_GetTexture( arg );*/ + return /*glt->encode*/0; + case PARM_TEX_MIPCOUNT: + glt = R_GetTexture( arg ); + return glt->numMips; + case PARM_TEX_DEPTH: + /*glt = R_GetTexture( arg );*/ + return /*glt->depth*/1; + case PARM_TEX_SKYBOX: + Assert( arg >= 0 && arg < 6 ); + return tr.skyboxTextures[arg]; + case PARM_TEX_SKYTEXNUM: + return tr.skytexturenum; + case PARM_TEX_LIGHTMAP: + arg = bound( 0, arg, MAX_LIGHTMAPS - 1 ); + return tr.lightmapTextures[arg]; + case PARM_WIDESCREEN: + return gpGlobals->wideScreen; + case PARM_FULLSCREEN: + return gpGlobals->fullScreen; + case PARM_SCREEN_WIDTH: + return gpGlobals->width; + case PARM_SCREEN_HEIGHT: + return gpGlobals->height; + case PARM_TEX_TARGET: + /*glt = R_GetTexture( arg );*/ + return /*glt->target*/0; + case PARM_TEX_TEXNUM: + /*glt = R_GetTexture( arg );*/ + return /*glt->texnum*/0; + case PARM_TEX_FLAGS: + glt = R_GetTexture( arg ); + return glt->flags; + case PARM_ACTIVE_TMU: + return 0; + case PARM_LIGHTSTYLEVALUE: + arg = bound( 0, arg, MAX_LIGHTSTYLES - 1 ); + return tr.lightstylevalue[arg]; + case PARM_MAX_IMAGE_UNITS: + return 1; + case PARM_REBUILD_GAMMA: + return glConfig.softwareGammaUpdate; + case PARM_SURF_SAMPLESIZE: + if( arg >= 0 && arg < WORLDMODEL->numsurfaces ) + return gEngfuncs.Mod_SampleSizeForFace( &WORLDMODEL->surfaces[arg] ); + return LM_SAMPLE_SIZE; + case PARM_GL_CONTEXT_TYPE: + return CONTEXT_TYPE_GL; + case PARM_GLES_WRAPPER: + return GLES_WRAPPER_NONE; + case PARM_STENCIL_ACTIVE: + return glState.stencilEnabled; + case PARM_SKY_SPHERE: + return ENGINE_GET_PARM_( parm, arg ) && !tr.fCustomSkybox; + default: + return ENGINE_GET_PARM_( parm, arg ); + } + return 0; +} + +static void R_GetDetailScaleForTexture( int texture, float *xScale, float *yScale ) +{ + gl_texture_t *glt = R_GetTexture( texture ); + + if( xScale ) *xScale = glt->xscale; + if( yScale ) *yScale = glt->yscale; +} + +static void R_GetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *density ) +{ + gl_texture_t *glt = R_GetTexture( texture ); + + if( red ) *red = glt->fogParams[0]; + if( green ) *green = glt->fogParams[1]; + if( blue ) *blue = glt->fogParams[2]; + if( density ) *density = glt->fogParams[3]; +} + + +static void R_SetCurrentEntity( cl_entity_t *ent ) +{ + RI.currententity = ent; + + // set model also + if( RI.currententity != NULL ) + { + RI.currentmodel = RI.currententity->model; + } +} + +static void R_SetCurrentModel( model_t *mod ) +{ + RI.currentmodel = mod; +} + +static float R_GetFrameTime( void ) +{ + return tr.frametime; +} + +static const char *GL_TextureName( unsigned int texnum ) +{ + return R_GetTexture( texnum )->name; +} + +const byte *GL_TextureData( unsigned int texnum ) +{ + rgbdata_t *pic = R_GetTexture( texnum )->original; + + if( pic != NULL ) + return pic->buffer; + return NULL; +} + +void R_ProcessEntData( qboolean allocate ) +{ + if( !allocate ) + { + tr.draw_list->num_solid_entities = 0; + tr.draw_list->num_trans_entities = 0; + tr.draw_list->num_beam_entities = 0; + } + + if( gEngfuncs.drawFuncs->R_ProcessEntData ) + gEngfuncs.drawFuncs->R_ProcessEntData( allocate ); +} + +qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int offset_y, float scale_x, float scale_y ) +{ + qboolean ret = true; + if( rotate > 0 ) + { + gEngfuncs.Con_Printf("rotation transform not supported\n"); + ret = false; + } + + if( offset_x || offset_y ) + { + gEngfuncs.Con_Printf("offset transform not supported\n"); + ret = false; + } + + if( scale_x != 1.0f || scale_y != 1.0f ) + { + gEngfuncs.Con_Printf("scale transform not supported\n"); + ret = false; + } + + return ret; +} + +static void* GAME_EXPORT R_GetProcAddress( const char *name ) +{ + return gEngfuncs.GL_GetProcAddress( name ); +} + +static const char *R_GetConfigName( void ) +{ + return "ref_gu"; +} + +ref_interface_t gReffuncs = +{ + R_Init, + R_Shutdown, + R_GetConfigName, + R_SetDisplayTransform, + + GL_SetupAttributes, + GL_InitExtensions, + GL_ClearExtensions, + + R_BeginFrame, + R_RenderScene, + R_EndFrame, + R_PushScene, + R_PopScene, + GL_BackendStartFrame, + GL_BackendEndFrame, + + R_ClearScreen, + R_AllowFog, + GL_SetRenderMode, + GL_SetColor4ub, + + R_AddEntity, + CL_AddCustomBeam, + R_ProcessEntData, + + R_ShowTextures, + + R_GetTextureOriginalBuffer, + GL_LoadTextureFromBuffer, + GL_ProcessTexture, + R_SetupSky, + + R_Set2DMode, + R_DrawStretchRaw, + R_DrawStretchPic, + R_DrawTileClear, + CL_FillRGBA, + CL_FillRGBABlend, + + VID_ScreenShot, + VID_CubemapShot, + + R_LightPoint, + + R_DecalShoot, + R_DecalRemoveAll, + R_CreateDecalList, + R_ClearAllDecals, + + R_StudioEstimateFrame, + R_StudioLerpMovement, + CL_InitStudioAPI, + + R_InitSkyClouds, + GL_SubdivideSurface, + CL_RunLightStyles, + + R_GetSpriteParms, + R_GetSpriteTexture, + + Mod_LoadMapSprite, + Mod_ProcessRenderData, + Mod_StudioLoadTextures, + + CL_DrawParticles, + CL_DrawTracers, + CL_DrawBeams, + R_BeamCull, + + GL_RefGetParm, + R_GetDetailScaleForTexture, + R_GetExtraParmsForTexture, + R_GetFrameTime, + + R_SetCurrentEntity, + R_SetCurrentModel, + + GL_FindTexture, + GL_TextureName, + GL_TextureData, + GL_LoadTexture, + GL_CreateTexture, + GL_LoadTextureArray, + GL_CreateTextureArray, + GL_FreeTexture, + + DrawSingleDecal, + R_DecalSetupVerts, + R_EntityRemoveDecals, + + R_UploadStretchRaw, + + GL_Bind, + GL_SelectTexture, + GL_LoadTexMatrixExt, + GL_LoadIdentityTexMatrix, + GL_CleanUpTextureUnits, + GL_TexGen, + GL_TextureTarget, + GL_SetTexCoordArrayMode, + GL_UpdateTexSize, + NULL, + NULL, + + CL_DrawParticlesExternal, + R_LightVec, + R_StudioGetTexture, + + R_RenderFrame, + Mod_SetOrthoBounds, + R_SpeedsMessage, + Mod_GetCurrentVis, + R_NewMap, + R_ClearScene, + R_GetProcAddress, + + getTriAPI, + + VGUI_DrawInit, + VGUI_DrawShutdown, + VGUI_SetupDrawingText, + VGUI_SetupDrawingRect, + VGUI_SetupDrawingImage, + VGUI_BindTexture, + VGUI_EnableTexture, + VGUI_CreateTexture, + VGUI_UploadTexture, + VGUI_UploadTextureBlock, + VGUI_DrawQuad, + VGUI_GetTextureSizes, + VGUI_GenerateTexture, +}; + +int EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals ) +{ + if( version != REF_API_VERSION ) + return 0; + + // fill in our callbacks + memcpy( funcs, &gReffuncs, sizeof( ref_interface_t )); + memcpy( &gEngfuncs, engfuncs, sizeof( ref_api_t )); + gpGlobals = globals; + + return REF_API_VERSION; +} + +void EXPORT GetRefHumanReadableName( char *out, size_t size ) +{ + Q_strncpy( out, "sceGu", size ); +} diff --git a/ref_gu/gu_cull.c b/ref_gu/gu_cull.c new file mode 100644 index 000000000..782ba4e5b --- /dev/null +++ b/ref_gu/gu_cull.c @@ -0,0 +1,149 @@ +/* +gl_cull.c - render culling routines +Copyright (C) 2010 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "entity_types.h" + +/* +============================================================= + +FRUSTUM AND PVS CULLING + +============================================================= +*/ +/* +================= +R_CullBox + +Returns true if the box is completely outside the frustum +================= +*/ +qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ) +{ + return GL_FrustumCullBox( &RI.frustum, mins, maxs, 0 ); +} + +/* +================= +R_CullSphere + +Returns true if the sphere is completely outside the frustum +================= +*/ +qboolean R_CullSphere( const vec3_t centre, const float radius ) +{ + return GL_FrustumCullSphere( &RI.frustum, centre, radius, 0 ); +} + +/* +============= +R_CullModel +============= +*/ +int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ) +{ + if( e == gEngfuncs.GetViewModel() ) + { + if( ENGINE_GET_PARM( PARM_DEV_OVERVIEW )) + return 1; + + if( RP_NORMALPASS() && !ENGINE_GET_PARM( PARM_THIRDPERSON ) && CL_IsViewEntityLocalPlayer()) + return 0; + + return 1; + } + + // local client can't view himself if camera or thirdperson is not active + if( RP_LOCALCLIENT( e ) && !ENGINE_GET_PARM( PARM_THIRDPERSON ) && CL_IsViewEntityLocalPlayer()) + return 1; + + if( R_CullBox( absmin, absmax )) + return 1; + + return 0; +} + +/* +================= +R_CullSurface + +cull invisible surfaces +================= +*/ +int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ) +{ + cl_entity_t *e = RI.currententity; + + if( !surf || !surf->texinfo || !surf->texinfo->texture ) + return CULL_OTHER; + + if( r_nocull->value ) + return CULL_VISIBLE; + + // world surfaces can be culled by vis frame too + if( RI.currententity == gEngfuncs.GetEntityByIndex( 0 ) && surf->visframe != tr.framecount ) + return CULL_VISFRAME; + + // only static ents can be culled by frustum + if( !R_StaticEntity( e )) frustum = NULL; + + if( !VectorIsNull( surf->plane->normal )) + { + float dist; + + // can use normal.z for world (optimisation) + if( RI.drawOrtho ) + { + vec3_t orthonormal; + + if( e == gEngfuncs.GetEntityByIndex( 0 ) ) orthonormal[2] = surf->plane->normal[2]; + else Matrix4x4_VectorRotate( RI.objectMatrix, surf->plane->normal, orthonormal ); + dist = orthonormal[2]; + } + else dist = PlaneDiff( tr.modelorg, surf->plane ); + + if( glState.faceCull == GL_FRONT ) + { + if( FBitSet( surf->flags, SURF_PLANEBACK )) + { + if( dist >= -BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + else + { + if( dist <= BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + } + else if( glState.faceCull == GL_BACK ) + { + if( FBitSet( surf->flags, SURF_PLANEBACK )) + { + if( dist <= BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + else + { + if( dist >= -BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + } + } + + if( frustum && GL_FrustumCullBox( frustum, surf->info->mins, surf->info->maxs, clipflags )) + return CULL_FRUSTUM; + + return CULL_VISIBLE; +} diff --git a/ref_gu/gu_dbghulls.c b/ref_gu/gu_dbghulls.c new file mode 100644 index 000000000..5d340ff7f --- /dev/null +++ b/ref_gu/gu_dbghulls.c @@ -0,0 +1,124 @@ +/* +gl_dbghulls.c - loading & handling world and brushmodels +Copyright (C) 2016 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "mod_local.h" + +#define list_entry( ptr, type, member ) \ + ((type *)((char *)(ptr) - (size_t)(&((type *)0)->member))) + +// iterate over each entry in the list +#define list_for_each_entry( pos, head, member ) \ + for( pos = list_entry( (head)->next, winding_t, member ); \ + &pos->member != (head); \ + pos = list_entry( pos->member.next, winding_t, member )) + +// REFTODO: rewrite in triapi +void R_DrawWorldHull( void ) +{ + hull_model_t *hull = &WORLD->hull_models[0]; + winding_t *poly; + int i; + + if( FBitSet( r_showhull->flags, FCVAR_CHANGED )) + { + int val = bound( 0, (int)r_showhull->value, 3 ); + if( val ) gEngfuncs.Mod_CreatePolygonsForHull( val ); + ClearBits( r_showhull->flags, FCVAR_CHANGED ); + } + + if( !CVAR_TO_BOOL( r_showhull )) + return; +#if 1 + sceGuDisable( GU_TEXTURE_2D ); + list_for_each_entry( poly, &hull->polys, chain ) + { + srand((unsigned int)poly); + sceGuColor( GU_RGBA(rand() % 256, rand() % 256, rand() % 256, rand() % 256) ); + gu_vert_fv_t* const out = ( gu_vert_fv_t* )sceGuGetMemory( sizeof( gu_vert_fv_t ) * poly->numpoints ); + for( i = 0; i < poly->numpoints; i++ ) + { + out[i].x = poly->p[i][0]; + out[i].y = poly->p[i][1]; + out[i].z = poly->p[i][2]; + } + sceGuDrawArray( GU_TRIANGLE_FAN, GU_VERTEX_32BITF, poly->numpoints, 0, out ); + } + sceGuEnable( GU_TEXTURE_2D ); +#else + pglDisable( GL_TEXTURE_2D ); + list_for_each_entry( poly, &hull->polys, chain ) + { + srand((unsigned int)poly); + pglColor3f( rand() % 256 / 255.0, rand() % 256 / 255.0, rand() % 256 / 255.0 ); + pglBegin( GL_POLYGON ); + for( i = 0; i < poly->numpoints; i++ ) + pglVertex3fv( poly->p[i] ); + pglEnd(); + } + pglEnable( GL_TEXTURE_2D ); +#endif +} + +void R_DrawModelHull( void ) +{ + hull_model_t *hull; + winding_t *poly; + int i; + + if( !CVAR_TO_BOOL( r_showhull )) + return; + + if( !RI.currentmodel || RI.currentmodel->name[0] != '*' ) + return; + + i = atoi( RI.currentmodel->name + 1 ); + if( i < 1 || i >= WORLD->num_hull_models ) + return; + + hull = &WORLD->hull_models[i]; +#if 1 + sceGuDisable( GU_TEXTURE_2D ); + list_for_each_entry( poly, &hull->polys, chain ) + { + srand((unsigned int)poly); + sceGuColor( GU_RGBA(rand() % 256, rand() % 256, rand() % 256, rand() % 256) ); + gu_vert_fv_t* const out = ( gu_vert_fv_t* )sceGuGetMemory( sizeof( gu_vert_fv_t ) * poly->numpoints ); + for( i = 0; i < poly->numpoints; i++ ) + { + out[i].x = poly->p[i][0]; + out[i].y = poly->p[i][1]; + out[i].z = poly->p[i][2]; + } + sceGuDrawArray( GU_TRIANGLE_FAN, GU_VERTEX_32BITF, poly->numpoints, 0, out ); + } + sceGuEnable( GU_TEXTURE_2D ); +#else + pglPolygonOffset( 1.0f, 2.0 ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + pglDisable( GL_TEXTURE_2D ); + list_for_each_entry( poly, &hull->polys, chain ) + { + srand((unsigned int)poly); + pglColor3f( rand() % 256 / 255.0, rand() % 256 / 255.0, rand() % 256 / 255.0 ); + pglBegin( GL_POLYGON ); + for( i = 0; i < poly->numpoints; i++ ) + pglVertex3fv( poly->p[i] ); + pglEnd(); + } + pglEnable( GL_TEXTURE_2D ); + pglDisable( GL_POLYGON_OFFSET_FILL ); +#endif +} diff --git a/ref_gu/gu_decals.c b/ref_gu/gu_decals.c new file mode 100644 index 000000000..a3e52985b --- /dev/null +++ b/ref_gu/gu_decals.c @@ -0,0 +1,1268 @@ +/* +gl_decals.c - decal paste and rendering +Copyright (C) 2010 Uncle Mike +Copyright (C) 2020 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "cl_tent.h" + +#define DECAL_OVERLAP_DISTANCE 2 +#define DECAL_DISTANCE 4 // too big values produce more clipped polygons +#define MAX_DECALCLIPVERT 32 // produced vertexes of fragmented decal +#define DECAL_CACHEENTRY 256 // MUST BE POWER OF 2 or code below needs to change! +#define DECAL_TRANSPARENT_THRESHOLD 230 // transparent decals draw with GL_MODULATE + +// empirically determined constants for minimizing overalpping decals +#define MAX_OVERLAP_DECALS 6 +#define DECAL_OVERLAP_DIST 8 +#define MIN_DECAL_SCALE 0.01f +#define MAX_DECAL_SCALE 16.0f + +// clip edges +#define LEFT_EDGE 0 +#define RIGHT_EDGE 1 +#define TOP_EDGE 2 +#define BOTTOM_EDGE 3 + +// This structure contains the information used to create new decals +typedef struct +{ + vec3_t m_Position; // world coordinates of the decal center + model_t *m_pModel; // the model the decal is going to be applied in + int m_iTexture; // The decal material + int m_Size; // Size of the decal (in world coords) + int m_Flags; + int m_Entity; // Entity the decal is applied to. + float m_scale; + int m_decalWidth; + int m_decalHeight; + vec3_t m_Basis[3]; +} decalinfo_t; + +static float g_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE]; +static float g_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE]; + +decal_t gDecalPool[MAX_RENDER_DECALS]; +static int gDecalCount; + +void R_ClearDecals( void ) +{ + memset( gDecalPool, 0, sizeof( gDecalPool )); + gDecalCount = 0; +} + +// unlink pdecal from any surface it's attached to +static void R_DecalUnlink( decal_t *pdecal ) +{ + decal_t *tmp; + + if( pdecal->psurface ) + { + if( pdecal->psurface->pdecals == pdecal ) + { + pdecal->psurface->pdecals = pdecal->pnext; + } + else + { + tmp = pdecal->psurface->pdecals; + if( !tmp ) gEngfuncs.Host_Error( "R_DecalUnlink: bad decal list\n" ); + + while( tmp->pnext ) + { + if( tmp->pnext == pdecal ) + { + tmp->pnext = pdecal->pnext; + break; + } + tmp = tmp->pnext; + } + } + } + + if( pdecal->polys ) + Mem_Free( pdecal->polys ); + + pdecal->psurface = NULL; + pdecal->polys = NULL; +} + +// Just reuse next decal in list +// A decal that spans multiple surfaces will use multiple decal_t pool entries, +// as each surface needs it's own. +static decal_t *R_DecalAlloc( decal_t *pdecal ) +{ + int limit = MAX_RENDER_DECALS; + + if( r_decals->value < limit ) + limit = r_decals->value; + + if( !limit ) return NULL; + + if( !pdecal ) + { + int count = 0; + + // check for the odd possiblity of infinte loop + do + { + if( gDecalCount >= limit ) + gDecalCount = 0; + + pdecal = &gDecalPool[gDecalCount]; // reuse next decal + gDecalCount++; + count++; + } while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit ); + } + + // if decal is already linked to a surface, unlink it. + R_DecalUnlink( pdecal ); + + return pdecal; +} + +//----------------------------------------------------------------------------- +// find decal image and grab size from it +//----------------------------------------------------------------------------- +static void R_GetDecalDimensions( int texture, int *width, int *height ) +{ + if( width ) *width = 1; // to avoid divide by zero + if( height ) *height = 1; + + R_GetTextureParms( width, height, texture ); +} + +//----------------------------------------------------------------------------- +// compute the decal basis based on surface normal +//----------------------------------------------------------------------------- +void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] ) +{ + vec3_t surfaceNormal; + + // setup normal + if( surf->flags & SURF_PLANEBACK ) + VectorNegate( surf->plane->normal, surfaceNormal ); + else VectorCopy( surf->plane->normal, surfaceNormal ); + + VectorNormalize2( surfaceNormal, textureSpaceBasis[2] ); +#if 0 + if( FBitSet( flags, FDECAL_CUSTOM )) + { + vec3_t pSAxis = { 1, 0, 0 }; + + // T = S cross N + CrossProduct( pSAxis, textureSpaceBasis[2], textureSpaceBasis[1] ); + + // Name sure they aren't parallel or antiparallel + // In that case, fall back to the normal algorithm. + if( DotProduct( textureSpaceBasis[1], textureSpaceBasis[1] ) > 1e-6 ) + { + // S = N cross T + CrossProduct( textureSpaceBasis[2], textureSpaceBasis[1], textureSpaceBasis[0] ); + + VectorNormalizeFast( textureSpaceBasis[0] ); + VectorNormalizeFast( textureSpaceBasis[1] ); + return; + } + // Fall through to the standard algorithm for parallel or antiparallel + } +#endif + VectorNormalize2( surf->texinfo->vecs[0], textureSpaceBasis[0] ); + VectorNormalize2( surf->texinfo->vecs[1], textureSpaceBasis[1] ); +} + +void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) +{ + int width, height; + + // Compute the non-scaled decal basis + R_DecalComputeBasis( surf, pDecal->flags, textureSpaceBasis ); + R_GetDecalDimensions( texture, &width, &height ); + + // world width of decal = ptexture->width / pDecal->scale + // world height of decal = ptexture->height / pDecal->scale + // scale is inverse, scales world space to decal u/v space [0,1] + // OPTIMIZE: Get rid of these divides + decalWorldScale[0] = (float)pDecal->scale / width; + decalWorldScale[1] = (float)pDecal->scale / height; + + VectorScale( textureSpaceBasis[0], decalWorldScale[0], textureSpaceBasis[0] ); + VectorScale( textureSpaceBasis[1], decalWorldScale[1], textureSpaceBasis[1] ); +} + +// Build the initial list of vertices from the surface verts into the global array, 'verts'. +void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts ) +{ + float *v; + int i; + + for( i = 0; i < surf->polys->numverts; i++, verts += VERTEXSIZE ) + { + VectorCopy( surf->polys->verts[i].xyz, verts ); // copy model space coordinates + verts[3] = DotProduct( verts, textureSpaceBasis[0] ) - pDecal->dx + 0.5f; + verts[4] = DotProduct( verts, textureSpaceBasis[1] ) - pDecal->dy + 0.5f; + verts[5] = verts[6] = 0.0f; + } +} + +// Figure out where the decal maps onto the surface. +void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) +{ + R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, textureSpaceBasis, decalWorldScale ); + + // Generate texture coordinates for each vertex in decal s,t space + // probably should pre-generate this, store it and use it for decal-decal collisions + // as in R_DecalsIntersect() + pDecal->dx = DotProduct( pDecal->position, textureSpaceBasis[0] ); + pDecal->dy = DotProduct( pDecal->position, textureSpaceBasis[1] ); +} + +// Quick and dirty sutherland Hodgman clipper +// Clip polygon to decal in texture space +// JAY: This code is lame, change it later. It does way too much work per frame +// It can be made to recursively call the clipping code and only copy the vertex list once +int R_ClipInside( float *vert, int edge ) +{ + switch( edge ) + { + case LEFT_EDGE: + if( vert[3] > 0.0f ) + return 1; + return 0; + case RIGHT_EDGE: + if( vert[3] < 1.0f ) + return 1; + return 0; + case TOP_EDGE: + if( vert[4] > 0.0f ) + return 1; + return 0; + case BOTTOM_EDGE: + if( vert[4] < 1.0f ) + return 1; + return 0; + } + return 0; +} + +void R_ClipIntersect( float *one, float *two, float *out, int edge ) +{ + float t; + + // t is the parameter of the line between one and two clipped to the edge + // or the fraction of the clipped point between one & two + // vert[0], vert[1], vert[2] is X, Y, Z + // vert[3] is u + // vert[4] is v + // vert[5] is lightmap u + // vert[6] is lightmap v + + if( edge < TOP_EDGE ) + { + if( edge == LEFT_EDGE ) + { + // left + t = ((one[3] - 0.0f) / (one[3] - two[3])); + out[3] = out[5] = 0.0f; + } + else + { + // right + t = ((one[3] - 1.0f) / (one[3] - two[3])); + out[3] = out[5] = 1.0f; + } + + out[4] = one[4] + (two[4] - one[4]) * t; + out[6] = one[6] + (two[6] - one[6]) * t; + } + else + { + if( edge == TOP_EDGE ) + { + // top + t = ((one[4] - 0.0f) / (one[4] - two[4])); + out[4] = out[6] = 0.0f; + } + else + { + // bottom + t = ((one[4] - 1.0f) / (one[4] - two[4])); + out[4] = out[6] = 1.0f; + } + + out[3] = one[3] + (two[3] - one[3]) * t; + out[5] = one[5] + (two[4] - one[5]) * t; + } + + VectorLerp( one, t, two, out ); +} + +static int SHClip( float *vert, int vertCount, float *out, int edge ) +{ + int j, outCount; + float *s, *p; + + outCount = 0; + + s = &vert[(vertCount - 1) * VERTEXSIZE]; + + for( j = 0; j < vertCount; j++ ) + { + p = &vert[j * VERTEXSIZE]; + + if( R_ClipInside( p, edge )) + { + if( R_ClipInside( s, edge )) + { + // Add a vertex and advance out to next vertex + memcpy( out, p, sizeof( float ) * VERTEXSIZE ); + out += VERTEXSIZE; + outCount++; + } + else + { + R_ClipIntersect( s, p, out, edge ); + out += VERTEXSIZE; + outCount++; + + memcpy( out, p, sizeof( float ) * VERTEXSIZE ); + out += VERTEXSIZE; + outCount++; + } + } + else + { + if( R_ClipInside( s, edge )) + { + R_ClipIntersect( p, s, out, edge ); + out += VERTEXSIZE; + outCount++; + } + } + + s = p; + } + + return outCount; +} + +float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount ) +{ + float *pOutVerts = g_DecalClipVerts[0]; + int outCount; + + // clip the polygon to the decal texture space + outCount = SHClip( pInVerts, nStartVerts, g_DecalClipVerts2[0], LEFT_EDGE ); + outCount = SHClip( g_DecalClipVerts2[0], outCount, g_DecalClipVerts[0], RIGHT_EDGE ); + outCount = SHClip( g_DecalClipVerts[0], outCount, g_DecalClipVerts2[0], TOP_EDGE ); + outCount = SHClip( g_DecalClipVerts2[0], outCount, pOutVerts, BOTTOM_EDGE ); + + if( pVertCount ) + *pVertCount = outCount; + + return pOutVerts; +} + +//----------------------------------------------------------------------------- +// Generate clipped vertex list for decal pdecal projected onto polygon psurf +//----------------------------------------------------------------------------- +float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount ) +{ + float decalWorldScale[2]; + vec3_t textureSpaceBasis[3]; + + // figure out where the decal maps onto the surface. + R_SetupDecalClip( pDecal, surf, texture, textureSpaceBasis, decalWorldScale ); + + // build the initial list of vertices from the surface verts. + R_SetupDecalVertsForMSurface( pDecal, surf, textureSpaceBasis, g_DecalClipVerts[0] ); + + return R_DoDecalSHClip( g_DecalClipVerts[0], pDecal, surf->polys->numverts, pVertCount ); +} + +// Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf +static void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount ) +{ + float s, t; + mtexinfo_t *tex; + mextrasurf_t *info = surf->info; + float sample_size; + int j; + + sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); + tex = surf->texinfo; + + for( j = 0; j < vertCount; j++, v += VERTEXSIZE ) + { + // lightmap texture coordinates + s = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; + s += surf->light_s * sample_size; + s += sample_size * 0.5f; + s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + + t = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; + t += surf->light_t * sample_size; + t += sample_size * 0.5f; + t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; + + v[5] = s; + v[6] = t; + } +} + +// Check for intersecting decals on this surface +static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int *pcount ) +{ + int texture; + decal_t *plast, *pDecal; + vec3_t decalExtents[2]; + float lastArea = 2; + int mapSize[2]; + + plast = NULL; + *pcount = 0; + + // (Same as R_SetupDecalClip). + texture = decalinfo->m_iTexture; + + // precalculate the extents of decalinfo's decal in world space. + R_GetDecalDimensions( texture, &mapSize[0], &mapSize[1] ); + VectorScale( decalinfo->m_Basis[0], ((mapSize[0] / decalinfo->m_scale) * 0.5f), decalExtents[0] ); + VectorScale( decalinfo->m_Basis[1], ((mapSize[1] / decalinfo->m_scale) * 0.5f), decalExtents[1] ); + + pDecal = surf->pdecals; + + while( pDecal ) + { + texture = pDecal->texture; + + // Don't steal bigger decals and replace them with smaller decals + // Don't steal permanent decals + if( !FBitSet( pDecal->flags, FDECAL_PERMANENT )) + { + vec3_t testBasis[3]; + vec3_t testPosition[2]; + float testWorldScale[2]; + vec2_t vDecalMin, vDecalMax; + vec2_t vUnionMin, vUnionMax; + + R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, testBasis, testWorldScale ); + + VectorSubtract( decalinfo->m_Position, decalExtents[0], testPosition[0] ); + VectorSubtract( decalinfo->m_Position, decalExtents[1], testPosition[1] ); + + // Here, we project the min and max extents of the decal that got passed in into + // this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were + // clipping a triangle into pDecal's clip space. + Vector2Set( vDecalMin, + DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f, + DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f ); + + VectorAdd( decalinfo->m_Position, decalExtents[0], testPosition[0] ); + VectorAdd( decalinfo->m_Position, decalExtents[1], testPosition[1] ); + + Vector2Set( vDecalMax, + DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f, + DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f ); + + // Now figure out the part of the projection that intersects pDecal's + // clip box [0,0,1,1]. + Vector2Set( vUnionMin, max( vDecalMin[0], 0 ), max( vDecalMin[1], 0 )); + Vector2Set( vUnionMax, min( vDecalMax[0], 1 ), min( vDecalMax[1], 1 )); + + if( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 ) + { + // Figure out how much of this intersects the (0,0) - (1,1) bbox. + float flArea = (vUnionMax[0] - vUnionMin[1]) * (vUnionMax[1] - vUnionMin[1]); + + if( flArea > 0.6f ) + { + *pcount += 1; + + if( !plast || flArea <= lastArea ) + { + plast = pDecal; + lastArea = flArea; + } + } + } + } + pDecal = pDecal->pnext; + } + return plast; +} + +/* +==================== +R_DecalCreatePoly + +creates mesh for decal on first rendering +==================== +*/ +glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf ) +{ + int lnumverts; + glpoly_t *poly; + float *v; + int i; + + if( pdecal->polys ) // already created? + return pdecal->polys; + + v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &lnumverts ); + if( !lnumverts ) return NULL; // probably this never happens + + // allocate glpoly + // REFTODO: com_studiocache pool! + poly = Mem_Calloc( r_temppool, sizeof( glpoly_t ) + ( lnumverts * 2 - 1 ) * sizeof( gu_vert_t )); + poly->next = pdecal->polys; + poly->flags = surf->flags; + pdecal->polys = poly; + poly->numverts = lnumverts; + + for( i = 0; i < lnumverts; i++, v += VERTEXSIZE ) + { + poly->verts[i].uv[0] = v[3]; + poly->verts[i].uv[1] = v[4]; + VectorCopy( v, poly->verts[i].xyz ); + + poly->verts[i + lnumverts].uv[0] = v[5]; + poly->verts[i + lnumverts].uv[1] = v[6]; + VectorCopy( v, poly->verts[i + lnumverts].xyz ); + } + + return poly; +} + +// Add the decal to the surface's list of decals. +static void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t *decalinfo ) +{ + decal_t *pold; + + pdecal->pnext = NULL; + pold = surf->pdecals; + + if( pold ) + { + while( pold->pnext ) + pold = pold->pnext; + pold->pnext = pdecal; + } + else + { + surf->pdecals = pdecal; + } + + // tag surface + pdecal->psurface = surf; + + // at this point decal are linked with surface + // and will be culled, drawing and sorting + // together with surface + + // alloc clipped poly for decal + R_DecalCreatePoly( decalinfo, pdecal, surf ); +} + +static void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, float y ) +{ + decal_t *pdecal, *pold; + int count, vertCount; + + if( !surf ) return; // ??? + + pold = R_DecalIntersect( decalinfo, surf, &count ); + if( count < MAX_OVERLAP_DECALS ) pold = NULL; + + pdecal = R_DecalAlloc( pold ); + if( !pdecal ) return; // r_decals == 0 ??? + + pdecal->flags = decalinfo->m_Flags; + + VectorCopy( decalinfo->m_Position, pdecal->position ); + + pdecal->dx = x; + pdecal->dy = y; + + // set scaling + pdecal->scale = decalinfo->m_scale; + pdecal->entityIndex = decalinfo->m_Entity; + pdecal->texture = decalinfo->m_iTexture; + + // check to see if the decal actually intersects the surface + // if not, then remove the decal + R_DecalVertsClip( pdecal, surf, decalinfo->m_iTexture, &vertCount ); + + if( !vertCount ) + { + R_DecalUnlink( pdecal ); + return; + } + + // add to the surface's list + R_AddDecalToSurface( pdecal, surf, decalinfo ); +} + +void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) +{ + // get the texture associated with this surface + mtexinfo_t *tex = surf->texinfo; + decal_t *decal = surf->pdecals; + vec4_t textureU, textureV; + float s, t, w, h; + connstate_t state = ENGINE_GET_PARM( PARM_CONNSTATE ); + + // we in restore mode + if( state == ca_connected || state == ca_validate ) + { + // NOTE: we may have the decal on this surface that come from another level. + // check duplicate with same position and texture + while( decal != NULL ) + { + if( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture ) + return; // decal already exists, don't place it again + decal = decal->pnext; + } + } + + Vector4Copy( tex->vecs[0], textureU ); + Vector4Copy( tex->vecs[1], textureV ); + + // project decal center into the texture space of the surface + s = DotProduct( decalinfo->m_Position, textureU ) + textureU[3] - surf->texturemins[0]; + t = DotProduct( decalinfo->m_Position, textureV ) + textureV[3] - surf->texturemins[1]; + + // Determine the decal basis (measured in world space) + // Note that the decal basis vectors 0 and 1 will always lie in the same + // plane as the texture space basis vectorstextureVecsTexelsPerWorldUnits. + R_DecalComputeBasis( surf, decalinfo->m_Flags, decalinfo->m_Basis ); + + // Compute an effective width and height (axis aligned) in the parent texture space + // How does this work? decalBasis[0] represents the u-direction (width) + // of the decal measured in world space, decalBasis[1] represents the + // v-direction (height) measured in world space. + // textureVecsTexelsPerWorldUnits[0] represents the u direction of + // the surface's texture space measured in world space (with the appropriate + // scale factor folded in), and textureVecsTexelsPerWorldUnits[1] + // represents the texture space v direction. We want to find the dimensions (w,h) + // of a square measured in texture space, axis aligned to that coordinate system. + // All we need to do is to find the components of the decal edge vectors + // (decalWidth * decalBasis[0], decalHeight * decalBasis[1]) + // in texture coordinates: + + w = fabs( decalinfo->m_decalWidth * DotProduct( textureU, decalinfo->m_Basis[0] )) + + fabs( decalinfo->m_decalHeight * DotProduct( textureU, decalinfo->m_Basis[1] )); + + h = fabs( decalinfo->m_decalWidth * DotProduct( textureV, decalinfo->m_Basis[0] )) + + fabs( decalinfo->m_decalHeight * DotProduct( textureV, decalinfo->m_Basis[1] )); + + // move s,t to upper left corner + s -= ( w * 0.5f ); + t -= ( h * 0.5f ); + + // Is this rect within the surface? -- tex width & height are unsigned + if( s <= -w || t <= -h || s > (surf->extents[0] + w) || t > (surf->extents[1] + h)) + { + return; // nope + } + + // stamp it + R_DecalCreate( decalinfo, surf, s, t ); +} + +//----------------------------------------------------------------------------- +// iterate over all surfaces on a node, looking for surfaces to decal +//----------------------------------------------------------------------------- +static void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) +{ + // iterate over all surfaces in the node + msurface_t *surf; + int i; + + surf = model->surfaces + node->firstsurface; + + for( i = 0; i < node->numsurfaces; i++, surf++ ) + { + // never apply decals on the water or sky surfaces + if( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR)) + continue; + + if( surf->flags & SURF_TRANSPARENT && !glState.stencilEnabled ) + continue; + + R_DecalSurface( surf, decalinfo ); + } +} + +//----------------------------------------------------------------------------- +// Recursive routine to find surface to apply a decal to. World coordinates of +// the decal are passed in r_recalpos like the rest of the engine. This should +// be called through R_DecalShoot() +//----------------------------------------------------------------------------- +static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) +{ + mplane_t *splitplane; + float dist; + + Assert( node != NULL ); + + if( node->contents < 0 ) + { + // hit a leaf + return; + } + + splitplane = node->plane; + dist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist; + + // This is arbitrarily set to 10 right now. In an ideal world we'd have the + // exact surface but we don't so, this tells me which planes are "sort of + // close" to the gunshot -- the gunshot is actually 4 units in front of the + // wall (see dlls\weapons.cpp). We also need to check to see if the decal + // actually intersects the texture space of the surface, as this method tags + // parallel surfaces in the same node always. + // JAY: This still tags faces that aren't correct at edges because we don't + // have a surface normal + if( dist > decalinfo->m_Size ) + { + R_DecalNode( model, node->children[0], decalinfo ); + } + else if( dist < -decalinfo->m_Size ) + { + R_DecalNode( model, node->children[1], decalinfo ); + } + else + { + if( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE ) + R_DecalNodeSurfaces( model, node, decalinfo ); + + R_DecalNode( model, node->children[0], decalinfo ); + R_DecalNode( model, node->children[1], decalinfo ); + } +} + +// Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords +void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale ) +{ + decalinfo_t decalInfo; + cl_entity_t *ent = NULL; + model_t *model = NULL; + int width, height; + hull_t *hull; + + if( textureIndex <= 0 || textureIndex >= MAX_TEXTURES ) + { + gEngfuncs.Con_Printf( S_ERROR "Decal has invalid texture!\n" ); + return; + } + + if( entityIndex > 0 ) + { + ent = gEngfuncs.GetEntityByIndex( entityIndex ); + + if( modelIndex > 0 ) model = gEngfuncs.pfnGetModelByIndex( modelIndex ); + else if( ent != NULL ) model = gEngfuncs.pfnGetModelByIndex( ent->curstate.modelindex ); + else return; + } + else if( modelIndex > 0 ) + model = gEngfuncs.pfnGetModelByIndex( modelIndex ); + else model = WORLDMODEL; + + if( !model ) return; + + if( model->type != mod_brush ) + { + gEngfuncs.Con_Printf( S_ERROR "Decals must hit mod_brush!\n" ); + return; + } + + decalInfo.m_pModel = model; + hull = &model->hulls[0]; // always use #0 hull + + // NOTE: all the decals at 'first shoot' placed into local space of parent entity + // and won't transform again on a next restore, levelchange etc + if( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE )) + { + vec3_t pos_l; + + // transform decal position in local bmodel space + if( !VectorIsNull( ent->angles )) + { + matrix4x4 matrix; + + Matrix4x4_CreateFromEntity( matrix, ent->angles, ent->origin, 1.0f ); + Matrix4x4_VectorITransform( matrix, pos, pos_l ); + } + else + { + VectorSubtract( pos, ent->origin, pos_l ); + } + + VectorCopy( pos_l, decalInfo.m_Position ); + // decal position moved into local space + SetBits( flags, FDECAL_LOCAL_SPACE ); + } + else + { + // already in local space + VectorCopy( pos, decalInfo.m_Position ); + } + + // this decal must use landmark for correct transition + // because their model exist only in world-space + if( !FBitSet( model->flags, MODEL_HAS_ORIGIN )) + SetBits( flags, FDECAL_USE_LANDMARK ); + + // more state used by R_DecalNode() + decalInfo.m_iTexture = textureIndex; + decalInfo.m_Entity = entityIndex; + decalInfo.m_Flags = flags; + + R_GetDecalDimensions( textureIndex, &width, &height ); + decalInfo.m_Size = width >> 1; + if(( height >> 1 ) > decalInfo.m_Size ) + decalInfo.m_Size = height >> 1; + + decalInfo.m_scale = bound( MIN_DECAL_SCALE, scale, MAX_DECAL_SCALE ); + + // compute the decal dimensions in world space + decalInfo.m_decalWidth = width / decalInfo.m_scale; + decalInfo.m_decalHeight = height / decalInfo.m_scale; + + R_DecalNode( model, &model->nodes[hull->firstclipnode], &decalInfo ); +} + +// Build the vertex list for a decal on a surface and clip it to the surface. +// This is a template so it can work on world surfaces and dynamic displacement +// triangles the same way. +float *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount ) +{ + glpoly_t *p = pDecal->polys; + int i, count; + float *v; + + if( p ) + { + v = g_DecalClipVerts[0]; + count = p->numverts; + + // if we have mesh so skip clipping and just copy vertexes out (perf) + for( i = 0; i < count; i++, v += VERTEXSIZE ) + { + VectorCopy( p->verts[i].xyz, v ); + v[3] = p->verts[i].uv[0]; + v[4] = p->verts[i].uv[1]; + v[5] = p->verts[i + count].uv[0]; + v[6] = p->verts[i + count].uv[1]; + } + + // restore pointer + v = g_DecalClipVerts[0]; + } + else + { + v = R_DecalVertsClip( pDecal, surf, texture, &count ); + R_DecalVertsLight( v, surf, count ); + } + + if( outCount ) + *outCount = count; + + return v; +} + +void DrawSingleDecal( decal_t *pDecal, msurface_t *fa ) +{ + float *v; + int i, numVerts; + + v = R_DecalSetupVerts( pDecal, fa, pDecal->texture, &numVerts ); + if( !numVerts ) return; + + GL_Bind( XASH_TEXTURE0, pDecal->texture ); + + gu_vert_t* const out = ( gu_vert_t* )sceGuGetMemory( sizeof( gu_vert_t ) * numVerts ); + for( i = 0; i < numVerts; i++, v += VERTEXSIZE ) + { + out[i].uv[0] = v[3]; + out[i].uv[1] = v[4]; + VectorCopy( v, out[i].xyz ); + } + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, numVerts, 0, out ); +} + +void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse ) +{ + decal_t *p; + cl_entity_t *e; + + if( !fa->pdecals ) return; + + e = RI.currententity; + Assert( e != NULL ); + + if( single ) + { + if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha ) + { + sceGuDepthMask( GU_TRUE ); + sceGuEnable( GU_BLEND ); + + if( e->curstate.rendermode == kRenderTransAlpha ) + sceGuDisable( GU_ALPHA_TEST ); + } + + if( e->curstate.rendermode == kRenderTransColor ) + sceGuEnable( GU_TEXTURE_2D ); + + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + GL_Cull( GL_NONE ); + + if( gl_depthoffset->value ) + sceGuDepthOffset( ( int )-gl_depthoffset->value ); + } + + if( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled ) + { + mtexinfo_t *tex = fa->texinfo; + + for( p = fa->pdecals; p; p = p->pnext ) + { + if( p->texture ) + { + float *o, *v; + int i, numVerts; + o = R_DecalSetupVerts( p, fa, p->texture, &numVerts ); + + sceGuEnable( GU_STENCIL_TEST ); + sceGuStencilFunc( GU_ALWAYS, 1, 0xffffffff ); + sceGuPixelMask( 0xffffffff ); + + sceGuStencilOp( GU_KEEP, GU_KEEP, GU_REPLACE ); + gu_vert_t* const out = ( gu_vert_t* )sceGuGetMemory( sizeof( gu_vert_t ) * numVerts ); + for( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE ) + { + out[i].uv[0] = ( DotProduct( v, tex->vecs[0] ) + tex->vecs[0][3] ) / tex->texture->width; + out[i].uv[1] = ( DotProduct( v, tex->vecs[1] ) + tex->vecs[1][3] ) / tex->texture->height; + VectorCopy( v, out[i].xyz ); + } + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, numVerts, 0, out ); +#if 0 /* two layer ??? */ + sceGuStencilOp( GU_KEEP, GU_KEEP, GU_DECR ); + sceGuEnable( GU_ALPHA_TEST ); + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, numVerts, 0, out ); + sceGuDisable( GU_ALPHA_TEST ); +#endif + sceGuPixelMask( 0x00000000 ); + sceGuStencilFunc( GU_EQUAL, 0, 0xffffffff ); + sceGuStencilOp( GU_KEEP, GU_KEEP, GU_KEEP ); + } + } + } + + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + + if( reverse && e->curstate.rendermode == kRenderTransTexture ) + { + decal_t *list[1024]; + int i, count; + + for( p = fa->pdecals, count = 0; p && count < 1024; p = p->pnext ) + if( p->texture ) list[count++] = p; + + for( i = count - 1; i >= 0; i-- ) + DrawSingleDecal( list[i], fa ); + } + else + { + for( p = fa->pdecals; p; p = p->pnext ) + { + if( !p->texture ) continue; + DrawSingleDecal( p, fa ); + } + } + + if( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled ) + sceGuDisable( GU_STENCIL_TEST ); + + if( single ) + { + if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha ) + { + sceGuDepthMask( GU_FALSE ); + sceGuDisable( GU_BLEND ); + + if( e->curstate.rendermode == kRenderTransAlpha ) + sceGuEnable( GU_ALPHA_TEST ); + } + + if( gl_depthoffset->value ) + sceGuDepthOffset( 0 ); + + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + GL_Cull( GL_FRONT ); + + if( e->curstate.rendermode == kRenderTransColor ) + sceGuDisable( GU_TEXTURE_2D ); + + // restore blendfunc here + if( e->curstate.rendermode == kRenderTransAdd || e->curstate.rendermode == kRenderGlow ) + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, GUBLEND1 ); + + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + } +} + +void DrawDecalsBatch( void ) +{ + cl_entity_t *e; + int i; + + if( !tr.num_draw_decals ) + return; + + e = RI.currententity; + Assert( e != NULL ); + + if( e->curstate.rendermode != kRenderTransTexture ) + { + sceGuEnable( GU_BLEND ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuDepthMask( GU_TRUE ); + } + + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + GL_Cull( GL_NONE ); + + if( gl_depthoffset->value ) + sceGuDepthOffset( ( int )-gl_depthoffset->value ); + + for( i = 0; i < tr.num_draw_decals; i++ ) + { + DrawSurfaceDecals( tr.draw_decals[i], false, false ); + } + + if( e->curstate.rendermode != kRenderTransTexture ) + { + sceGuDepthMask( GU_FALSE ); + sceGuDisable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + } + + if( gl_depthoffset->value ) + sceGuDepthOffset( 0 ); + + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + GL_Cull( GL_FRONT ); + + tr.num_draw_decals = 0; +} + +/* +============================================================= + + DECALS SERIALIZATION + +============================================================= +*/ +static qboolean R_DecalUnProject( decal_t *pdecal, decallist_t *entry ) +{ + if( !pdecal || !( pdecal->psurface )) + return false; + + VectorCopy( pdecal->position, entry->position ); + entry->entityIndex = pdecal->entityIndex; + + // Grab surface plane equation + if( pdecal->psurface->flags & SURF_PLANEBACK ) + VectorNegate( pdecal->psurface->plane->normal, entry->impactPlaneNormal ); + else VectorCopy( pdecal->psurface->plane->normal, entry->impactPlaneNormal ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pList - +// count - +// Output : static int +//----------------------------------------------------------------------------- +static int DecalListAdd( decallist_t *pList, int count ) +{ + vec3_t tmp; + decallist_t *pdecal; + int i; + + pdecal = pList + count; + + for( i = 0; i < count; i++ ) + { + if( !Q_strcmp( pdecal->name, pList[i].name ) && pdecal->entityIndex == pList[i].entityIndex ) + { + VectorSubtract( pdecal->position, pList[i].position, tmp ); // Merge + + if( VectorLength( tmp ) < DECAL_OVERLAP_DISTANCE ) + return count; + } + } + + // this is a new decal + return count + 1; +} + +static int DecalDepthCompare( const void *a, const void *b ) +{ + const decallist_t *elem1, *elem2; + + elem1 = (const decallist_t *)a; + elem2 = (const decallist_t *)b; + + if( elem1->depth > elem2->depth ) + return 1; + if( elem1->depth < elem2->depth ) + return -1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Called by CSaveRestore::SaveClientState +// Input : *pList - +// Output : int +//----------------------------------------------------------------------------- +int R_CreateDecalList( decallist_t *pList ) +{ + int total = 0; + int i, depth; + + if( WORLDMODEL ) + { + for( i = 0; i < MAX_RENDER_DECALS; i++ ) + { + decal_t *decal = &gDecalPool[i]; + decal_t *pdecals; + + // decal is in use and is not a custom decal + if( decal->psurface == NULL || FBitSet( decal->flags, FDECAL_DONTSAVE )) + continue; + + // compute depth + depth = 0; + pdecals = decal->psurface->pdecals; + + while( pdecals && pdecals != decal ) + { + depth++; + pdecals = pdecals->pnext; + } + + pList[total].depth = depth; + pList[total].flags = decal->flags; + pList[total].scale = decal->scale; + + R_DecalUnProject( decal, &pList[total] ); + COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name ); + + // check to see if the decal should be added + total = DecalListAdd( pList, total ); + } + + if( gEngfuncs.drawFuncs->R_CreateStudioDecalList ) + { + total += gEngfuncs.drawFuncs->R_CreateStudioDecalList( pList, total ); + } + } + + // sort the decals lowest depth first, so they can be re-applied in order + qsort( pList, total, sizeof( decallist_t ), DecalDepthCompare ); + + return total; +} + +/* +=============== +R_DecalRemoveAll + +remove all decals with specified texture +=============== +*/ +void R_DecalRemoveAll( int textureIndex ) +{ + decal_t *pdecal; + int i; + + if( textureIndex < 0 || textureIndex >= MAX_TEXTURES ) + return; // out of bounds + + for( i = 0; i < gDecalCount; i++ ) + { + pdecal = &gDecalPool[i]; + + // don't remove permanent decals + if( !textureIndex && FBitSet( pdecal->flags, FDECAL_PERMANENT )) + continue; + + if( !textureIndex || ( pdecal->texture == textureIndex )) + R_DecalUnlink( pdecal ); + } +} + +/* +=============== +R_EntityRemoveDecals + +remove all decals from specified entity +=============== +*/ +void R_EntityRemoveDecals( model_t *mod ) +{ + msurface_t *psurf; + decal_t *p; + int i; + + if( !mod || mod->type != mod_brush ) + return; + + psurf = &mod->surfaces[mod->firstmodelsurface]; + for( i = 0; i < mod->nummodelsurfaces; i++, psurf++ ) + { + for( p = psurf->pdecals; p; p = p->pnext ) + R_DecalUnlink( p ); + } +} + +/* +=============== +R_ClearAllDecals + +remove all decals from anything +used for full decals restart +=============== +*/ +void R_ClearAllDecals( void ) +{ + decal_t *pdecal; + int i; + + // because gDecalCount may be zeroed after recach the decal limit + for( i = 0; i < MAX_RENDER_DECALS; i++ ) + { + pdecal = &gDecalPool[i]; + R_DecalUnlink( pdecal ); + } + + if( gEngfuncs.drawFuncs->R_ClearStudioDecals ) + { + gEngfuncs.drawFuncs->R_ClearStudioDecals(); + } +} diff --git a/ref_gu/gu_draw.c b/ref_gu/gu_draw.c new file mode 100644 index 000000000..5b77462ae --- /dev/null +++ b/ref_gu/gu_draw.c @@ -0,0 +1,308 @@ +/* +gu_draw.c - orthogonal drawing stuff +Copyright (C) 2010 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" + +/* +============= +R_GetImageParms +============= +*/ +void R_GetTextureParms( int *w, int *h, int texnum ) +{ + gl_texture_t *glt; + + glt = R_GetTexture( texnum ); + if( w ) *w = glt->srcWidth; + if( h ) *h = glt->srcHeight; +} + +/* +============= +R_GetSpriteParms + +same as GetImageParms but used +for sprite models +============= +*/ +void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite ) +{ + mspriteframe_t *pFrame; + + if( !pSprite || pSprite->type != mod_sprite ) return; // bad model ? + pFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f ); + + if( frameWidth ) *frameWidth = pFrame->width; + if( frameHeight ) *frameHeight = pFrame->height; + if( numFrames ) *numFrames = pSprite->numframes; +} + +int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame ) +{ + if( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data ) + return 0; + + return R_GetSpriteFrame( m_pSpriteModel, frame, 0.0f )->gl_texturenum; +} + +/* +============= +R_DrawStretchPic +============= +*/ +void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ) +{ + float uw, uh; + gl_texture_t *glt; + + GL_Bind( XASH_TEXTURE0, texnum ); + + glt = R_GetTexture( texnum ); + + gu_vert_htv_t* const out = ( gu_vert_htv_t* )sceGuGetMemory( sizeof( gu_vert_htv_t ) * 2 ); + out[0].u = s1 * glt->width; + out[0].v = t1 * glt->height; + out[0].x = x; + out[0].y = y; + out[0].z = 0; + out[1].u = s2 * glt->width; + out[1].v = t2 * glt->height; + out[1].x = x + w; + out[1].y = y + h; + out[1].z = 0; + sceGuDrawArray( GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, out ); +} + +/* +============= +Draw_TileClear + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +void R_DrawTileClear( int texnum, int x, int y, int w, int h ) +{ + float tw, th; + gl_texture_t *glt; + + GL_SetRenderMode( kRenderNormal ); + + sceGuColor( 0xffffffff ); + + GL_Bind( XASH_TEXTURE0, texnum ); + + glt = R_GetTexture( texnum ); + tw = glt->srcWidth; + th = glt->srcHeight; + + gu_vert_htv_t* const out = ( gu_vert_htv_t* )sceGuGetMemory( sizeof( gu_vert_ftv_t ) * 2 ); + out[0].u = x / tw * glt->width; + out[0].v = y / th * glt->height; + out[0].x = x; + out[0].y = y; + out[0].z = 0; + out[1].u = ( x + w ) / tw * glt->width; + out[1].v = ( y + h ) / th * glt->height; + out[1].x = x + w; + out[1].y = y + h; + out[1].z = 0; + sceGuDrawArray( GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, out ); +} + +/* +============= +R_DrawStretchRaw +============= +*/ +void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ) +{ +#if 0 + byte *raw = NULL; + gl_texture_t *tex; + + if( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT )) + { + int width = 1, height = 1; + + // check the dimensions + width = NearestPOW( cols, true ); + height = NearestPOW( rows, false ); + + if( cols != width || rows != height ) + { + raw = GL_ResampleTexture( data, cols, rows, width, height, false ); + cols = width; + rows = height; + } + } + else + { + raw = (byte *)data; + } + + if( cols > glConfig.max_2d_texture_size ) + gEngfuncs.Host_Error( "R_DrawStretchRaw: size %i exceeds hardware limits\n", cols ); + if( rows > glConfig.max_2d_texture_size ) + gEngfuncs.Host_Error( "R_DrawStretchRaw: size %i exceeds hardware limits\n", rows ); + + pglDisable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + + tex = R_GetTexture( tr.cinTexture ); + GL_Bind( XASH_TEXTURE0, tr.cinTexture ); + + if( cols == tex->width && rows == tex->height ) + { + if( dirty ) + { + pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_BGRA, GL_UNSIGNED_BYTE, raw ); + } + } + else + { + tex->size = cols * rows * 4; + tex->width = cols; + tex->height = rows; + if( dirty ) + { + pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, cols, rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw ); + } + } + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0, 0 ); + pglVertex2f( x, y ); + pglTexCoord2f( 1, 0 ); + pglVertex2f( x + w, y ); + pglTexCoord2f( 1, 1 ); + pglVertex2f( x + w, y + h ); + pglTexCoord2f( 0, 1 ); + pglVertex2f( x, y + h ); + pglEnd(); +#endif +} + +/* +============= +R_UploadStretchRaw +============= +*/ +void R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data ) +{ +#if 0 + byte *raw = NULL; + gl_texture_t *tex; + + if( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT )) + { + // check the dimensions + width = NearestPOW( width, true ); + height = NearestPOW( height, false ); + } + else + { + width = bound( 128, width, glConfig.max_2d_texture_size ); + height = bound( 128, height, glConfig.max_2d_texture_size ); + } + + if( cols != width || rows != height ) + { + raw = GL_ResampleTexture( data, cols, rows, width, height, false ); + cols = width; + rows = height; + } + else + { + raw = (byte *)data; + } + + if( cols > glConfig.max_2d_texture_size ) + gEngfuncs.Host_Error( "R_UploadStretchRaw: size %i exceeds hardware limits\n", cols ); + if( rows > glConfig.max_2d_texture_size ) + gEngfuncs.Host_Error( "R_UploadStretchRaw: size %i exceeds hardware limits\n", rows ); + + tex = R_GetTexture( texture ); + GL_Bind( GL_KEEP_UNIT, texture ); + tex->width = cols; + tex->height = rows; + + pglTexImage2D( GL_TEXTURE_2D, 0, tex->format, cols, rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw ); + GL_ApplyTextureParams( tex ); +#endif +} + +/* +=============== +R_Set2DMode +=============== +*/ +void R_Set2DMode( qboolean enable ) +{ + if( enable ) + { + if( glState.in2DMode ) + return; + + // set 2D virtual screen size +#if 1 // through mode + sceGuViewport( 2048, 2048, gpGlobals->width, gpGlobals->height ); + sceGuScissor( 0, 0, gpGlobals->width, gpGlobals->height ); +#else + pglViewport( 0, 0, gpGlobals->width, gpGlobals->height ); + pglMatrixMode( GL_PROJECTION ); + pglLoadIdentity(); + pglOrtho( 0, gpGlobals->width, gpGlobals->height, 0, -99999, 99999 ); + pglMatrixMode( GL_MODELVIEW ); + pglLoadIdentity(); +#endif + GL_Cull( GL_NONE ); +#if 1 + sceGuDepthMask( GU_TRUE ); + sceGuDisable( GU_DEPTH_TEST ); + sceGuEnable( GU_ALPHA_TEST ); + sceGuColor( 0xffffffff ); +#else + pglDepthMask( GL_FALSE ); + pglDisable( GL_DEPTH_TEST ); + pglEnable( GL_ALPHA_TEST ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); +#endif + glState.in2DMode = true; + RI.currententity = NULL; + RI.currentmodel = NULL; + } + else + { +#if 1 + sceGuDepthMask( GU_FALSE ); + sceGuEnable( GU_DEPTH_TEST ); + glState.in2DMode = false; +#else + pglDepthMask( GL_TRUE ); + pglEnable( GL_DEPTH_TEST ); + glState.in2DMode = false; + + pglMatrixMode( GL_PROJECTION ); + GL_LoadMatrix( RI.projectionMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.worldviewMatrix ); +#endif + GL_Cull( GL_FRONT ); + } +} diff --git a/ref_gu/gu_extension.c b/ref_gu/gu_extension.c new file mode 100644 index 000000000..260783dc6 --- /dev/null +++ b/ref_gu/gu_extension.c @@ -0,0 +1,79 @@ +/* +gu_extension.c - PSP Graphic Unit extensions +Copyright (C) 2020 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include +#include +#include +#include +#include "gu_extension.h" + +/* from guInternal.h */ +typedef struct +{ + unsigned int* start; + unsigned int* current; + int parent_context; +} GuDisplayList; +extern GuDisplayList* gu_list; +extern int gu_curr_context; +extern int ge_list_executed[]; + +static unsigned int gu_list_size; + + +void extGuStart(int cid, void* list, int size) +{ + gu_list_size = size; + sceGuStart( cid, list ); +} + +/* Begin user packet */ +void *extGuBeginPacket( unsigned int *maxsize ) +{ + unsigned int* current_ptr = gu_list->current; + if( maxsize != NULL ) + { + unsigned int size = ( ( unsigned int )gu_list->current ) - ( ( unsigned int )gu_list->start ); + *maxsize = ( ( unsigned int )gu_list_size ) - ( ( unsigned int )size ); + } + return current_ptr + 2; +} + +/* End user packet */ +void extGuEndPacket( void *eaddr ) +{ + unsigned int* current_ptr = gu_list->current; + unsigned int size = ( ( unsigned int ) eaddr ) - ( ( unsigned int )current_ptr ); + + if( size > 0 ) + { + size += 3; + size += ( ( unsigned int )( size >> 31 ) ) >> 30; + size = ( size >> 2 ) << 2; + + unsigned int* new_ptr = ( unsigned int* )( ( ( unsigned int ) current_ptr ) + size + 8 ); + + int lo = ( 8 << 24 ) | ( ( ( unsigned int )new_ptr ) & 0xffffff ); + int hi = ( 16 << 24 ) | ( ( ( ( unsigned int )new_ptr ) >> 8 ) & 0xf0000 ); + + current_ptr[0] = hi; + current_ptr[1] = lo; + + gu_list->current = new_ptr; + + if ( !gu_curr_context ) + sceGeListUpdateStallAddr( ge_list_executed[0], new_ptr ); + } +} \ No newline at end of file diff --git a/ref_gu/gu_extension.h b/ref_gu/gu_extension.h new file mode 100644 index 000000000..7273870e8 --- /dev/null +++ b/ref_gu/gu_extension.h @@ -0,0 +1,23 @@ +/* +gu_extension.h - PSP Graphic Unit extensions header +Copyright (C) 2020 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#ifndef GU_EXTENSION_H +#define GU_EXTENSION_H + +void extGuStart(int cid, void* list, int size); +void *extGuBeginPacket( unsigned int *maxsize ); +void extGuEndPacket( void *eaddr ); + +#endif//GU_EXTENSION_H diff --git a/ref_gu/gu_frustum.c b/ref_gu/gu_frustum.c new file mode 100644 index 000000000..35c97c69e --- /dev/null +++ b/ref_gu/gu_frustum.c @@ -0,0 +1,356 @@ +/* +gu_frustum.cpp - frustum test implementation +Copyright (C) 2016 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "xash3d_mathlib.h" + +void GL_FrustumEnablePlane( gl_frustum_t *out, int side ) +{ + Assert( side >= 0 && side < FRUSTUM_PLANES ); + + // make sure what plane is ready + if( !VectorIsNull( out->planes[side].normal )) + SetBits( out->clipFlags, BIT( side )); +} + +void GL_FrustumDisablePlane( gl_frustum_t *out, int side ) +{ + Assert( side >= 0 && side < FRUSTUM_PLANES ); + ClearBits( out->clipFlags, BIT( side )); +} + +void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ) +{ + Assert( side >= 0 && side < FRUSTUM_PLANES ); + + out->planes[side].type = PlaneTypeForNormal( vecNormal ); + out->planes[side].signbits = SignbitsForPlane( vecNormal ); + VectorCopy( vecNormal, out->planes[side].normal ); + out->planes[side].dist = flDist; + + SetBits( out->clipFlags, BIT( side )); +} + +void GL_FrustumNormalizePlane( gl_frustum_t *out, int side ) +{ + float length; + + Assert( side >= 0 && side < FRUSTUM_PLANES ); + + // normalize + length = VectorLength( out->planes[side].normal ); + + if( length ) + { + float ilength = (1.0f / length); + out->planes[side].normal[0] *= ilength; + out->planes[side].normal[1] *= ilength; + out->planes[side].normal[2] *= ilength; + out->planes[side].dist *= ilength; + } + + out->planes[side].type = PlaneTypeForNormal( out->planes[side].normal ); + out->planes[side].signbits = SignbitsForPlane( out->planes[side].normal ); + + SetBits( out->clipFlags, BIT( side )); +} + +void GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY ) +{ + float xs, xc; + vec3_t farpoint, nearpoint; + vec3_t normal, iforward; + + // horizontal fov used for left and right planes + SinCos( DEG2RAD( flFovX ) * 0.5f, &xs, &xc ); + + // setup left plane + VectorMAM( xs, RI.cull_vforward, -xc, RI.cull_vright, normal ); + GL_FrustumSetPlane( out, FRUSTUM_LEFT, normal, DotProduct( RI.cullorigin, normal )); + + // setup right plane + VectorMAM( xs, RI.cull_vforward, xc, RI.cull_vright, normal ); + GL_FrustumSetPlane( out, FRUSTUM_RIGHT, normal, DotProduct( RI.cullorigin, normal )); + + // vertical fov used for top and bottom planes + SinCos( DEG2RAD( flFovY ) * 0.5f, &xs, &xc ); + VectorNegate( RI.cull_vforward, iforward ); + + // setup bottom plane + VectorMAM( xs, RI.cull_vforward, -xc, RI.cull_vup, normal ); + GL_FrustumSetPlane( out, FRUSTUM_BOTTOM, normal, DotProduct( RI.cullorigin, normal )); + + // setup top plane + VectorMAM( xs, RI.cull_vforward, xc, RI.cull_vup, normal ); + GL_FrustumSetPlane( out, FRUSTUM_TOP, normal, DotProduct( RI.cullorigin, normal )); + + // setup far plane + VectorMA( RI.cullorigin, flZFar, RI.cull_vforward, farpoint ); + GL_FrustumSetPlane( out, FRUSTUM_FAR, iforward, DotProduct( iforward, farpoint )); + + // no need to setup backplane for general view. + if( flZNear == 0.0f ) return; + + // setup near plane + VectorMA( RI.cullorigin, flZNear, RI.cull_vforward, nearpoint ); + GL_FrustumSetPlane( out, FRUSTUM_NEAR, RI.cull_vforward, DotProduct( RI.cull_vforward, nearpoint )); +} + +void GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yTop, float yBottom, float flZNear, float flZFar ) +{ + vec3_t iforward, iright, iup; + + // setup the near and far planes + float orgOffset = DotProduct( RI.cullorigin, RI.cull_vforward ); + VectorNegate( RI.cull_vforward, iforward ); + + // because quake ortho is inverted and far and near should be swaped + GL_FrustumSetPlane( out, FRUSTUM_FAR, iforward, -flZNear - orgOffset ); + GL_FrustumSetPlane( out, FRUSTUM_NEAR, RI.cull_vforward, flZFar + orgOffset ); + + // setup left and right planes + orgOffset = DotProduct( RI.cullorigin, RI.cull_vright ); + VectorNegate( RI.cull_vright, iright ); + + GL_FrustumSetPlane( out, FRUSTUM_LEFT, RI.cull_vright, xLeft + orgOffset ); + GL_FrustumSetPlane( out, FRUSTUM_RIGHT, iright, -xRight - orgOffset ); + + // setup top and buttom planes + orgOffset = DotProduct( RI.cullorigin, RI.cull_vup ); + VectorNegate( RI.cull_vup, iup ); + + GL_FrustumSetPlane( out, FRUSTUM_TOP, RI.cull_vup, yTop + orgOffset ); + GL_FrustumSetPlane( out, FRUSTUM_BOTTOM, iup, -yBottom - orgOffset ); +} + +void GL_FrustumInitBox( gl_frustum_t *out, const vec3_t org, float radius ) +{ + vec3_t normal; + int i; + + for( i = 0; i < FRUSTUM_PLANES; i++ ) + { + // setup normal for each direction + VectorClear( normal ); + normal[((i >> 1) + 1) % 3] = (i & 1) ? 1.0f : -1.0f; + GL_FrustumSetPlane( out, i, normal, DotProduct( org, normal ) - radius ); + } +} + +void GL_FrustumInitProjFromMatrix( gl_frustum_t *out, const matrix4x4 projection ) +{ + int i; + + // left + out->planes[FRUSTUM_LEFT].normal[0] = projection[0][3] + projection[0][0]; + out->planes[FRUSTUM_LEFT].normal[1] = projection[1][3] + projection[1][0]; + out->planes[FRUSTUM_LEFT].normal[2] = projection[2][3] + projection[2][0]; + out->planes[FRUSTUM_LEFT].dist = -(projection[3][3] + projection[3][0]); + + // right + out->planes[FRUSTUM_RIGHT].normal[0] = projection[0][3] - projection[0][0]; + out->planes[FRUSTUM_RIGHT].normal[1] = projection[1][3] - projection[1][0]; + out->planes[FRUSTUM_RIGHT].normal[2] = projection[2][3] - projection[2][0]; + out->planes[FRUSTUM_RIGHT].dist = -(projection[3][3] - projection[3][0]); + + // bottom + out->planes[FRUSTUM_BOTTOM].normal[0] = projection[0][3] + projection[0][1]; + out->planes[FRUSTUM_BOTTOM].normal[1] = projection[1][3] + projection[1][1]; + out->planes[FRUSTUM_BOTTOM].normal[2] = projection[2][3] + projection[2][1]; + out->planes[FRUSTUM_BOTTOM].dist = -(projection[3][3] + projection[3][1]); + + // top + out->planes[FRUSTUM_TOP].normal[0] = projection[0][3] - projection[0][1]; + out->planes[FRUSTUM_TOP].normal[1] = projection[1][3] - projection[1][1]; + out->planes[FRUSTUM_TOP].normal[2] = projection[2][3] - projection[2][1]; + out->planes[FRUSTUM_TOP].dist = -(projection[3][3] - projection[3][1]); + + // near + out->planes[FRUSTUM_NEAR].normal[0] = projection[0][3] + projection[0][2]; + out->planes[FRUSTUM_NEAR].normal[1] = projection[1][3] + projection[1][2]; + out->planes[FRUSTUM_NEAR].normal[2] = projection[2][3] + projection[2][2]; + out->planes[FRUSTUM_NEAR].dist = -(projection[3][3] + projection[3][2]); + + // far + out->planes[FRUSTUM_FAR].normal[0] = projection[0][3] - projection[0][2]; + out->planes[FRUSTUM_FAR].normal[1] = projection[1][3] - projection[1][2]; + out->planes[FRUSTUM_FAR].normal[2] = projection[2][3] - projection[2][2]; + out->planes[FRUSTUM_FAR].dist = -(projection[3][3] - projection[3][2]); + + for( i = 0; i < FRUSTUM_PLANES; i++ ) + { + GL_FrustumNormalizePlane( out, i ); + } +} + +void GL_FrustumComputeCorners( gl_frustum_t *out, vec3_t corners[8] ) +{ + memset( corners, 0, sizeof( vec3_t ) * 8 ); + + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_FAR], corners[0] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_FAR], corners[1] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_FAR], corners[2] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_FAR], corners[3] ); + + if( FBitSet( out->clipFlags, BIT( FRUSTUM_NEAR ))) + { + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_NEAR], corners[4] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_NEAR], corners[5] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_NEAR], corners[6] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_NEAR], corners[7] ); + } + else + { + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], corners[4] ); + VectorCopy( corners[4], corners[5] ); + VectorCopy( corners[4], corners[6] ); + VectorCopy( corners[4], corners[7] ); + } +} + +void GL_FrustumComputeBounds( gl_frustum_t *out, vec3_t mins, vec3_t maxs ) +{ + vec3_t corners[8]; + int i; + + GL_FrustumComputeCorners( out, corners ); + + ClearBounds( mins, maxs ); + + for( i = 0; i < 8; i++ ) + AddPointToBounds( corners[i], mins, maxs ); +} + +void GL_FrustumDrawDebug( gl_frustum_t *out ) +{ +/* + vec3_t bbox[8]; + int i; + + GL_FrustumComputeCorners( out, bbox ); + + // g-cont. frustum must be yellow :-) + pglColor4f( 1.0f, 1.0f, 0.0f, 1.0f ); + pglDisable( GL_TEXTURE_2D ); + pglBegin( GL_LINES ); + + for( i = 0; i < 2; i += 1 ) + { + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i*2+0] ); + pglVertex3fv( bbox[i*2+1] ); + pglVertex3fv( bbox[i*2+4] ); + pglVertex3fv( bbox[i*2+5] ); + } + + pglEnd(); + pglEnable( GL_TEXTURE_2D ); +*/ +} + +// cull methods +qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ) +{ + int iClipFlags; + int i, bit; + + if( r_nocull->value ) + return false; + + if( userClipFlags != 0 ) + iClipFlags = userClipFlags; + else iClipFlags = out->clipFlags; + + for( i = FRUSTUM_PLANES, bit = 1; i > 0; i--, bit <<= 1 ) + { + const mplane_t *p = &out->planes[FRUSTUM_PLANES - i]; + + if( !FBitSet( iClipFlags, bit )) + continue; + + switch( p->signbits ) + { + case 0: + if( p->normal[0] * maxs[0] + p->normal[1] * maxs[1] + p->normal[2] * maxs[2] < p->dist ) + return true; + break; + case 1: + if( p->normal[0] * mins[0] + p->normal[1] * maxs[1] + p->normal[2] * maxs[2] < p->dist ) + return true; + break; + case 2: + if( p->normal[0] * maxs[0] + p->normal[1] * mins[1] + p->normal[2] * maxs[2] < p->dist ) + return true; + break; + case 3: + if( p->normal[0] * mins[0] + p->normal[1] * mins[1] + p->normal[2] * maxs[2] < p->dist ) + return true; + break; + case 4: + if( p->normal[0] * maxs[0] + p->normal[1] * maxs[1] + p->normal[2] * mins[2] < p->dist ) + return true; + break; + case 5: + if( p->normal[0] * mins[0] + p->normal[1] * maxs[1] + p->normal[2] * mins[2] < p->dist ) + return true; + break; + case 6: + if( p->normal[0] * maxs[0] + p->normal[1] * mins[1] + p->normal[2] * mins[2] < p->dist ) + return true; + break; + case 7: + if( p->normal[0] * mins[0] + p->normal[1] * mins[1] + p->normal[2] * mins[2] < p->dist ) + return true; + break; + default: + return false; + } + } + + return false; +} + +qboolean GL_FrustumCullSphere( gl_frustum_t *out, const vec3_t center, float radius, int userClipFlags ) +{ + int iClipFlags; + int i, bit; + + if( r_nocull->value ) + return false; + + if( userClipFlags != 0 ) + iClipFlags = userClipFlags; + else iClipFlags = out->clipFlags; + + for( i = FRUSTUM_PLANES, bit = 1; i > 0; i--, bit <<= 1 ) + { + const mplane_t *p = &out->planes[FRUSTUM_PLANES - i]; + + if( !FBitSet( iClipFlags, bit )) + continue; + + if( DotProduct( center, p->normal ) - p->dist <= -radius ) + return true; + } + + return false; +} diff --git a/ref_gu/gu_frustum.h b/ref_gu/gu_frustum.h new file mode 100644 index 000000000..f48793b5e --- /dev/null +++ b/ref_gu/gu_frustum.h @@ -0,0 +1,52 @@ +/* +gl_frustum.cpp - frustum test implementation +Copyright (C) 2016 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#ifndef GL_FRUSTUM_H +#define GL_FRUSTUM_H + +// don't change this order +#define FRUSTUM_LEFT 0 +#define FRUSTUM_RIGHT 1 +#define FRUSTUM_BOTTOM 2 +#define FRUSTUM_TOP 3 +#define FRUSTUM_FAR 4 +#define FRUSTUM_NEAR 5 +#define FRUSTUM_PLANES 6 + +typedef struct gl_frustum_s +{ + mplane_t planes[FRUSTUM_PLANES]; + unsigned int clipFlags; +} gl_frustum_t; + +void GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY ); +void GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yTop, float yBottom, float flZNear, float flZFar ); +void GL_FrustumInitBox( gl_frustum_t *out, const vec3_t org, float radius ); // used for pointlights +void GL_FrustumInitProjFromMatrix( gl_frustum_t *out, const matrix4x4 projection ); +void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ); +void GL_FrustumNormalizePlane( gl_frustum_t *out, int side ); +void GL_FrustumComputeBounds( gl_frustum_t *out, vec3_t mins, vec3_t maxs ); +void GL_FrustumComputeCorners( gl_frustum_t *out, vec3_t bbox[8] ); +void GL_FrustumDrawDebug( gl_frustum_t *out ); + +// cull methods +qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ); +qboolean GL_FrustumCullSphere( gl_frustum_t *out, const vec3_t centre, float radius, int userClipFlags ); + +// plane manipulating +void GL_FrustumEnablePlane( gl_frustum_t *out, int side ); +void GL_FrustumDisablePlane( gl_frustum_t *out, int side ); + +#endif//GL_FRUSTUM_H diff --git a/ref_gu/gu_helper.h b/ref_gu/gu_helper.h new file mode 100644 index 000000000..5730bbd89 --- /dev/null +++ b/ref_gu/gu_helper.h @@ -0,0 +1,67 @@ +/* +gl_helper.h - gu helper +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#ifndef GU_HELPER_H +#define GU_HELPER_H +#ifndef APIENTRY +#define APIENTRY +#endif + +#ifndef APIENTRY_LINKAGE +#define APIENTRY_LINKAGE extern +#endif + +// GL types wrapping +typedef uint GLenum; +typedef byte GLboolean; +typedef uint GLbitfield; +typedef void GLvoid; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef byte GLubyte; +typedef word GLushort; +typedef uint GLuint; +typedef int GLsizei; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef int GLintptrARB; +typedef int GLsizeiptrARB; +typedef char GLcharARB; +typedef uint GLhandleARB; +typedef float GLmatrix[16]; + +// GL_Cull wrapping +#define GL_NONE 0 +#define GL_FRONT GU_CW + 1 +#define GL_BACK GU_CCW + 1 + +// color functions wrapping +#define GUCOLOR4F( r, g, b, a ) GU_COLOR( ( r ), ( g ), ( b ), ( a ) ) +#define GUCOLOR4FV( v ) GU_COLOR( ( v )[0], ( v )[1], ( v )[2], ( v )[3] ) +#define GUCOLOR4UB( r, g, b, a ) GU_RGBA( ( r ), ( g ), ( b ), ( a ) ) +#define GUCOLOR4UBV( v ) GU_RGBA( ( v )[0], ( v )[1], ( v )[2], ( v )[3] ) +#define GUCOLOR3F( r, g, b ) GU_COLOR( ( r ), ( g ), ( b ), 1.0f ) +#define GUCOLOR3FV( v ) GU_COLOR( ( v )[0], ( v )[1], ( v )[2], 1.0f ) +#define GUCOLOR3UB( r, g, b ) GU_RGBA( ( r ), ( g ), ( b ), 255 ) +#define GUCOLOR3UBV( v ) GU_RGBA( ( v )[0], ( v )[1], ( v )[2], 255 ) + +// blend function wrapping +#define GUBLEND1 0xffffffff +#define GUBLEND0 0x00000000 + +#endif // GU_HELPER_H diff --git a/ref_gu/gu_image.c b/ref_gu/gu_image.c new file mode 100644 index 000000000..739a50d2c --- /dev/null +++ b/ref_gu/gu_image.c @@ -0,0 +1,2063 @@ +/* +gu_image.c - texture uploading and processing +Copyright (C) 2010 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include +#include +#include "gu_local.h" +#include "crclib.h" + +#define TEXTURES_HASH_SIZE (MAX_TEXTURES >> 2) + +static gl_texture_t gl_textures[MAX_TEXTURES]; +static gl_texture_t* gl_texturesHashTable[TEXTURES_HASH_SIZE]; +static uint gl_numTextures; +static byte* gl_palette_332 = NULL; + +/* + num_colors = PALETTE_BLOCKS x 16 (16-bit CLUT) + num_colors = PALETTE_BLOCKS x 8 (32-bit CLUT) +*/ +#define PALETTE_FORMAT GU_PSM_4444 +#if (PALETTE_FORMAT == GU_PSM_4444) || (PALETTE_FORMAT == GU_PSM_5551) || (PALETTE_FORMAT == GU_PSM_5650) +#define PALETTE_SIZE 2 * 256 +#define PALETTE_BLOCKS 16 +#elif PALETTE_FORMAT == GU_PSM_8888 +#define PALETTE_SIZE 4 * 256 +#define PALETTE_BLOCKS 32 +#else +#error Current PALETTE_FORMAT not supported! +#endif + +#define TEXTURE_SIZE_MIN 16 + +#define IsLightMap( tex ) ( FBitSet(( tex )->flags, TF_ATLAS_PAGE )) + +/* +================= +R_GetTexture + +acess to array elem +================= +*/ +gl_texture_t *R_GetTexture( GLenum texnum ) +{ + ASSERT( texnum >= 0 && texnum < MAX_TEXTURES ); + return &gl_textures[texnum]; +} + +/* +================= +GL_Bind +================= +*/ +void GL_Bind( GLint tmu, GLenum texnum ) +{ + gl_texture_t *texture; + int i, offset, width, height; + + // missed or invalid texture? + if( texnum <= 0 || texnum >= MAX_TEXTURES ) + { + if( texnum != 0 ) + gEngfuncs.Con_DPrintf( S_ERROR "GL_Bind: invalid texturenum %d\n", texnum ); + texnum = tr.defaultTexture; + } + + if( glState.currentTexture == texnum ) + return; + + texture = &gl_textures[texnum]; + + // Set palette + if( texture->format == GU_PSM_T8 ) + { + sceGuClutMode( PALETTE_FORMAT, 0, 0xff, 0 ); + sceGuClutLoad( PALETTE_BLOCKS, texture->dstPalette ); + } + + // Set texture parameters + sceGuTexMode( texture->format, texture->numMips - 1, 0, FBitSet( texture->flags, TF_IMG_SWIZZLED ) ? GU_TRUE : GU_FALSE ); + if( texture->numMips > 1 ) + { + //sceGuTexFilter( GU_LINEAR_MIPMAP_LINEAR, GU_LINEAR_MIPMAP_LINEAR ); + if( FBitSet( texture->flags, TF_NEAREST ) || gl_texture_nearest->value ) + sceGuTexFilter( GU_NEAREST_MIPMAP_NEAREST, GU_NEAREST ); + else + sceGuTexFilter( GU_LINEAR_MIPMAP_LINEAR, GU_LINEAR ); + + sceGuTexLevelMode( gl_texture_lodfunc->value, gl_texture_lodbias->value ); + sceGuTexSlope( gl_texture_lodslope->value ); + } + else + { + if( FBitSet( texture->flags, TF_NEAREST ) || ( IsLightMap( texture ) && gl_lightmap_nearest->value )) + sceGuTexFilter( GU_NEAREST, GU_NEAREST ); + else + sceGuTexFilter( GU_LINEAR, GU_LINEAR ); + } + + if( FBitSet( texture->flags, TF_CLAMP ) || FBitSet( texture->flags, TF_BORDER )) + sceGuTexWrap( GU_CLAMP, GU_CLAMP ); + else + sceGuTexWrap( GU_REPEAT, GU_REPEAT ); + + // Set base texture + sceGuTexImage( 0, texture->width, texture->height, texture->width, texture->dstTexture ); + + // Set mip textures + if( texture->numMips > 1 ) + { + offset = texture->width * texture->height * texture->bpp; + for ( i = 1; i < texture->numMips; i++ ) + { + width = Q_max( TEXTURE_SIZE_MIN, ( texture->width >> i )); + height = Q_max( TEXTURE_SIZE_MIN, ( texture->height >> i )); + sceGuTexImage( i, width, height, width, &texture->dstTexture[offset] ); + offset += width * height * texture->bpp; + } + } + glState.currentTexture = texnum; +} + +/* +================== +GL_CalcImageSize +================== +*/ +static size_t GL_CalcImageSize( pixformat_t format, int width, int height, int depth ) +{ + size_t size = 0; + + // check the depth error + depth = Q_max( 1, depth ); + + switch( format ) + { + case PF_INDEXED_24: + case PF_INDEXED_32: + case PF_LUMINANCE: + case PF_RGB_332: + size = width * height * depth; + break; + case PF_RGB_5650: + case PF_RGBA_5551: + case PF_RGBA_4444: + size = width * height * depth * 2; + break; + case PF_RGB_24: + case PF_BGR_24: + size = width * height * depth * 3; + break; + case PF_BGRA_32: + case PF_RGBA_32: + size = width * height * depth * 4; + break; + case PF_DXT1: + size = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth; + break; + case PF_DXT3: + case PF_DXT5: + case PF_ATI2: + size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; + break; + } + + return size; +} + +/* +================== +GL_CalcTextureSize +================== +*/ +static size_t GL_CalcTextureSize( int format, int width, int height, byte *outBpp ) +{ + size_t size = 0; + byte bpp = 0; + + switch( format ) + { + case GU_PSM_T4: + case GU_PSM_DXT1: + size = (( width * height ) >> 1 ); + break; + case GU_PSM_T8: + case GU_PSM_DXT3: + case GU_PSM_DXT5: + bpp = 1; + size = width * height; + break; + case GU_PSM_T16: + case GU_PSM_4444: + case GU_PSM_5551: + case GU_PSM_5650: + bpp = 2; + size = width * height * 2; + break; + case GU_PSM_T32: + case GU_PSM_8888: + bpp = 4; + size = width * height * 4; + break; + default: + gEngfuncs.Host_Error( "GL_CalcTextureSize: bad texture internal format (%u)\n", format ); + break; + } + + if(outBpp != NULL) *outBpp = bpp; + + return size; +} + +static int GL_CalcMipmapCount( gl_texture_t *tex, qboolean haveBuffer, size_t *mipSize ) +{ + int width, height; + int mipCount; + + Assert( tex != NULL ); + + if( !haveBuffer ) + return 1; + +#if 0 + // brush model only + if( !FBitSet( tex->flags, TF_ALLOW_EMBOSS )) + return 1; +#endif + + // generate mip-levels by user request + if( FBitSet( tex->flags, TF_NOMIPMAP )) + return 1; + + // 8 levels - 7 + 1 base + for( mipCount = 1; mipCount < 8; mipCount++ ) + { + width = Q_max( TEXTURE_SIZE_MIN, ( tex->width >> mipCount )); + height = Q_max( TEXTURE_SIZE_MIN, ( tex->height >> mipCount )); + + if( width == TEXTURE_SIZE_MIN && height == TEXTURE_SIZE_MIN ) + break; + + // calc without base size + if( mipSize != NULL ) + *mipSize += GL_CalcTextureSize( tex->format, width, height, NULL ); + } + return mipCount; +} + +/* +================ +GL_SetTextureDimensions +================ +*/ +static void GL_SetTextureDimensions( gl_texture_t *tex, int width, int height ) +{ + int maxTextureSize = glConfig.max_texture_size; + int step = (int)gl_round_down->value; + int scaled_width, scaled_height; + + Assert( tex != NULL ); + + // store original sizes + tex->srcWidth = width; + tex->srcHeight = height; + + for( scaled_width = 1; scaled_width < width; scaled_width <<= 1 ); + + if( step > 0 && width < scaled_width && ( step == 1 || ( scaled_width - width ) > ( scaled_width >> step ))) + scaled_width >>= 1; + + for( scaled_height = 1; scaled_height < height; scaled_height <<= 1 ); + + if( step > 0 && height < scaled_height && ( step == 1 || ( scaled_height - height ) > ( scaled_height >> step ))) + scaled_height >>= 1; + + width = scaled_width; + height = scaled_height; + + if( width > maxTextureSize || height > maxTextureSize ) + { + while( width > maxTextureSize || height > maxTextureSize ) + { + width >>= 1; + height >>= 1; + } + } + + // set the texture dimensions + tex->width = Q_max( TEXTURE_SIZE_MIN, width ); + tex->height = Q_max( TEXTURE_SIZE_MIN, height ); +} + +/* +=============== +GL_SetTextureFormat +=============== +*/ +static void GL_SetTextureFormat( gl_texture_t *tex, pixformat_t format, int channelMask ) +{ + qboolean haveColor = ( channelMask & IMAGE_HAS_COLOR ); + qboolean haveAlpha = ( channelMask & IMAGE_HAS_ALPHA ); + + Assert( tex != NULL ); + + switch( format ) + { + case PF_DXT1: // never use DXT1 with 1-bit alpha + tex->format = GU_PSM_DXT1; + break; + case PF_DXT3: + tex->format = GU_PSM_DXT3; + break; + case PF_DXT5: + tex->format = GU_PSM_DXT5; + break; + case PF_INDEXED_24: + case PF_INDEXED_32: + case PF_RGB_332: + tex->format = GU_PSM_T8; + break; + case PF_RGB_5650: + tex->format = GU_PSM_5650; + break; + case PF_RGBA_5551: + tex->format = GU_PSM_5551; + break; + case PF_RGBA_4444: + tex->format = GU_PSM_4444; + break; + case PF_RGBA_32: + case PF_BGRA_32: + case PF_RGB_24: + case PF_BGR_24: + if ( IsLightMap( tex )) + tex->format = GU_PSM_8888; + else if( haveAlpha ) + tex->format = GU_PSM_4444; + else + tex->format = GU_PSM_T8; // rgb332 + break; + default: + gEngfuncs.Host_Error( "GL_SetTextureFormat: unknown format %i\n", format ); + break; + } + /*if( FBitSet( tex->flags, TF_DEPTHMAP )) + { + if( FBitSet( tex->flags, TF_ARB_16BIT )) + tex->format = GL_DEPTH_COMPONENT16; + else if( FBitSet( tex->flags, TF_ARB_FLOAT )) + tex->format = GL_DEPTH_COMPONENT32F; + else tex->format = GL_DEPTH_COMPONENT24; + }*/ +} + +/* +================= +GL_ResampleTexture32 + +Assume input buffer is RGBA +================= +*/ +void GL_ResampleTexture32( const byte *source, int inWidth, int inHeight, byte *dest, int outWidth, int outHeight, qboolean isNormalMap ) +{ + uint frac, fracStep; + uint *in = (uint *)source; + uint p1[0x1000], p2[0x1000]; + byte *pix1, *pix2, *pix3, *pix4; + uint *out, *inRow1, *inRow2; + vec3_t normal; + int i, x, y; + + if( !source ) + return; + + fracStep = inWidth * 0x10000 / outWidth; + out = (uint *)dest; + + frac = fracStep >> 2; + for( i = 0; i < outWidth; i++ ) + { + p1[i] = 4 * (frac >> 16); + frac += fracStep; + } + + frac = (fracStep >> 2) * 3; + for( i = 0; i < outWidth; i++ ) + { + p2[i] = 4 * (frac >> 16); + frac += fracStep; + } + + if( isNormalMap ) + { + for( y = 0; y < outHeight; y++, out += outWidth ) + { + inRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight / outHeight); + inRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight / outHeight); + + for( x = 0; x < outWidth; x++ ) + { + pix1 = (byte *)inRow1 + p1[x]; + pix2 = (byte *)inRow1 + p2[x]; + pix3 = (byte *)inRow2 + p1[x]; + pix4 = (byte *)inRow2 + p2[x]; + + normal[0] = MAKE_SIGNED( pix1[0] ) + MAKE_SIGNED( pix2[0] ) + MAKE_SIGNED( pix3[0] ) + MAKE_SIGNED( pix4[0] ); + normal[1] = MAKE_SIGNED( pix1[1] ) + MAKE_SIGNED( pix2[1] ) + MAKE_SIGNED( pix3[1] ) + MAKE_SIGNED( pix4[1] ); + normal[2] = MAKE_SIGNED( pix1[2] ) + MAKE_SIGNED( pix2[2] ) + MAKE_SIGNED( pix3[2] ) + MAKE_SIGNED( pix4[2] ); + + if( !VectorNormalizeLength( normal )) + VectorSet( normal, 0.5f, 0.5f, 1.0f ); + + ((byte *)(out+x))[0] = 128 + (byte)(127.0f * normal[0]); + ((byte *)(out+x))[1] = 128 + (byte)(127.0f * normal[1]); + ((byte *)(out+x))[2] = 128 + (byte)(127.0f * normal[2]); + ((byte *)(out+x))[3] = 255; + } + } + } + else + { + for( y = 0; y < outHeight; y++, out += outWidth ) + { + inRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight / outHeight); + inRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight / outHeight); + + for( x = 0; x < outWidth; x++ ) + { + pix1 = (byte *)inRow1 + p1[x]; + pix2 = (byte *)inRow1 + p2[x]; + pix3 = (byte *)inRow2 + p1[x]; + pix4 = (byte *)inRow2 + p2[x]; + + ((byte *)(out+x))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2; + ((byte *)(out+x))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2; + ((byte *)(out+x))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2; + ((byte *)(out+x))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3]) >> 2; + } + } + } +} + +/* +================= +GL_ResampleTexture8 + +For indexed textures +================= +*/ +void GL_ResampleTexture8(const byte *source, int inWidth, int inHeight, byte *dest, int outWidth, int outHeight) +{ + uint fracstep; + byte *out; + const byte *inrow; + uint frac; + int i, j; + + if( !source ) + return; + + fracstep = inWidth * 0x10000 / outWidth; + out = dest; + + for (i = 0; i < outHeight ; ++i, out += outWidth) + { + inrow = source + inWidth * (i * inHeight / outHeight); + frac = fracstep >> 1; + for (j = 0; j < outWidth; ++j, frac += fracstep) + { + out[j] = inrow[frac >> 16]; + } + } +} +/* +================= +GL_BoxFilter3x3 + +box filter 3x3 +================= +*/ +void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y ) +{ + int r = 0, g = 0, b = 0, a = 0; + int count = 0, acount = 0; + int i, j, u, v; + const byte *pixel; + + for( i = 0; i < 3; i++ ) + { + u = ( i - 1 ) + x; + + for( j = 0; j < 3; j++ ) + { + v = ( j - 1 ) + y; + + if( u >= 0 && u < w && v >= 0 && v < h ) + { + pixel = &in[( u + v * w ) * 4]; + + if( pixel[3] != 0 ) + { + r += pixel[0]; + g += pixel[1]; + b += pixel[2]; + a += pixel[3]; + acount++; + } + } + } + } + + if( acount == 0 ) + acount = 1; + + out[0] = r / acount; + out[1] = g / acount; + out[2] = b / acount; +// out[3] = (int)( SimpleSpline( ( a / 12.0f ) / 255.0f ) * 255 ); +} + +/* +================= +GL_ApplyFilter + +Apply box-filter to 1-bit alpha +================= +*/ +byte *GL_ApplyFilter( const byte *source, int width, int height ) +{ + byte *in = (byte *)source; + byte *out = (byte *)source; + int i; + + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) ) + return in; + + for( i = 0; source && i < width * height; i++, in += 4 ) + { + if( in[0] == 0 && in[1] == 0 && in[2] == 0 && in[3] == 0 ) + GL_BoxFilter3x3( in, source, width, height, i % width, i / width ); + } + + return out; +} + +/* +================= +GL_BuildMipMap + +Operates in place, quartering the size of the texture +================= +*/ +static void GL_BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags ) +{ + byte *out = in; + int instride = ALIGN( srcWidth * 4, 1 ); + int mipWidth, mipHeight, outpadding; + int row, x, y, z; + vec3_t normal; + + if( !in ) return; + + mipWidth = Q_max( 1, ( srcWidth >> 1 )); + mipHeight = Q_max( 1, ( srcHeight >> 1 )); + outpadding = ALIGN( mipWidth * 4, 1 ) - mipWidth * 4; + row = srcWidth << 2; + + if( FBitSet( flags, TF_ALPHACONTRAST )) + { + memset( in, mipWidth, mipWidth * mipHeight * 4 ); + return; + } + + // move through all layers + for( z = 0; z < srcDepth; z++ ) + { + if( FBitSet( flags, TF_NORMALMAP )) + { + for( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding ) + { + byte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in; + for( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 ) + { + if((( x << 1 ) + 1 ) < srcWidth ) + { + normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( in[row+4] ) + + MAKE_SIGNED( next[row+0] ) + MAKE_SIGNED( next[row+4] ); + normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( in[row+5] ) + + MAKE_SIGNED( next[row+1] ) + MAKE_SIGNED( next[row+5] ); + normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( in[row+6] ) + + MAKE_SIGNED( next[row+2] ) + MAKE_SIGNED( next[row+6] ); + } + else + { + normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( next[row+0] ); + normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( next[row+1] ); + normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( next[row+2] ); + } + + if( !VectorNormalizeLength( normal )) + VectorSet( normal, 0.5f, 0.5f, 1.0f ); + + out[0] = 128 + (byte)(127.0f * normal[0]); + out[1] = 128 + (byte)(127.0f * normal[1]); + out[2] = 128 + (byte)(127.0f * normal[2]); + out[3] = 255; + } + } + } + else + { + for( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding ) + { + byte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in; + for( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 ) + { + if((( x << 1 ) + 1 ) < srcWidth ) + { + out[0] = (in[row+0] + in[row+4] + next[row+0] + next[row+4]) >> 2; + out[1] = (in[row+1] + in[row+5] + next[row+1] + next[row+5]) >> 2; + out[2] = (in[row+2] + in[row+6] + next[row+2] + next[row+6]) >> 2; + out[3] = (in[row+3] + in[row+7] + next[row+3] + next[row+7]) >> 2; + } + else + { + out[0] = (in[row+0] + next[row+0]) >> 1; + out[1] = (in[row+1] + next[row+1]) >> 1; + out[2] = (in[row+2] + next[row+2]) >> 1; + out[3] = (in[row+3] + next[row+3]) >> 1; + } + } + } + } + } +} + +/* +=============== +GL_PixelConverter + +32 <-> 16 bit Image converter + +Macro for inFormat/outFormat: +PC_HWF - PSP Hardware format mask +PC_SWF - Xash3d Software format mask +=============== +*/ +void GL_PixelConverter( byte *dst, const byte *src, int size, int inFormat, int outFormat ) +{ + int i; + byte color[4]; + + color[0] = color[1] = color[2] = color[3] = 0xff; + + for( i = 0; i < size; i++ ) + { + // unpack + switch( inFormat ) + { + case PC_HWF( GU_PSM_5650 ): + color[0] = ( *src & 0x1f ) << 3; + color[1] = ( *src & 0xe0 ) >> 3; src++; + color[1] |= ( *src & 0x07 ) << 5; + color[2] = ( *src & 0xf8 ); src++; + color[3] = 0xff; + break; + case PC_HWF( GU_PSM_5551 ): + color[0] = ( *src & 0x1f ) << 3; + color[1] = ( *src & 0xe0 ) >> 2; src++; + color[1] |= ( *src & 0x03 ) << 6; + color[2] = ( *src & 0x7c ) << 1; + color[3] = ( *src & 0x80 ) ? 0xff : 0x00; src++; + break; + case PC_HWF( GU_PSM_4444 ): + color[0] = ( *src & 0x0f ) << 4; + color[1] = ( *src & 0xf0 ); src++; + color[2] = ( *src & 0x0f ) << 4; + color[3] = ( *src & 0xf0 ); src++; + break; + case PC_SWF( PF_INDEXED_24 ): // palette !!! + case PC_SWF( PF_RGB_24 ): + color[0] = *src; src++; + color[1] = *src; src++; + color[2] = *src; src++; + color[3] = 0xff; + break; + case PC_SWF( PF_BGR_24 ): + color[2] = *src; src++; + color[1] = *src; src++; + color[0] = *src; src++; + color[3] = 0xff; + src += 3; + break; + case PC_SWF( PF_INDEXED_32 ): // palette !!! + case PC_SWF( PF_RGBA_32 ): + case PC_HWF( GU_PSM_8888 ): + color[0] = *src; src++; + color[1] = *src; src++; + color[2] = *src; src++; + color[3] = *src; src++; + break; + case PC_SWF( PF_BGRA_32 ): + color[2] = *src; src++; + color[1] = *src; src++; + color[0] = *src; src++; + color[3] = *src; src++; + break; + default: + gEngfuncs.Host_Error( "GL_PixelConverter: unknown input format\n"); + break; + } + + // pack + switch( outFormat ) + { + case PC_HWF( GU_PSM_T8 ): // 332, indexing + *dst = ( color[0] >> 5 ) & 0x07; + *dst |= ( color[1] >> 2 ) & 0x38; + *dst |= ( color[2] ) & 0xc0; dst++; + break; + case PC_HWF( GU_PSM_5650 ): + *dst = ( color[0] >> 3 ) & 0x1f; + *dst |= ( color[1] << 3 ) & 0xe0; dst++; + *dst = ( color[1] >> 5 ) & 0x07; + *dst |= ( color[2] ) & 0xf8; dst++; + break; + case PC_HWF( GU_PSM_5551 ): + *dst = ( color[0] >> 3 ); + *dst |= ( color[1] << 2 ) & 0xe0; dst++; + *dst = ( color[1] >> 6 ) & 0x03; + *dst |= ( color[2] >> 1 ) & 0x7c; + *dst |= ( color[3] ) & 0x80; dst++; + break; + case PC_HWF( GU_PSM_4444 ): + *dst = ( color[0] >> 4 ) & 0x0f; + *dst |= ( color[1] ) & 0xf0; dst++; + *dst = ( color[2] >> 4 ) & 0x0f; + *dst |= ( color[3] ) & 0xf0; dst++; + break; + case PC_SWF( PF_INDEXED_24 ): // palette !!! + case PC_SWF( PF_RGB_24 ): + *dst = color[0]; dst++; + *dst = color[1]; dst++; + *dst = color[2]; dst++; + break; + case PC_SWF( PF_BGR_24 ): + *dst = color[2]; dst++; + *dst = color[1]; dst++; + *dst = color[0]; dst++; + break; + case PC_SWF( PF_INDEXED_32 ): // palette !!! + case PC_SWF( PF_RGBA_32 ): + case PC_HWF( GU_PSM_8888 ): + *dst = color[0]; dst++; + *dst = color[1]; dst++; + *dst = color[2]; dst++; + *dst = color[3]; dst++; + break; + case PC_SWF( PF_BGRA_32 ): + *dst = color[3]; dst++; + *dst = color[2]; dst++; + *dst = color[1]; dst++; + *dst = color[0]; dst++; + break; + default: + gEngfuncs.Host_Error( "GL_PixelConverter: unknown output format\n"); + break; + } + } +} + +/* +=============== +GL_TextureSwizzle + +fast swizzling +=============== +*/ +static void GL_TextureSwizzle( byte *dst, const byte *src, uint width, uint height ) +{ + uint blockx, blocky; + uint j; + uint width_blocks = ( width / 16 ); + uint height_blocks = ( height / 8 ); + uint src_pitch = ( width - 16 ) / 4; + uint src_row = width * 8; + const byte* ysrc = src; + uint* dst32 = ( uint* )dst; + + for ( blocky = 0; blocky < height_blocks; ++blocky ) + { + const byte* xsrc = ysrc; + for ( blockx = 0; blockx < width_blocks; ++blockx ) + { + const int* src32 = ( uint* )xsrc; + for ( j = 0; j < 8; ++j ) + { + *( dst32++ ) = *( src32++ ); + *( dst32++ ) = *( src32++ ); + *( dst32++ ) = *( src32++ ); + *( dst32++ ) = *( src32++ ); + src32 += src_pitch; + } + xsrc += 16; + } + ysrc += src_row; + } +} + +/* +=============== +GL_UploadTexture + +upload texture into memory +=============== +*/ +static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic ) +{ + size_t texSize; + uint width, height; + uint i; + uint offset, mipOffset; + qboolean normalMap; + qboolean swizzle; + byte *tempBuff; + + // dedicated server + if( !glw_state.initialized ) + return true; + + Assert( pic != NULL ); + Assert( tex != NULL ); + + GL_SetTextureDimensions( tex, pic->width, pic->height ); + GL_SetTextureFormat( tex, pic->type, pic->flags ); + + tex->fogParams[0] = pic->fogParams[0]; + tex->fogParams[1] = pic->fogParams[1]; + tex->fogParams[2] = pic->fogParams[2]; + tex->fogParams[3] = pic->fogParams[3]; + + if(( pic->width * pic->height ) & 3 ) + { + // will be resampled, just tell me for debug targets + gEngfuncs.Con_Reportf( "GL_UploadTexture: %s s&3 [%d x %d]\n", tex->name, pic->width, pic->height ); + } + + if( !pic->buffer ) + return true; + + // Prepare sizes + offset = GL_CalcImageSize( pic->type, pic->width, pic->height, /* pic->depth */ 1 ); + texSize = GL_CalcTextureSize( tex->format, tex->width, tex->height, &tex->bpp ); + normalMap = FBitSet( tex->flags, TF_NORMALMAP ) ? true : false; + tex->numMips = ImageIND( pic->type ) ? GL_CalcMipmapCount( tex, ( pic->buffer != NULL ), &texSize ) : 1; + swizzle = IsLightMap( tex ) ? false : true; + mipOffset = 0; + + // Check allocation size + if( tex->dstTexture && ( tex->size != texSize ) ) + { + if( FBitSet( tex->flags, TF_IMG_INVRAM ) ) + vfree( tex->dstTexture ); + else + free( tex->dstTexture ); + + tex->dstTexture = NULL; + ClearBits( tex->flags, TF_IMG_INVRAM ); + } + + // already allocated? + if( !tex->dstTexture ) + { + tex->dstTexture = ( byte* )valloc( texSize ); + if( !tex->dstTexture ) + { + tex->dstTexture = ( byte* )memalign( 16, texSize ); + if ( !tex->dstTexture ) + gEngfuncs.Host_Error( "GL_UploadTexture: %s out of memory for texture ( %lu )\n", tex->name, texSize ); + } + else SetBits( tex->flags, TF_IMG_INVRAM ); + } + + if( ImageDXT( pic->type ) ) + { + memcpy( tex->dstTexture, pic->buffer, texSize ); + } + else if( ImageIND( pic->type ) || pic->type == PF_RGB_332 ) + { + if( pic->type != PF_RGB_332 ) + { + if( !tex->dstPalette ) + { + tex->dstPalette = ( byte* )valloc( PALETTE_SIZE ); + if( !tex->dstPalette ) + { + tex->dstPalette = ( byte* )memalign( 16, PALETTE_SIZE ); + if ( !tex->dstPalette ) + gEngfuncs.Host_Error( "GL_UploadTexture: %s out of memory for palette ( %lu )\n", tex->name, PALETTE_SIZE ); + } + } + + // Load palette + GL_PixelConverter( tex->dstPalette, pic->palette, 256, PC_SWF( pic->type ), PC_HWF( PALETTE_FORMAT )); + sceKernelDcacheWritebackRange( tex->dstPalette, PALETTE_SIZE ); + } + else tex->dstPalette = gl_palette_332; + + // Volatile memory for temporary buffer + if( swizzle ) + { + tempBuff = gEngfuncs.P5Ram_Alloc( texSize, 0 ); + if( !tempBuff ) gEngfuncs.Host_Error( "GL_UploadTexture: temporary memory error\n" ); + } + + // Load base + mip texture + for( i = 0; i < tex->numMips; i++ ) + { + width = Q_max( TEXTURE_SIZE_MIN, ( tex->width >> i ) ); + height = Q_max( TEXTURE_SIZE_MIN, ( tex->height >> i ) ); + + if(( pic->width != width ) || ( pic->height != height )) + GL_ResampleTexture8( pic->buffer, pic->width, pic->height, swizzle ? tempBuff : ( tex->dstTexture + mipOffset ), width, height ); + else memcpy( swizzle ? tempBuff : ( tex->dstTexture + mipOffset ), pic->buffer, offset ); + + if(swizzle) GL_TextureSwizzle( tex->dstTexture + mipOffset, tempBuff, width, height ); + mipOffset += GL_CalcTextureSize( tex->format, width, height, NULL ); + } + + if( swizzle ) + { + gEngfuncs.P5Ram_Free( tempBuff ); + SetBits( tex->flags, TF_IMG_SWIZZLED ); + } + } + else if( pic->type == PF_RGB_5650 || pic->type == PF_RGBA_5551 || pic->type == PF_RGBA_4444 ) + { + if( swizzle ) + { + tempBuff = gEngfuncs.P5Ram_Alloc( texSize, 0 ); + if( !tempBuff ) gEngfuncs.Host_Error( "GL_UploadTexture: temporary memory error\n" ); + } + + if(( pic->width != tex->width ) || ( pic->height != tex->height )) + { + for( i = 0; i < tex->height; i++ ) + { + memcpy( swizzle ? ( tempBuff + tex->width * tex->bpp * i ) : ( tex->dstTexture + tex->width * tex->bpp * i ), + ( pic->buffer + pic->width * tex->bpp * i ), pic->width * tex->bpp ); + } + } + else memcpy( swizzle ? tempBuff : tex->dstTexture, pic->buffer, offset ); + + if( swizzle ) + { + GL_TextureSwizzle( tex->dstTexture, tempBuff, tex->width * tex->bpp, tex->height ); + gEngfuncs.P5Ram_Free( tempBuff ); + SetBits( tex->flags, TF_IMG_SWIZZLED ); + } + } + else // RGBA32 + { + if(( pic->width != tex->width ) || ( pic->height != tex->height )) + offset = tex->width * tex->height * 4; // src size + + tempBuff = gEngfuncs.P5Ram_Alloc( swizzle ? ( texSize + offset ) : texSize, 0 ); + if( !tempBuff ) gEngfuncs.Host_Error( "GL_UploadTexture: temporary memory error\n" ); + + if(( pic->width != tex->width ) || ( pic->height != tex->height )) + GL_ResampleTexture32( pic->buffer, pic->width, pic->height, tempBuff, tex->width, tex->height, normalMap ); + else memcpy( tempBuff, pic->buffer, offset ); +/* + if( !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) + tempBuff = GL_ApplyFilter( tempBuff, tex->width, tex->height ); +*/ + if( tex->format == GU_PSM_8888 ) + { + if ( swizzle ) + { + GL_TextureSwizzle( tex->dstTexture, tempBuff, tex->width * tex->bpp, tex->height ); + SetBits( tex->flags, TF_IMG_SWIZZLED ); + } + else memcpy( tex->dstTexture, tempBuff, texSize ); + } + else + { + if( tex->format == GU_PSM_T8 ) tex->dstPalette = gl_palette_332; + + GL_PixelConverter( swizzle ? (tempBuff + offset) : tex->dstTexture, tempBuff, tex->width * tex->height, PC_SWF( pic->type ), PC_HWF( tex->format ) ); + if( swizzle ) + { + GL_TextureSwizzle( tex->dstTexture, tempBuff + offset, tex->width * tex->bpp, tex->height ); + SetBits( tex->flags, TF_IMG_SWIZZLED ); + } + } + gEngfuncs.P5Ram_Free( tempBuff ); + } + + sceKernelDcacheWritebackRange( tex->dstTexture, texSize ); + tex->size = texSize; + + SetBits( tex->flags, TF_IMG_UPLOADED ); // done + + return true; +} + +/* +=============== +GL_UpdateTexture + +=============== +*/ +qboolean GL_UpdateTexture( int texnum, int xoff, int yoff, int width, int height, const void *buffer ) +{ + gl_texture_t *tex; + size_t texsize; + byte *dst, *src; + + // missed or invalid texture? + if(( texnum <= 0 ) || ( texnum >= MAX_TEXTURES )) + { + if( texnum != 0 ) + { + gEngfuncs.Con_DPrintf( S_ERROR "GL_UpdateTexture: invalid texture num %d\n", texnum ); + return false; + } + } + tex = &gl_textures[texnum]; + + if(( tex->width < width + xoff ) || ( tex->height < height + yoff )) + { + gEngfuncs.Con_DPrintf( S_ERROR "GL_UpdateTexture: %s invalid update area size XY[%d x %d] WH[%d x %d]\n", tex->name, width, height, xoff, yoff ); + return false; + } + + if( !FBitSet( tex->flags, TF_IMG_UPLOADED )) + { + tex->size = GL_CalcTextureSize( tex->format, tex->width, tex->height, &tex->bpp ); + tex->numMips = 1; + tex->dstPalette = ( tex->format == GU_PSM_T8 ) ? gl_palette_332 : NULL; // default + + tex->dstTexture = ( byte* )valloc( tex->size ); + if( !tex->dstTexture ) + { + tex->dstTexture = ( byte* )memalign( 16, tex->size ); + if ( !tex->dstTexture ) + gEngfuncs.Host_Error( "GL_UpdateTexture: out of memory! ( texture: %i %s )\n", tex->size, tex->name ); + } + else SetBits( tex->flags, TF_IMG_INVRAM ); + + memset( tex->dstTexture, 0x00, tex->size ); + SetBits( tex->flags, TF_IMG_UPLOADED ); // done + } + + src = ( byte* )buffer; + dst = tex->dstTexture + ( yoff * tex->width + xoff ) * tex->bpp; + while( height-- ) + { + memcpy( dst, src, width * tex->bpp ); + dst += tex->width * tex->bpp; + src += width * tex->bpp; + } + + sceKernelDcacheWritebackRange( tex->dstTexture, tex->size ); + return true; +} + +/* +=============== +GL_ProcessImage + +do specified actions on pixels +=============== +*/ +static void GL_ProcessImage( gl_texture_t *tex, rgbdata_t *pic ) +{ + float emboss_scale = 0.0f; + uint img_flags = 0; + + // force upload texture as RGB or RGBA (detail textures requires this) + if( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR; + if( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA; +#if 0 + tex->encode = pic->encode; // share encode method +#endif + if( ImageDXT( pic->type )) + { + if( !pic->numMips ) + tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request + + // clear all the unsupported flags + tex->flags &= ~TF_KEEP_SOURCE; + } + else + { + // copy flag about luma pixels + if( pic->flags & IMAGE_HAS_LUMA ) + tex->flags |= TF_HAS_LUMA; + + if( pic->flags & IMAGE_QUAKEPAL ) + tex->flags |= TF_QUAKEPAL; + + // create luma texture from quake texture + if( tex->flags & TF_MAKELUMA ) + { + img_flags |= IMAGE_MAKE_LUMA; + tex->flags &= ~TF_MAKELUMA; + } + + if( tex->flags & TF_ALLOW_EMBOSS ) + { + img_flags |= IMAGE_EMBOSS; + //tex->flags &= ~TF_ALLOW_EMBOSS; + } + + if( !FBitSet( tex->flags, TF_IMG_UPLOADED ) && FBitSet( tex->flags, TF_KEEP_SOURCE )) + tex->original = gEngfuncs.FS_CopyImage( pic ); // because current pic will be expanded to rgba +#if 0 // forced indexed textures + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + img_flags |= IMAGE_FORCE_RGBA; +#endif + // dedicated server doesn't register this variable + if( gl_emboss_scale != NULL ) + emboss_scale = gl_emboss_scale->value; + + // processing image before uploading (force to rgba, make luma etc) + if( pic->buffer ) gEngfuncs.Image_Process( &pic, 0, 0, img_flags, emboss_scale ); + + if( FBitSet( tex->flags, TF_LUMINANCE )) + ClearBits( pic->flags, IMAGE_HAS_COLOR ); + } +} + +/* +================ +GL_CheckTexName +================ +*/ +qboolean GL_CheckTexName( const char *name ) +{ + if( !COM_CheckString( name )) + return false; + + // because multi-layered textures can exceed name string + if( Q_strlen( name ) >= sizeof( gl_textures->name )) + { + gEngfuncs.Con_Printf( S_ERROR "LoadTexture: too long name %s (%d)\n", name, Q_strlen( name )); + return false; + } + + return true; +} + +/* +================ +GL_TextureForName +================ +*/ +static gl_texture_t *GL_TextureForName( const char *name ) +{ + gl_texture_t *tex; + uint hash; + + // find the texture in array + hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); + + for( tex = gl_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash ) + { + if( !Q_stricmp( tex->name, name )) + return tex; + } + + return NULL; +} + +/* +================ +GL_AllocTexture +================ +*/ +static gl_texture_t *GL_AllocTexture( const char *name, texFlags_t flags ) +{ + gl_texture_t *tex; + uint i; + + // find a free texture_t slot + for( i = 0, tex = gl_textures; i < gl_numTextures; i++, tex++ ) + if( !tex->name[0] ) break; + + if( i == gl_numTextures ) + { + if( gl_numTextures == MAX_TEXTURES ) + gEngfuncs.Host_Error( "GL_AllocTexture: MAX_TEXTURES limit exceeds\n" ); + gl_numTextures++; + } + + tex = &gl_textures[i]; + + // copy initial params + Q_strncpy( tex->name, name, sizeof( tex->name )); +#if 0 + if( FBitSet( flags, TF_SKYSIDE )) + tex->texnum = tr.skyboxbasenum++; + else tex->texnum = i; // texnum is used for fast acess into gl_textures array too +#endif + tex->flags = flags; + + // add to hash table + tex->hashValue = COM_HashKey( name, TEXTURES_HASH_SIZE ); + tex->nextHash = gl_texturesHashTable[tex->hashValue]; + gl_texturesHashTable[tex->hashValue] = tex; + + return tex; +} + +/* +================ +GL_DeleteTexture +================ +*/ +static void GL_DeleteTexture( gl_texture_t *tex ) +{ + gl_texture_t **prev; + gl_texture_t *cur; + + ASSERT( tex != NULL ); + + // already freed? + if( !tex->dstTexture && !tex->name[0] ) return; + + // debug + if( !tex->name[0] ) + { + gEngfuncs.Con_Printf( S_ERROR "GL_DeleteTexture: trying to free unnamed texture\n"); + return; + } + + // remove from hash table + prev = &gl_texturesHashTable[tex->hashValue]; + while( 1 ) + { + cur = *prev; + if( !cur ) break; + + if( cur == tex ) + { + *prev = cur->nextHash; + break; + } + prev = &cur->nextHash; + } + + // release source + if( tex->original ) + gEngfuncs.FS_FreeImage( tex->original ); + + if( tex->dstPalette && tex->dstPalette != gl_palette_332 ) + { + if( vchkptr( tex->dstPalette )) + vfree( tex->dstPalette ); + else free( tex->dstPalette ); + } + + if( tex->dstTexture ) + { + if( FBitSet( tex->flags, TF_IMG_INVRAM ) ) + vfree( tex->dstTexture ); + else + free( tex->dstTexture ); + } + memset( tex, 0, sizeof( *tex )); +} + +/* +================ +GL_UpdateTexSize + +recalc image room +================ +*/ +void GL_UpdateTexSize( int texnum, int width, int height, int depth ) +{ +#if 0 + int i, j, texsize; + int numSides; + gl_texture_t *tex; + + if( texnum <= 0 || texnum >= MAX_TEXTURES ) + return; + + tex = &gl_textures[texnum]; + numSides = FBitSet( tex->flags, TF_CUBEMAP ) ? 6 : 1; + GL_SetTextureDimensions( tex, width, height, depth ); + tex->size = 0; // recompute now + + for( i = 0; i < numSides; i++ ) + { + for( j = 0; j < Q_max( 1, tex->numMips ); j++ ) + { + width = Q_max( 1, ( tex->width >> j )); + height = Q_max( 1, ( tex->height >> j )); + texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); + tex->size += texsize; + } + } +#endif +} + +/* +================ +GL_LoadTexture +================ +*/ +int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ) +{ + gl_texture_t *tex; + rgbdata_t *pic; + uint picFlags = 0; + + if( !GL_CheckTexName( name )) + return 0; + + // see if already loaded + if(( tex = GL_TextureForName( name ))) + return (tex - gl_textures); + + if( FBitSet( flags, TF_NOFLIP_TGA )) + SetBits( picFlags, IL_DONTFLIP_TGA ); +#if 1 + SetBits( picFlags, IL_KEEP_8BIT ); +#else + if( FBitSet( flags, TF_KEEP_SOURCE ) && !FBitSet( flags, TF_EXPAND_SOURCE )) + SetBits( picFlags, IL_KEEP_8BIT ); +#endif + // set some image flags + gEngfuncs.Image_SetForceFlags( picFlags ); + + pic = gEngfuncs.FS_LoadImage( name, buf, size ); + if( !pic ) return 0; // couldn't loading image + + // allocate the new one + tex = GL_AllocTexture( name, flags ); + GL_ProcessImage( tex, pic ); + + if( !GL_UploadTexture( tex, pic )) + { + memset( tex, 0, sizeof( gl_texture_t )); + gEngfuncs.FS_FreeImage( pic ); // release source texture + return 0; + } + + gEngfuncs.FS_FreeImage( pic ); // release source texture + + // NOTE: always return texnum as index in array or engine will stop work !!! + return tex - gl_textures; +} + +/* +================ +GL_LoadTextureArray +================ +*/ +int GL_LoadTextureArray( const char **names, int flags ) +{ +#if 1 + return 0; +#else + rgbdata_t *pic, *src; + char basename[256]; + uint numLayers = 0; + uint picFlags = 0; + char name[256]; + gl_texture_t *tex; + uint i, j; + + if( !names || !names[0] || !glw_state.initialized ) + return 0; + + // count layers (g-cont. this is pontentially unsafe loop) + for( i = 0; i < glConfig.max_2d_texture_layers && ( *names[i] != '\0' ); i++ ) + numLayers++; + name[0] = '\0'; + + if( numLayers <= 0 ) return 0; + + // create complexname from layer names + for( i = 0; i < numLayers; i++ ) + { + COM_FileBase( names[i], basename ); + Q_strncat( name, va( "%s", basename ), sizeof( name )); + if( i != ( numLayers - 1 )) Q_strncat( name, "|", sizeof( name )); + } + + Q_strncat( name, va( "[%i]", numLayers ), sizeof( name )); + + if( !GL_CheckTexName( name )) + return 0; + + // see if already loaded + if(( tex = GL_TextureForName( name ))) + return (tex - gl_textures); + + // load all the images and pack it into single image + for( i = 0, pic = NULL; i < numLayers; i++ ) + { + size_t srcsize, dstsize, mipsize; + + src = gEngfuncs.FS_LoadImage( names[i], NULL, 0 ); + if( !src ) break; // coldn't find layer + + if( pic ) + { + // mixed mode: DXT + RGB + if( pic->type != src->type ) + { + gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: mismatch image format for %s and %s\n", names[0], names[i] ); + break; + } + + // different mipcount + if( pic->numMips != src->numMips ) + { + gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: mismatch mip count for %s and %s\n", names[0], names[i] ); + break; + } + + if( pic->encode != src->encode ) + { + gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: mismatch custom encoding for %s and %s\n", names[0], names[i] ); + break; + } + + // but allow to rescale raw images + if( ImageRAW( pic->type ) && ImageRAW( src->type ) && ( pic->width != src->width || pic->height != src->height )) + gEngfuncs.Image_Process( &src, pic->width, pic->height, IMAGE_RESAMPLE, 0.0f ); + + if( pic->size != src->size ) + { + gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: mismatch image size for %s and %s\n", names[0], names[i] ); + break; + } + } + else + { + // create new image + pic = Mem_Malloc( gEngfuncs.Image_GetPool(), sizeof( rgbdata_t )); + memcpy( pic, src, sizeof( rgbdata_t )); + + // expand pic buffer for all layers + pic->buffer = Mem_Malloc( gEngfuncs.Image_GetPool(), pic->size * numLayers ); + pic->depth = 0; + } + + mipsize = srcsize = dstsize = 0; + + for( j = 0; j < max( 1, pic->numMips ); j++ ) + { + int width = Q_max( 1, ( pic->width >> j )); + int height = Q_max( 1, ( pic->height >> j )); + mipsize = GL_CalcImageSize( pic->type, width, height, 1 ); + memcpy( pic->buffer + dstsize + mipsize * i, src->buffer + srcsize, mipsize ); + dstsize += mipsize * numLayers; + srcsize += mipsize; + } + + gEngfuncs.FS_FreeImage( src ); + + // increase layers + pic->depth++; + } + + // there were errors + if( !pic || ( pic->depth != numLayers )) + { + gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: not all layers were loaded. Texture array is not created\n" ); + if( pic ) gEngfuncs.FS_FreeImage( pic ); + return 0; + } + + // it's multilayer image! + SetBits( pic->flags, IMAGE_MULTILAYER ); + pic->size *= numLayers; + + // allocate the new one + tex = GL_AllocTexture( name, flags ); + GL_ProcessImage( tex, pic ); + + if( !GL_UploadTexture( tex, pic )) + { + memset( tex, 0, sizeof( gl_texture_t )); + gEngfuncs.FS_FreeImage( pic ); // release source texture + return 0; + } + + GL_ApplyTextureParams( tex ); // update texture filter, wrap etc + gEngfuncs.FS_FreeImage( pic ); // release source texture + + // NOTE: always return texnum as index in array or engine will stop work !!! + return tex - gl_textures; +#endif +} + +/* +================ +GL_LoadTextureFromBuffer +================ +*/ +int GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ) +{ + gl_texture_t *tex; + + if( !GL_CheckTexName( name )) + return 0; + + // see if already loaded + if(( tex = GL_TextureForName( name )) && !update ) + return (tex - gl_textures); + + // couldn't loading image + if( !pic ) return 0; + + if( update ) + { + if( tex == NULL ) + gEngfuncs.Host_Error( "GL_LoadTextureFromBuffer: couldn't find texture %s for update\n", name ); + SetBits( tex->flags, flags ); + } + else + { + // allocate the new one + tex = GL_AllocTexture( name, flags ); + } + + GL_ProcessImage( tex, pic ); + + if( !GL_UploadTexture( tex, pic )) + { + memset( tex, 0, sizeof( gl_texture_t )); + return 0; + } + + return (tex - gl_textures); +} + +/* +================ +GL_CreateTexture + +creates texture from buffer +================ +*/ +int GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags ) +{ + qboolean update = FBitSet( flags, TF_UPDATE ) ? true : false; + int datasize = 1; + rgbdata_t r_empty; + + if( FBitSet( flags, TF_ARB_16BIT )) + datasize = 2; + else if( FBitSet( flags, TF_ARB_FLOAT )) + datasize = 4; + + ClearBits( flags, TF_UPDATE ); + memset( &r_empty, 0, sizeof( r_empty )); + r_empty.width = width; + r_empty.height = height; + r_empty.type = PF_RGBA_32; + r_empty.size = r_empty.width * r_empty.height * datasize * 4; + r_empty.buffer = (byte *)buffer; + + // clear invalid combinations + ClearBits( flags, TF_TEXTURE_3D ); + + // if image not luminance and not alphacontrast it will have color + if( !FBitSet( flags, TF_LUMINANCE ) && !FBitSet( flags, TF_ALPHACONTRAST )) + SetBits( r_empty.flags, IMAGE_HAS_COLOR ); + + if( FBitSet( flags, TF_HAS_ALPHA )) + SetBits( r_empty.flags, IMAGE_HAS_ALPHA ); +#if 0 + if( FBitSet( flags, TF_CUBEMAP )) + { + if( !GL_Support( GL_TEXTURE_CUBEMAP_EXT )) + return 0; + SetBits( r_empty.flags, IMAGE_CUBEMAP ); + r_empty.size *= 6; + } +#else + if( FBitSet( flags, TF_CUBEMAP )) + return 0; +#endif + return GL_LoadTextureFromBuffer( name, &r_empty, flags, update ); +} + +/* +================ +GL_CreateTextureArray + +creates texture array from buffer +================ +*/ +int GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ) +{ +#if 1 + return 0; +#else + rgbdata_t r_empty; + + memset( &r_empty, 0, sizeof( r_empty )); + r_empty.width = Q_max( width, 1 ); + r_empty.height = Q_max( height, 1 ); + r_empty.depth = Q_max( depth, 1 ); + r_empty.type = PF_RGBA_32; + r_empty.size = r_empty.width * r_empty.height * r_empty.depth * 4; + r_empty.buffer = (byte *)buffer; + + // clear invalid combinations + ClearBits( flags, TF_CUBEMAP|TF_SKYSIDE|TF_HAS_LUMA|TF_MAKELUMA|TF_ALPHACONTRAST ); + + // if image not luminance it will have color + if( !FBitSet( flags, TF_LUMINANCE )) + SetBits( r_empty.flags, IMAGE_HAS_COLOR ); + + if( FBitSet( flags, TF_HAS_ALPHA )) + SetBits( r_empty.flags, IMAGE_HAS_ALPHA ); + + if( FBitSet( flags, TF_TEXTURE_3D )) + { + if( !GL_Support( GL_TEXTURE_3D_EXT )) + return 0; + } + else + { + if( !GL_Support( GL_TEXTURE_ARRAY_EXT )) + return 0; + SetBits( r_empty.flags, IMAGE_MULTILAYER ); + } + + return GL_LoadTextureInternal( name, &r_empty, flags ); +#endif +} + +/* +================ +GL_FindTexture +================ +*/ +int GL_FindTexture( const char *name ) +{ + gl_texture_t *tex; + + if( !GL_CheckTexName( name )) + return 0; + + // see if already loaded + if(( tex = GL_TextureForName( name ))) + return (tex - gl_textures); + + return 0; +} + +/* +================ +GL_FreeTexture +================ +*/ +void GL_FreeTexture( GLenum texnum ) +{ + // number 0 it's already freed + if( texnum <= 0 ) return; + + GL_DeleteTexture( &gl_textures[texnum] ); +} + +/* +================ +GL_ProcessTexture +================ +*/ +void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) +{ + gl_texture_t *image; + rgbdata_t *pic; + int flags = 0; + + if( texnum <= 0 || texnum >= MAX_TEXTURES ) + return; // missed image + image = &gl_textures[texnum]; + + // select mode + if( gamma != -1.0f ) + { + flags = IMAGE_LIGHTGAMMA; + } + else if( topColor != -1 && bottomColor != -1 ) + { + flags = IMAGE_REMAP; + } + else + { + gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: bad operation for %s\n", image->name ); + return; + } + + if( !image->original ) + { + gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: no input data for %s\n", image->name ); + return; + } + + if( ImageDXT( image->original->type )) + { + gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: can't process compressed texture %s\n", image->name ); + return; + } + + // all the operations makes over the image copy not an original + pic = gEngfuncs.FS_CopyImage( image->original ); + gEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f ); + + GL_UploadTexture( image, pic ); + + gEngfuncs.FS_FreeImage( pic ); +} + +/* +================ +GL_TexMemory + +return size of all uploaded textures +================ +*/ +int GL_TexMemory( void ) +{ + int i, total = 0; + + for( i = 0; i < gl_numTextures; i++ ) + total += gl_textures[i].size; + + return total; +} + +/* +============================================================================== + +INTERNAL TEXTURES + +============================================================================== +*/ +/* +================== +GL_FakeImage +================== +*/ +static rgbdata_t *GL_FakeImage( int width, int height, int depth, int flags ) +{ + static byte data2D[1024]; // 16x16x4 + static rgbdata_t r_image; + + // also use this for bad textures, but without alpha + r_image.width = Q_max( 1, width ); + r_image.height = Q_max( 1, height ); + r_image.depth = Q_max( 1, depth ); + r_image.flags = flags; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * r_image.depth * 4; + r_image.buffer = (r_image.size > sizeof( data2D )) ? NULL : data2D; + r_image.palette = NULL; + r_image.numMips = 1; + r_image.encode = 0; + + if( FBitSet( r_image.flags, IMAGE_CUBEMAP )) + r_image.size *= 6; + memset( data2D, 0xFF, sizeof( data2D )); + + return &r_image; +} + +/* +================== +R_InitDlightTexture +================== +*/ +void R_InitDlightTexture( void ) +{ + rgbdata_t r_image; + + if( tr.dlightTexture != 0 ) + return; // already initialized + + memset( &r_image, 0, sizeof( r_image )); + r_image.width = BLOCK_SIZE; + r_image.height = BLOCK_SIZE; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = LIGHTMAP_FORMAT; + r_image.size = r_image.width * r_image.height * LIGHTMAP_BPP; + + tr.dlightTexture = GL_LoadTextureInternal( "*dlight", &r_image, TF_NOMIPMAP|TF_CLAMP|TF_ATLAS_PAGE ); +} + +/* +================== +GL_CreateInternalTextures +================== +*/ +static void GL_CreateInternalTextures( void ) +{ + int dx2, dy, d; + int x, y; + rgbdata_t *pic; + + // emo-texture from quake1 + pic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR ); + + for( y = 0; y < 16; y++ ) + { + for( x = 0; x < 16; x++ ) + { + if(( y < 8 ) ^ ( x < 8 )) + ((uint *)pic->buffer)[y*16+x] = 0xFFFF00FF; + else ((uint *)pic->buffer)[y*16+x] = 0xFF000000; + } + } + + tr.defaultTexture = GL_LoadTextureInternal( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP ); + + // particle texture from quake1 + pic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA ); + + for( x = 0; x < 16; x++ ) + { + dx2 = x - 8; + dx2 = dx2 * dx2; + + for( y = 0; y < 16; y++ ) + { + dy = y - 8; + d = 255 - 35 * sqrt( dx2 + dy * dy ); + pic->buffer[( y * 16 + x ) * 4 + 3] = bound( 0, d, 255 ); + } + } + + tr.particleTexture = GL_LoadTextureInternal( REF_PARTICLE_TEXTURE, pic, TF_CLAMP ); + + // white texture + pic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); + for( x = 0; x < 16; x++ ) + ((uint *)pic->buffer)[x] = 0xFFFFFFFF; + tr.whiteTexture = GL_LoadTextureInternal( REF_WHITE_TEXTURE, pic, TF_COLORMAP ); + + // gray texture + pic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); + for( x = 0; x < 16; x++ ) + ((uint *)pic->buffer)[x] = 0xFF7F7F7F; + tr.grayTexture = GL_LoadTextureInternal( REF_GRAY_TEXTURE, pic, TF_COLORMAP ); + + // black texture + pic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); + for( x = 0; x < 16; x++ ) + ((uint *)pic->buffer)[x] = 0xFF000000; + tr.blackTexture = GL_LoadTextureInternal( REF_BLACK_TEXTURE, pic, TF_COLORMAP ); + + // cinematic dummy + pic = GL_FakeImage( 640, 100, 1, IMAGE_HAS_COLOR ); + tr.cinTexture = GL_LoadTextureInternal( "*cintexture", pic, TF_NOMIPMAP|TF_CLAMP ); +} + +/* +================== +GL_Create332Palette +================== +*/ +static void GL_Create332Palette( void ) +{ + gl_palette_332 = ( byte* )valloc( PALETTE_SIZE ); + if( !gl_palette_332 ) + { + gl_palette_332 = ( byte* )memalign( 16, PALETTE_SIZE ); + if ( !gl_palette_332 ) + gEngfuncs.Host_Error( "GL_Create332Palette: out of memory!\n" ); + } + + for(int i = 0; i < 256; i++) + { +#if PALETTE_FORMAT == GU_PSM_5650 + gl_palette_332[i * 2 + 0] = ( i & 0x07 ) << 2; + gl_palette_332[i * 2 + 1] = ( i & 0x38 ) >> 3; + gl_palette_332[i * 2 + 1] |= ( i & 0xc0 ); +#elif PALETTE_FORMAT == GU_PSM_5551 + gl_palette_332[i * 2 + 0] = ( i & 0x07 ) << 2; + gl_palette_332[i * 2 + 0] |= ( i & 0x08 ) << 4; + gl_palette_332[i * 2 + 1] = ( i & 0x30 ) >> 4; + gl_palette_332[i * 2 + 1] |= ( i & 0xc0 ) >> 1; + gl_palette_332[i * 2 + 1] |= 0x80; +#elif PALETTE_FORMAT == GU_PSM_4444 + gl_palette_332[i * 2 + 0] = ( i & 0x07 ) << 1; + gl_palette_332[i * 2 + 0] |= ( i & 0x38 ) << 2; + gl_palette_332[i * 2 + 1] = ( i & 0xc0 ) >> 4; + gl_palette_332[i * 2 + 1] |= 0xf0; +#elif PALETTE_FORMAT == GU_PSM_8888 + gl_palette_332[i * 4 + 0] = ( i & 0x07 ) << 5; + gl_palette_332[i * 4 + 1] = ( i & 0x38 ) << 2; + gl_palette_332[i * 4 + 2] = ( i & 0xc0 ); + gl_palette_332[i * 4 + 3] = 0xff; +#endif + } + sceKernelDcacheWritebackRange( gl_palette_332, PALETTE_SIZE ); +} + + +/* +=============== +R_TextureList_f +=============== +*/ +void R_TextureList_f( void ) +{ + gl_texture_t *image; + int i, texCount, ram_bytes = 0, vram_bytes = 0; + + gEngfuncs.Con_Printf( "\n" ); + gEngfuncs.Con_Printf( " -id- -w- -h- -size- -fmt- -s- -wrap- -name--------\n" ); + + for( i = texCount = 0, image = gl_textures; i < gl_numTextures; i++, image++ ) + { + if( !image->dstTexture ) continue; + + if( image->dstPalette && image->dstPalette != gl_palette_332 ) ram_bytes += PALETTE_SIZE; + + vram_bytes += ( FBitSet( image->flags, TF_IMG_INVRAM ) ) ? image->size : 0; + ram_bytes += ( FBitSet( image->flags, TF_IMG_INVRAM ) ) ? 0 : image->size; + texCount++; + + gEngfuncs.Con_Printf( "%4i: ", i ); + gEngfuncs.Con_Printf( "%3i %3i ", image->width, image->height ); + gEngfuncs.Con_Printf( "%12s ", Q_memprint( image->size )); + + switch( image->format ) + { + case GU_PSM_T4: + gEngfuncs.Con_Printf( "T4 " ); + break; + case GU_PSM_T8: + gEngfuncs.Con_Printf( "T8 " ); + break; + case GU_PSM_T16: + gEngfuncs.Con_Printf( "T16 " ); + break; + case GU_PSM_T32: + gEngfuncs.Con_Printf( "T32 " ); + break; + case GU_PSM_DXT1: + gEngfuncs.Con_Printf( "DXT1 " ); + break; + case GU_PSM_DXT3: + gEngfuncs.Con_Printf( "DXT3 " ); + break; + case GU_PSM_DXT5: + gEngfuncs.Con_Printf( "DXT5 " ); + break; + case GU_PSM_4444: + gEngfuncs.Con_Printf( "4444 " ); + break; + case GU_PSM_5551: + gEngfuncs.Con_Printf( "5551 " ); + break; + case GU_PSM_5650: + gEngfuncs.Con_Printf( "5650 " ); + break; + case GU_PSM_8888: + gEngfuncs.Con_Printf( "8888 " ); + break; + default: + gEngfuncs.Con_Printf( " ^1ERR^7 " ); + break; + } + + gEngfuncs.Con_Printf( "%s ", FBitSet( image->flags, TF_IMG_SWIZZLED ) ? "Y" : "N" ); + + if( image->flags & TF_CLAMP ) + gEngfuncs.Con_Printf( "clamp " ); + else if( image->flags & TF_BORDER ) + gEngfuncs.Con_Printf( "border " ); + else gEngfuncs.Con_Printf( "repeat " ); + + gEngfuncs.Con_Printf( " %s\n", image->name ); + } + + gEngfuncs.Con_Printf( "---------------------------------------------------------\n" ); + gEngfuncs.Con_Printf( "%i total textures\n", texCount ); + gEngfuncs.Con_Printf( "%i max index\n", gl_numTextures ); + gEngfuncs.Con_Printf( "%s total ram memory used\n", Q_memprint( ram_bytes )); + gEngfuncs.Con_Printf( "%s total vram memory used\n", Q_memprint( vram_bytes )); + gEngfuncs.Con_Printf( "\n" ); +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) +{ + memset( gl_textures, 0, sizeof( gl_textures )); + memset( gl_texturesHashTable, 0, sizeof( gl_texturesHashTable )); + gl_numTextures = 0; + + // create unused 0-entry + Q_strncpy( gl_textures->name, "*unused*", sizeof( gl_textures->name )); + gl_textures->hashValue = COM_HashKey( gl_textures->name, TEXTURES_HASH_SIZE ); + gl_textures->nextHash = gl_texturesHashTable[gl_textures->hashValue]; + gl_texturesHashTable[gl_textures->hashValue] = gl_textures; + gl_numTextures = 1; + + // create 332 palette + GL_Create332Palette(); + + // validate cvars + GL_CreateInternalTextures(); + + gEngfuncs.Cmd_AddCommand( "texturelist", R_TextureList_f, "display loaded textures list" ); +} + +/* +=============== +R_ShutdownImages +=============== +*/ +void R_ShutdownImages( void ) +{ + gl_texture_t *tex; + int i; + + gEngfuncs.Cmd_RemoveCommand( "texturelist" ); + GL_CleanupAllTextureUnits(); + + for( i = 0, tex = gl_textures; i < gl_numTextures; i++, tex++ ) + GL_DeleteTexture( tex ); + + if( gl_palette_332 ) + { + if( vchkptr( gl_palette_332 )) + vfree( gl_palette_332 ); + else free( gl_palette_332 ); + } + + memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures )); + memset( gl_texturesHashTable, 0, sizeof( gl_texturesHashTable )); + memset( gl_textures, 0, sizeof( gl_textures )); + gl_numTextures = 0; +} diff --git a/ref_gu/gu_local.h b/ref_gu/gu_local.h new file mode 100644 index 000000000..c193d9355 --- /dev/null +++ b/ref_gu/gu_local.h @@ -0,0 +1,829 @@ +/* +gu_local.h - renderer local declarations +Copyright (C) 2010 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#ifndef GL_LOCAL_H +#define GL_LOCAL_H + +#include +#include +#include +#include + +#include "port.h" +#include "xash3d_types.h" +#include "cvardef.h" +#include "const.h" +#include "com_model.h" +#include "cl_entity.h" +#include "render_api.h" +#include "protocol.h" +#include "dlight.h" +#include "gu_frustum.h" +#include "ref_api.h" +#include "xash3d_mathlib.h" +#include "ref_params.h" +#include "enginefeatures.h" +#include "com_strings.h" +#include "pm_movevars.h" +//#include "cvar.h" +#include "gu_helper.h" +#include "gu_extension.h" +#include "gu_vram.h" +#include "wadfile.h" + +#ifndef offsetof +#define offsetof(s,m) (size_t)&(((s *)0)->m) +#endif // offsetof + +#define ASSERT(x) if(!( x )) gEngfuncs.Host_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) +#define Assert(x) if(!( x )) gEngfuncs.Host_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) + +#include + +#define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) cvar_t cv = { cvname, cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, cvdesc } +#define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) cvar_t cv = { #cv, cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, cvdesc } +#define CVAR_TO_BOOL( x ) ((x) && ((x)->value != 0.0f) ? true : false ) + +#define WORLD (gEngfuncs.GetWorld()) +#define WORLDMODEL (gEngfuncs.pfnGetModelByIndex( 1 )) +#define MOVEVARS (gEngfuncs.pfnGetMoveVars()) + +// make mod_ref.h? +#define LM_SAMPLE_SIZE 16 + + +extern byte *r_temppool; + +#define LIGHTMAP_BPP 1 //1 2 3 4 + +#if LIGHTMAP_BPP == 1 +#define LIGHTMAP_FORMAT PF_RGB_332 +#elif LIGHTMAP_BPP == 2 +#define LIGHTMAP_FORMAT PF_RGB_5650 +#elif LIGHTMAP_BPP == 3 +#define LIGHTMAP_FORMAT PF_RGB_24 +#elif LIGHTMAP_BPP == 4 +#define LIGHTMAP_FORMAT PF_RGBA_32 +#else +#error (1 > LIGHTMAP_BPP > 4) +#endif + +#if 1 + +#define BLOCK_SIZE tr.block_size // lightmap blocksize +#define BLOCK_SIZE_DEFAULT 128 // for keep backward compatibility +#define BLOCK_SIZE_MAX 128 + +#define MAX_TEXTURES 1536 +#define MAX_DETAIL_TEXTURES 64 +#define MAX_LIGHTMAPS 64 +#define SUBDIVIDE_SIZE 64 +#define MAX_DECAL_SURFS 256 +#define MAX_DRAW_STACK 2 // normal view and menu view + +#else + +#define BLOCK_SIZE tr.block_size // lightmap blocksize +#define BLOCK_SIZE_DEFAULT 128 // for keep backward compatibility +#define BLOCK_SIZE_MAX 1024 + +#define MAX_TEXTURES 4096 +#define MAX_DETAIL_TEXTURES 256 +#define MAX_LIGHTMAPS 256 +#define SUBDIVIDE_SIZE 64 +#define MAX_DECAL_SURFS 4096 +#define MAX_DRAW_STACK 2 // normal view and menu view + +#endif + +#define SHADEDOT_QUANT 16 // precalculated dot products for quantized angles +#define SHADE_LAMBERT 1.495f + +#if 1 +#define DEFAULT_ALPHATEST 0x00 +#else +#define DEFAULT_ALPHATEST 0.0f +#endif + +// refparams +#define RP_NONE 0 +#define RP_ENVVIEW BIT( 0 ) // used for cubemapshot +#define RP_OLDVIEWLEAF BIT( 1 ) +#define RP_CLIPPLANE BIT( 2 ) + +#define RP_NONVIEWERREF (RP_ENVVIEW) +#define R_ModelOpaque( rm ) ( rm == kRenderNormal ) +#define R_StaticEntity( ent ) ( VectorIsNull( ent->origin ) && VectorIsNull( ent->angles )) +#define RP_LOCALCLIENT( e ) ((e) != NULL && (e)->index == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) && e->player ) +#define RP_NORMALPASS() ( FBitSet( RI.params, RP_NONVIEWERREF ) == 0 ) + +#define CL_IsViewEntityLocalPlayer() ( ENGINE_GET_PARM( PARM_VIEWENT_INDEX ) == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) ) + +#define CULL_VISIBLE 0 // not culled +#define CULL_BACKSIDE 1 // backside of transparent wall +#define CULL_FRUSTUM 2 // culled by frustum +#define CULL_VISFRAME 3 // culled by PVS +#define CULL_OTHER 4 // culled by other reason + +typedef struct gltexture_s +{ + + char name[128]; // game path, including extension (can be store image programs) + short srcWidth; // keep unscaled sizes + short srcHeight; + short width; // upload width\height + short height; + //short depth; // texture depth or count of layers for 2D_ARRAY + byte numMips; // mipmap count + byte *dstTexture; // texture pointer + byte *dstPalette; + byte bpp; + int format; // uploaded format + //GLint encode; // using GLSL decoder + texFlags_t flags; + + rgba_t fogParams; // some water textures + // contain info about underwater fog + + rgbdata_t *original; // keep original image + + // debug info + size_t size; // upload size for debug targets + + // detail textures stuff + float xscale; + float yscale; +#if 0 + int servercount; +#endif + uint hashValue; + struct gltexture_s *nextHash; +} gl_texture_t; + +#if 1 +typedef struct +{ + float x, y, z; +}gu_vert_fv_t; + +typedef struct +{ + unsigned int c; + float x, y, z; +}gu_vert_fcv_t; + +typedef struct +{ + float u, v; + float x, y, z; +}gu_vert_ftv_t; + +typedef struct +{ + float u, v; + unsigned int c; + float x, y, z; +}gu_vert_ftcv_t; + +typedef struct +{ + float u, v; + unsigned int c; + float nx, ny, nz; + float x, y, z; +}gu_vert_ftcnv_t; + +typedef struct +{ + short x, y, z; +}gu_vert_hv_t; + +typedef struct +{ + short u, v; + short x, y, z; +}gu_vert_htv_t; + +typedef struct +{ + short u, v; + unsigned int c; + short x, y, z; +}gu_vert_htcv_t; +#endif + +typedef struct +{ + int params; // rendering parameters + + qboolean drawWorld; // ignore world for drawing PlayerModel + qboolean isSkyVisible; // sky is visible + qboolean onlyClientDraw; // disabled by client request + qboolean drawOrtho; // draw world as orthogonal projection + + float fov_x, fov_y; // current view fov + + cl_entity_t *currententity; + model_t *currentmodel; + cl_entity_t *currentbeam; // same as above but for beams + + int viewport[4]; + gl_frustum_t frustum; + + mleaf_t *viewleaf; + mleaf_t *oldviewleaf; + vec3_t pvsorigin; + vec3_t vieworg; // locked vieworigin + vec3_t viewangles; + vec3_t vforward; + vec3_t vright; + vec3_t vup; + + vec3_t cullorigin; + vec3_t cull_vforward; + vec3_t cull_vright; + vec3_t cull_vup; + + float farClip; + + qboolean fogCustom; + qboolean fogEnabled; + qboolean fogSkybox; + vec4_t fogColor; + float fogDensity; + float fogStart; + float fogEnd; + int cached_contents; // in water + int cached_waterlevel; // was in water + + float skyMins[2][6]; + float skyMaxs[2][6]; + + matrix4x4 objectMatrix; // currententity matrix + matrix4x4 worldviewMatrix; // modelview for world + matrix4x4 modelviewMatrix; // worldviewMatrix * objectMatrix + + matrix4x4 projectionMatrix; + matrix4x4 worldviewProjectionMatrix; // worldviewMatrix * projectionMatrix + byte visbytes[(MAX_MAP_LEAFS+7)/8];// actual PVS for current frame + + float viewplanedist; + mplane_t clipPlane; +} ref_instance_t; + +typedef struct +{ + cl_entity_t *solid_entities[MAX_VISIBLE_PACKET]; // opaque moving or alpha brushes + cl_entity_t *trans_entities[MAX_VISIBLE_PACKET]; // translucent brushes + cl_entity_t *beam_entities[MAX_VISIBLE_PACKET]; + uint num_solid_entities; + uint num_trans_entities; + uint num_beam_entities; +} draw_list_t; + +typedef struct +{ + int defaultTexture; // use for bad textures + int particleTexture; + int whiteTexture; + int grayTexture; + int blackTexture; + int solidskyTexture; // quake1 solid-sky layer + int alphaskyTexture; // quake1 alpha-sky layer + int lightmapTextures[MAX_LIGHTMAPS]; + int dlightTexture; // custom dlight texture + int skyboxTextures[6]; // skybox sides + int cinTexture; // cinematic texture + + int skytexturenum; // this not a gl_texturenum! +#if 0 + int skyboxbasenum; // start with 5800 +#endif + // entity lists + draw_list_t draw_stack[MAX_DRAW_STACK]; + int draw_stack_pos; + draw_list_t *draw_list; + + msurface_t *draw_decals[MAX_DECAL_SURFS]; + int num_draw_decals; + + // OpenGL matrix states + qboolean modelviewIdentity; + + int visframecount; // PVS frame + int dlightframecount; // dynamic light frame + int realframecount; // not including viewpasses + int framecount; + + qboolean ignore_lightgamma; + qboolean fCustomRendering; + qboolean fResetVis; + qboolean fFlipViewModel; + + byte visbytes[(MAX_MAP_LEAFS+7)/8]; // member custom PVS + int lightstylevalue[MAX_LIGHTSTYLES]; // value 0 - 65536 + int block_size; // lightmap blocksize + + double frametime; // special frametime for multipass rendering (will set to 0 on a nextview) + float blend; // global blend value + + // cull info + vec3_t modelorg; // relative to viewpoint + + qboolean fCustomSkybox; +} gl_globals_t; + +typedef struct +{ + uint c_world_polys; + uint c_studio_polys; + uint c_sprite_polys; + uint c_alias_polys; + uint c_world_leafs; + + uint c_view_beams_count; + uint c_active_tents_count; + uint c_alias_models_drawn; + uint c_studio_models_drawn; + uint c_sprite_models_drawn; + uint c_particle_count; + + uint c_client_ents; // entities that moved to client + double t_world_node; + double t_world_draw; +} ref_speeds_t; + +extern ref_speeds_t r_stats; +extern ref_instance_t RI; +extern gl_globals_t tr; + +extern float gldepthmin, gldepthmax; +#define r_numEntities (tr.draw_list->num_solid_entities + tr.draw_list->num_trans_entities) +#define r_numStatics (r_stats.c_client_ents) + +// +// gu_backend.c +// +void GL_BackendStartFrame( void ); +void GL_BackendEndFrame( void ); +void GL_CleanUpTextureUnits( int last ); +void GL_Bind( GLint tmu, GLenum texnum ); +void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t ); +void GL_SetTexCoordArrayMode( GLenum mode ); +void GL_LoadTexMatrix( const matrix4x4 m ); +void GL_LoadTexMatrixExt( const float *glmatrix ); +void GL_LoadMatrix( const matrix4x4 source ); +void GL_TexGen( GLenum coord, GLenum mode ); +void GL_SelectTexture( GLint texture ); +void GL_CleanupAllTextureUnits( void ); +void GL_LoadIdentityTexMatrix( void ); +void GL_DisableAllTexGens( void ); +void GL_SetRenderMode( int mode ); +void GL_TextureTarget( uint target ); +void GL_Cull( GLenum cull ); +void R_ShowTextures( void ); +void SCR_TimeRefresh_f( void ); + +// +// gu_beams.c +// +void CL_DrawBeams( int fTrans, BEAM *active_beams ); +qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly ); + +// +// gu_cull.c +// +int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ); +qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); +qboolean R_CullSphere( const vec3_t centre, const float radius ); +int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ); + +// +// gu_decals.c +// +void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse ); +float *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount ); +void DrawSingleDecal( decal_t *pDecal, msurface_t *fa ); +void R_EntityRemoveDecals( model_t *mod ); +void DrawDecalsBatch( void ); +void R_ClearDecals( void ); + +// +// gu_draw.c +// +void R_Set2DMode( qboolean enable ); +void R_DrawTileClear( int texnum, int x, int y, int w, int h ); +void R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data ); + +// +// gu_drawhulls.c +// +void R_DrawWorldHull( void ); +void R_DrawModelHull( void ); + +// +// gu_image.c +// +gl_texture_t *R_GetTexture( GLenum texnum ); +#define GL_LoadTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, false ) +#define GL_UpdateTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, true ) +int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ); +int GL_LoadTextureArray( const char **names, int flags ); +int GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ); +byte *GL_ResampleTexture( const byte *source, int in_w, int in_h, int out_w, int out_h, qboolean isNormalMap ); +#define PC_SWF( X ) ( ( X ) | ( 1 << 15 ) ) +#define PC_HWF( X ) ( X ) +void GL_PixelConverter( byte *dst, const byte *src, int size, int inFormat, int outFormat ); +int GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags ); +int GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ); +void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ); +qboolean GL_UpdateTexture( int texnum, int xoff, int yoff, int width, int height, const void *buffer ); +void GL_UpdateTexSize( int texnum, int width, int height, int depth ); +int GL_FindTexture( const char *name ); +void GL_FreeTexture( GLenum texnum ); +const char *GL_Target( GLenum target ); +void R_InitDlightTexture( void ); +void R_TextureList_f( void ); +void R_InitImages( void ); +void R_ShutdownImages( void ); +int GL_TexMemory( void ); + +// +// gu_rlight.c +// +void CL_RunLightStyles( void ); +void R_PushDlights( void ); +void R_AnimateLight( void ); +void R_GetLightSpot( vec3_t lightspot ); +void R_MarkLights( dlight_t *light, int bit, mnode_t *node ); +colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lightspot, vec3_t lightvec ); +int R_CountSurfaceDlights( msurface_t *surf ); +colorVec R_LightPoint( const vec3_t p0 ); +int R_CountDlights( void ); + +// +// gu_rmain.c +// +void R_ClearScene( void ); +void R_LoadIdentity( void ); +void R_RenderScene( void ); +void R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size ); +void R_SetupRefParams( const struct ref_viewpass_s *rvp ); +void R_TranslateForEntity( cl_entity_t *e ); +void R_RotateForEntity( cl_entity_t *e ); +void R_SetupGL( qboolean set_gl_state ); +void R_AllowFog( qboolean allowed ); +void R_SetupFrustum( void ); +void R_FindViewLeaf( void ); +void R_CheckGamma( void ); +void R_PushScene( void ); +void R_PopScene( void ); +void R_DrawFog( void ); +int CL_FxBlend( cl_entity_t *e ); + +// +// gu_rmath.c +// +void Matrix4x4_ToFMatrix4( const matrix4x4 in, ScePspFMatrix4 *out ); +void Matrix4x4_FromFMatrix4( matrix4x4 out, ScePspFMatrix4 *in ); +void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); +void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z ); +void Matrix4x4_ConcatScale( matrix4x4 out, float x ); +void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ); +void Matrix4x4_CreateScale( matrix4x4 out, float x ); +void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_CreateProjection(matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar); +void Matrix4x4_CreateOrtho(matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar); +void Matrix4x4_CreateModelview( matrix4x4 out ); + +// +// gu_rmisc.c +// +void R_ClearStaticEntities( void ); + +// +// gu_rsurf.c +// +void R_MarkLeaves( void ); +void R_DrawWorld( void ); +void R_DrawWaterSurfaces( void ); +void R_DrawBrushModel( cl_entity_t *e ); +void GL_SubdivideSurface( msurface_t *fa ); +void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ); +void DrawGLPoly( glpoly_t *p, float xScale, float yScale ); +texture_t *R_TextureAnimation( msurface_t *s ); +void GL_SetupFogColorForSurfaces( void ); +void R_DrawAlphaTextureChains( void ); +void GL_RebuildLightmaps( void ); +void GL_InitRandomTable( void ); +void GL_BuildLightmaps( void ); +void GL_ResetFogColor( void ); +void R_GenerateVBO( void ); +void R_ClearVBO( void ); +void R_AddDecalVBO( decal_t *pdecal, msurface_t *surf ); + +// +// gu_rpart.c +// +void CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime ); +void CL_DrawParticles( double frametime, particle_t *cl_active_particles, float partsize ); +void CL_DrawTracers( double frametime, particle_t *cl_active_tracers ); + + +// +// gu_sprite.c +// +void R_SpriteInit( void ); +void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags ); +mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ); +void R_DrawSpriteModel( cl_entity_t *e ); + +// +// gu_studio.c +// +void R_StudioInit( void ); +void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ); +void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); +float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ); +struct mstudiotex_s *R_StudioGetTexture( cl_entity_t *e ); +float CL_GetStudioEstimatedFrame( cl_entity_t *ent ); +int R_GetEntityRenderMode( cl_entity_t *ent ); +void R_DrawStudioModel( cl_entity_t *e ); +player_info_t *pfnPlayerInfo( int index ); +void R_GatherPlayerLight( void ); +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ); +void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); +void R_StudioResetPlayerModels( void ); +void CL_InitStudioAPI( void ); +void Mod_StudioLoadTextures( model_t *mod, void *data ); +void Mod_StudioUnloadTextures( void *data ); + +// +// gu_alias.c +// +void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ); +void R_DrawAliasModel( cl_entity_t *e ); +void R_AliasInit( void ); + +// +// gu_warp.c +// +void R_InitSkyClouds( mip_t *mt, struct texture_s *tx, qboolean custom_palette ); +void R_AddSkyBoxSurface( msurface_t *fa ); +void R_ClearSkyBox( void ); +void R_DrawSkyBox( void ); +void R_DrawClouds( void ); +void EmitWaterPolys( msurface_t *warp, qboolean reverse ); + +// +// gu_vgui.c +// +void VGUI_DrawInit( void ); +void VGUI_DrawShutdown( void ); +void VGUI_SetupDrawingText( int *pColor ); +void VGUI_SetupDrawingRect( int *pColor ); +void VGUI_SetupDrawingImage( int *pColor ); +void VGUI_BindTexture( int id ); +void VGUI_EnableTexture( qboolean enable ); +void VGUI_CreateTexture( int id, int width, int height ); +void VGUI_UploadTexture( int id, const char *buffer, int width, int height ); +void VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ); +void VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ); +void VGUI_GetTextureSizes( int *width, int *height ); +int VGUI_GenerateTexture( void ); + +//#include "vid_common.h" + +// +// renderer exports +// +qboolean R_Init( void ); +void R_Shutdown( void ); +void GL_SetupAttributes( int safegl ); +void GL_OnContextCreated( void ); +void GL_InitExtensions( void ); +void GL_ClearExtensions( void ); +void VID_CheckChanges( void ); +int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ); +void GL_FreeImage( const char *name ); +qboolean VID_ScreenShot( const char *filename, int shot_type ); +qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot ); +void R_BeginFrame( qboolean clearScene ); +void R_RenderFrame( const struct ref_viewpass_s *vp ); +void R_EndFrame( void ); +void R_ClearScene( void ); +void R_GetTextureParms( int *w, int *h, int texnum ); +void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int curFrame, const struct model_s *pSprite ); +void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ); +void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ); +qboolean R_SpeedsMessage( char *out, size_t size ); +void R_SetupSky( const char *skyboxname ); +qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); +int R_WorldToScreen( const vec3_t point, vec3_t screen ); +void R_ScreenToWorld( const vec3_t screen, vec3_t point ); +qboolean R_AddEntity( struct cl_entity_s *pRefEntity, int entityType ); +void Mod_LoadMapSprite( struct model_s *mod, const void *buffer, size_t size, qboolean *loaded ); +void Mod_SpriteUnloadTextures( void *data ); +void Mod_UnloadAliasModel( struct model_s *mod ); +void Mod_AliasUnloadTextures( void *data ); +void GL_SetRenderMode( int mode ); +void GL_SetColor4ub( byte r, byte g, byte b, byte a ); +void R_RunViewmodelEvents( void ); +void R_DrawViewModel( void ); +int R_GetSpriteTexture( const struct model_s *m_pSpriteModel, int frame ); +void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale ); +void R_RemoveEfrags( struct cl_entity_s *ent ); +void R_AddEfrags( struct cl_entity_s *ent ); +void R_DecalRemoveAll( int texture ); +int R_CreateDecalList( decallist_t *pList ); +void R_ClearAllDecals( void ); +byte *Mod_GetCurrentVis( void ); +void Mod_SetOrthoBounds( const float *mins, const float *maxs ); +void R_NewMap( void ); +void CL_AddCustomBeam( cl_entity_t *pEnvBeam ); + +// +// gu_opengl.c +// + +// +// gu_triapi.c +// +void TriRenderMode( int mode ); +void TriBegin( int mode ); +void TriEnd( void ); +void TriTexCoord2f( float u, float v ); +void TriVertex3fv( const float *v ); +void TriVertex3f( float x, float y, float z ); +void TriColor4f( float r, float g, float b, float a ); +void TriColor4ub( byte r, byte g, byte b, byte a ); +void TriBrightness( float brightness ); +int TriWorldToScreen( const float *world, float *screen ); +int TriSpriteTexture( model_t *pSpriteModel, int frame ); +void TriFog( float flFogColor[3], float flStart, float flEnd, int bOn ); +void TriGetMatrix( const int pname, float *matrix ); +void TriFogParams( float flDensity, int iFogSkybox ); +void TriCullFace( TRICULLSTYLE mode ); +uint getTriBrightness( float brightness ); +int TriBoxInPVS( float *mins, float *maxs ); +void TriLightAtPoint( float *pos, float *value ); +void TriColor4fRendermode( float r, float g, float b, float a, int rendermode ); +int getTriAPI( int version, triangleapi_t *api ); + +// +// gu_clipping.c +// +#define CLIPPING_DEBUGGING 0 +void GU_ClipSetWorldFrustum( const matrix4x4 in ); +void GU_ClipRestoreWorldFrustum( void ); +void GU_ClipSetModelFrustum( const matrix4x4 in ); +void GU_ClipLoadFrustum( const mplane_t *plane ); // Experimental +int GU_ClipIsRequired( gu_vert_t* uv, int uvc ); +void GU_Clip( gu_vert_t *uv, int uvc, gu_vert_t **cv, int* cvc ); + +/* +======================================================================= + + GL STATE MACHINE + +======================================================================= +*/ +typedef struct +{ + int max_texture_size; + qboolean softwareGammaUpdate; +} glconfig_t; + +typedef struct +{ + + int width, height; + int activeTMU; + GLint currentTexture; + GLboolean texIdentityMatrix; + GLint isFogEnabled; + + int faceCull; + + qboolean stencilEnabled; + qboolean in2DMode; + + uint fogColor; + float fogDensity; + float fogStart; + float fogEnd; +} glstate_t; + + +typedef struct +{ + qboolean initialized; // OpenGL subsystem started + qboolean extended; // extended context allows to GL_Debug +} glwstate_t; + +typedef struct +{ + int screen_width; + int screen_height; + int buffer_width; + int buffer_format; + int buffer_bpp; + void *draw_buffer; + void *disp_buffer; + void *depth_buffer; + void *context_list; + size_t context_list_size; +}gurender_t; + +extern glconfig_t glConfig; +extern glstate_t glState; +extern gurender_t guRender; +// move to engine +extern glwstate_t glw_state; +extern ref_api_t gEngfuncs; +extern ref_globals_t *gpGlobals; + +#define ENGINE_GET_PARM_ (*gEngfuncs.EngineGetParm) +#define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_( ( parm ), 0 ) + +// +// renderer cvars +// +extern cvar_t *gl_texture_lodfunc; +extern cvar_t *gl_texture_lodbias; +extern cvar_t *gl_texture_lodslope; +extern cvar_t *gl_texture_nearest; +extern cvar_t *gl_lightmap_nearest; +extern cvar_t *gl_keeptjunctions; +extern cvar_t *gl_emboss_scale; +extern cvar_t *gl_round_down; +extern cvar_t *gl_detailscale; +extern cvar_t *gl_wireframe; +extern cvar_t *gl_depthoffset; +extern cvar_t *gl_clear; +extern cvar_t *gl_test; // cvar to testify new effects +extern cvar_t *gl_subdivide_size; + +extern cvar_t *r_speeds; +extern cvar_t *r_fullbright; +extern cvar_t *r_norefresh; +extern cvar_t *r_lighting_extended; +extern cvar_t *r_lighting_modulate; +extern cvar_t *r_lighting_ambient; +extern cvar_t *r_studio_lambert; +extern cvar_t *r_detailtextures; +extern cvar_t *r_drawentities; +extern cvar_t *r_decals; +extern cvar_t *r_novis; +extern cvar_t *r_nocull; +extern cvar_t *r_lockpvs; +extern cvar_t *r_lockfrustum; +extern cvar_t *r_traceglow; +extern cvar_t *r_dynamic; +extern cvar_t *r_lightmap; + +extern cvar_t *vid_brightness; +extern cvar_t *vid_gamma; + +// +// engine shared convars +// +extern cvar_t *gl_showtextures; +extern cvar_t *tracerred; +extern cvar_t *tracergreen; +extern cvar_t *tracerblue; +extern cvar_t *traceralpha; +extern cvar_t *cl_lightstyle_lerping; +extern cvar_t *r_showhull; +extern cvar_t *r_fast_particles; + +// +// engine callbacks +// +#include "crtlib.h" + +#define Mem_Malloc( pool, size ) gEngfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) +#define Mem_Calloc( pool, size ) gEngfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) +#define Mem_Realloc( pool, ptr, size ) gEngfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ ) +#define Mem_Free( mem ) gEngfuncs._Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_AllocPool( name ) gEngfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) +#define Mem_FreePool( pool ) gEngfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) +#define Mem_EmptyPool( pool ) gEngfuncs._Mem_EmptyPool( pool, __FILE__, __LINE__ ) + +#endif // GL_LOCAL_H diff --git a/ref_gu/gu_render.c b/ref_gu/gu_render.c new file mode 100644 index 000000000..1aa28e9a8 --- /dev/null +++ b/ref_gu/gu_render.c @@ -0,0 +1,477 @@ +/* +gu_render.c - render initialization +Copyright (C) 2010 Uncle Mike +Copyright (C) 2022 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include "gu_local.h" + +cvar_t *gl_texture_lodfunc; +cvar_t *gl_texture_lodbias; +cvar_t *gl_texture_lodslope; + +cvar_t *gl_texture_nearest; +cvar_t *gl_lightmap_nearest; +cvar_t *gl_keeptjunctions; +cvar_t *gl_emboss_scale; +cvar_t *gl_detailscale; +cvar_t *gl_depthoffset; +cvar_t *gl_wireframe; +cvar_t *gl_vsync; +cvar_t *gl_clear; +cvar_t *gl_test; +cvar_t *gl_subdivide_size; +cvar_t *r_speeds; +cvar_t *r_fullbright; +cvar_t *r_norefresh; +cvar_t *r_lighting_extended; +cvar_t *r_lighting_modulate; +cvar_t *r_lighting_ambient; +cvar_t *r_detailtextures; +cvar_t *r_drawentities; +cvar_t *r_adjust_fov; +cvar_t *r_decals; +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_lockpvs; +cvar_t *r_lockfrustum; +cvar_t *r_traceglow; +cvar_t *r_dynamic; +cvar_t *r_lightmap; +cvar_t *r_showhull; +cvar_t *r_fast_particles; +cvar_t *gl_round_down; +cvar_t *gl_showtextures; +cvar_t *cl_lightstyle_lerping; + +cvar_t *vid_brightness; +cvar_t *vid_gamma; +cvar_t *tracerred; +cvar_t *tracergreen; +cvar_t *tracerblue; +cvar_t *traceralpha; + +byte *r_temppool; + +gl_globals_t tr; +glconfig_t glConfig; +glstate_t glState; +glwstate_t glw_state; +gurender_t guRender; + +// Set frame buffer +#define PSP_FB_WIDTH 480 +#define PSP_FB_HEIGHT 272 +#define PSP_FB_BWIDTH 512 +#define PSP_FB_FORMAT GU_PSM_5650 //4444,5551,5650,8888 + +#if PSP_FB_FORMAT == GU_PSM_4444 +#define PSP_FB_BPP 2 +#elif PSP_FB_FORMAT == GU_PSM_5551 +#define PSP_FB_BPP 2 +#elif PSP_FB_FORMAT == GU_PSM_5650 +#define PSP_FB_BPP 2 +#elif PSP_FB_FORMAT == GU_PSM_8888 +#define PSP_FB_BPP 4 +#endif + +#define PSP_GU_LIST_SIZE 0x100000 // 1Mb + +static byte context_list[PSP_GU_LIST_SIZE] __attribute__( ( aligned( 64 ) ) ); + +/* +=============== +GU_Init +=============== +*/ +static void GU_Init( void ) +{ + memset( &guRender, 0, sizeof( guRender )); + + guRender.screen_width = PSP_FB_WIDTH; + guRender.screen_height = PSP_FB_HEIGHT; + guRender.buffer_width = PSP_FB_BWIDTH; + guRender.buffer_format = PSP_FB_FORMAT; + guRender.buffer_bpp = PSP_FB_BPP; + + guRender.draw_buffer = ( void* )valloc( guRender.buffer_width * guRender.screen_height * guRender.buffer_bpp ); + if( !guRender.draw_buffer ) + gEngfuncs.Host_Error( "Memory allocation failled! (guRender.draw_buffer)\n" ); + + guRender.disp_buffer = ( void* )valloc( guRender.buffer_width * guRender.screen_height * guRender.buffer_bpp ); + if( !guRender.disp_buffer ) + gEngfuncs.Host_Error( "Memory allocation failled! (guRender.disp_buffer)\n" ); + + guRender.depth_buffer = ( void* )valloc( guRender.buffer_width * guRender.screen_height * guRender.buffer_bpp ); + if( !guRender.depth_buffer ) + gEngfuncs.Host_Error( "Memory allocation failled! (guRender.depth_buffer)\n" ); + + guRender.context_list = context_list; + guRender.context_list_size = sizeof( context_list ); + + // Initialise the GU. + sceGuInit(); + + // Set up the GU. + sceGuStart( GU_DIRECT, guRender.context_list ); + + sceGuDrawBuffer( guRender.buffer_format, vrelptr( guRender.draw_buffer ), guRender.buffer_width ); + sceGuDispBuffer( guRender.screen_width, guRender.screen_height, vrelptr( guRender.disp_buffer ), guRender.buffer_width ); + sceGuDepthBuffer( vrelptr( guRender.depth_buffer ), guRender.buffer_width ); + + // Set the rendering offset and viewport. + sceGuOffset( 2048 - ( guRender.screen_width / 2 ), 2048 - ( guRender.screen_height / 2 ) ); + sceGuViewport( 2048, 2048, guRender.screen_width, guRender.screen_height ); + + // Set up scissoring. + sceGuEnable( GU_SCISSOR_TEST ); + sceGuScissor( 0, 0, guRender.screen_width, guRender.screen_height ); + + // Xash default + sceGuClearColor( GU_COLOR( 0.5f, 0.5f, 0.5f, 1.0f ) ); + + sceGuEnable( GU_DEPTH_TEST ); + sceGuDisable( GU_CULL_FACE ); + sceGuEnable( GU_CLIP_PLANES ); + sceGuDepthFunc( GU_LEQUAL ); + sceGuColor( 0xffffffff ); + + // Set up stencil + if( glState.stencilEnabled ) + { + sceGuDisable( GU_STENCIL_TEST ); + /*pglStencilMask( ( GLuint ) ~0 );*/ // alpha color sceGuPixelMask + sceGuStencilFunc( GU_EQUAL, 0, ~0 ); + sceGuStencilOp( GU_KEEP, GU_INCR, GU_INCR ); + } + + sceGuDepthRange( 0, 65535 ); + sceGuDepthOffset( 0 ); + + sceGuDisable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuAlphaFunc( GU_GREATER, DEFAULT_ALPHATEST, 0xff ); + sceGuEnable( GU_TEXTURE_2D ); + sceGuShadeModel( GU_SMOOTH ); + sceGuFrontFace( GU_CCW ); + + // Set the default matrices. + sceGumMatrixMode( GU_PROJECTION ); + sceGumLoadIdentity(); + sceGumMatrixMode( GU_VIEW ); + sceGumLoadIdentity(); + sceGumMatrixMode( GU_MODEL ); + sceGumLoadIdentity(); + sceGumMatrixMode( GU_TEXTURE ); + sceGumLoadIdentity(); + + sceGumUpdateMatrix(); + + sceGuFinish(); + sceGuSync( GU_SYNC_FINISH, GU_SYNC_WAIT ); + + // Turn on the display. + sceDisplayWaitVblankStart(); + sceGuDisplay(GU_TRUE); + + // Start a new render. + sceGuStart( GU_DIRECT, guRender.context_list ); +} + +/* +=============== +GU_Shutdown +=============== +*/ +static void GU_Shutdown( void ) +{ + // Finish rendering. + sceGuFinish(); + sceGuSync( GU_SYNC_FINISH, GU_SYNC_WAIT ); + + // Shut down the display. + sceGuTerm(); + + // Free the buffers. + if( guRender.draw_buffer ) + vfree( guRender.draw_buffer ); + if( guRender.disp_buffer ) + vfree( guRender.disp_buffer ); + if( guRender.depth_buffer ) + vfree( guRender.depth_buffer ); + + guRender.draw_buffer = NULL; + guRender.disp_buffer = NULL; + guRender.depth_buffer = NULL; +} + +/* +============== +GL_GetProcAddress + +defined just for nanogl/glwes, so it don't link to SDL2 directly, nor use dlsym +============== +*/ +void GAME_EXPORT *GL_GetProcAddress( const char *name ) +{ + return gEngfuncs.GL_GetProcAddress( name ); +} + +/* +=============== +GL_SetDefaultState +=============== +*/ +static void GL_SetDefaultState( void ) +{ + memset( &glState, 0, sizeof( glState )); + + // init draw stack + tr.draw_list = &tr.draw_stack[0]; + tr.draw_stack_pos = 0; + + // init glState struct + glState.currentTexture = -1; + glState.texIdentityMatrix = true; + glState.fogColor = 0; + glState.fogDensity = 0; + glState.fogStart = 100.0f; + glState.fogEnd = 1000.0f; +} +#if 0 +/* +=============== +GL_SetDefaults +=============== +*/ +static void GU_SetDefaults( void ) +{ + pglFinish(); + + pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f ); + + pglDisable( GL_DEPTH_TEST ); + pglDisable( GL_CULL_FACE ); + pglDisable( GL_SCISSOR_TEST ); + pglDepthFunc( GL_LEQUAL ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + + if( glState.stencilEnabled ) + { + pglDisable( GL_STENCIL_TEST ); + pglStencilMask( ( GLuint ) ~0 ); + pglStencilFunc( GL_EQUAL, 0, ~0 ); + pglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + + pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + pglPolygonOffset( -1.0f, -2.0f ); + + GL_CleanupAllTextureUnits(); + + pglDisable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglDisable( GL_POLYGON_OFFSET_FILL ); + pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST ); + pglEnable( GL_TEXTURE_2D ); + pglShadeModel( GL_SMOOTH ); + pglFrontFace( GL_CCW ); + + pglPointSize( 1.2f ); + pglLineWidth( 1.2f ); + + GL_Cull( GL_NONE ); +} +#endif + +/* +================= +R_RenderInfo_f +================= +*/ +void R_RenderInfo_f( void ) +{ + gEngfuncs.Con_Printf( "\n" ); + gEngfuncs.Con_Printf( "HARDWARE RENDER\n"); + gEngfuncs.Con_Printf( "MAX_TEXTURE_SIZE: %i\n", glConfig.max_texture_size ); + gEngfuncs.Con_Printf( "MODE: %ix%i\n", gpGlobals->width, gpGlobals->height ); + gEngfuncs.Con_Printf( "VERTICAL SYNC: %s\n", gl_vsync->value ? "enabled" : "disabled" ); + gEngfuncs.Con_Printf( "VRAM AVAILABLE: %i\n", vmemavail() ); +} + +void GL_InitExtensions( void ) +{ + glState.stencilEnabled = true; + glConfig.max_texture_size = 256; + + // get our various GL strings + gEngfuncs.Con_Reportf( "^3Video^7: PSP HW\n" ); + + gEngfuncs.Cvar_Get( "gl_max_size", va( "%i", glConfig.max_texture_size ), 0, "opengl texture max dims" ); +/* + gEngfuncs.Image_AddCmdFlags( IL_DDS_HARDWARE ); +*/ + R_RenderInfo_f(); + + tr.framecount = tr.visframecount = 1; + glw_state.initialized = true; +} + +void GL_ClearExtensions( void ) +{ + // now all extensions are disabled + glw_state.initialized = false; +} + +//======================================================================= + +/* +================= +GL_InitCommands +================= +*/ +void GL_InitCommands( void ) +{ + r_speeds = gEngfuncs.Cvar_Get( "r_speeds", "0", FCVAR_ARCHIVE, "shows renderer speeds" ); + r_fullbright = gEngfuncs.Cvar_Get( "r_fullbright", "0", FCVAR_CHEAT, "disable lightmaps, get fullbright for entities" ); + r_norefresh = gEngfuncs.Cvar_Get( "r_norefresh", "0", 0, "disable 3D rendering (use with caution)" ); + r_lighting_extended = gEngfuncs.Cvar_Get( "r_lighting_extended", "0", FCVAR_ARCHIVE, "allow to get lighting from world and bmodels" ); // disabled + r_lighting_modulate = gEngfuncs.Cvar_Get( "r_lighting_modulate", "0.6", FCVAR_ARCHIVE, "lightstyles modulate scale" ); + r_lighting_ambient = gEngfuncs.Cvar_Get( "r_lighting_ambient", "0.3", FCVAR_ARCHIVE, "map ambient lighting scale" ); + r_novis = gEngfuncs.Cvar_Get( "r_novis", "0", 0, "ignore vis information (perfomance test)" ); + r_nocull = gEngfuncs.Cvar_Get( "r_nocull", "0", 0, "ignore frustrum culling (perfomance test)" ); + r_detailtextures = gEngfuncs.Cvar_Get( "r_detailtextures", "0", FCVAR_ARCHIVE, "enable detail textures support, use '2' for autogenerate detail.txt" ); // disabled + r_lockpvs = gEngfuncs.Cvar_Get( "r_lockpvs", "0", FCVAR_CHEAT, "lockpvs area at current point (pvs test)" ); + r_lockfrustum = gEngfuncs.Cvar_Get( "r_lockfrustum", "0", FCVAR_CHEAT, "lock frustrum area at current point (cull test)" ); + r_dynamic = gEngfuncs.Cvar_Get( "r_dynamic", "1", FCVAR_ARCHIVE, "allow dynamic lighting (dlights, lightstyles)" ); + r_traceglow = gEngfuncs.Cvar_Get( "r_traceglow", "0", FCVAR_ARCHIVE, "cull flares behind models" ); // disabled + r_lightmap = gEngfuncs.Cvar_Get( "r_lightmap", "0", FCVAR_CHEAT, "lightmap debugging tool" ); + r_drawentities = gEngfuncs.Cvar_Get( "r_drawentities", "1", FCVAR_CHEAT, "render entities" ); + r_decals = gEngfuncs.pfnGetCvarPointer( "r_decals", 0 ); + r_showhull = gEngfuncs.pfnGetCvarPointer( "r_showhull", 0 ); + r_fast_particles = gEngfuncs.Cvar_Get( "r_fast_particles", "1", FCVAR_ARCHIVE, "use GU_POINTS for particles" ); + + gl_texture_nearest = gEngfuncs.Cvar_Get( "gl_texture_nearest", "0", FCVAR_GLCONFIG, "disable texture filter" ); + gl_lightmap_nearest = gEngfuncs.Cvar_Get( "gl_lightmap_nearest", "0", FCVAR_GLCONFIG, "disable lightmap filter" ); + gl_vsync = gEngfuncs.pfnGetCvarPointer( "gl_vsync", 0 ); + gl_detailscale = gEngfuncs.Cvar_Get( "gl_detailscale", "4.0", FCVAR_GLCONFIG, "default scale applies while auto-generate list of detail textures" ); + gl_texture_lodfunc = gEngfuncs.Cvar_Get( "gl_texture_lodfunc", "2", FCVAR_GLCONFIG, "LOD func for mipmapped textures" ); + gl_texture_lodbias = gEngfuncs.Cvar_Get( "gl_texture_lodbias", "-6.0", FCVAR_GLCONFIG, "LOD bias for mipmapped textures (perfomance|quality)" ); + gl_texture_lodslope = gEngfuncs.Cvar_Get( "gl_texture_lodslope", "0.3", FCVAR_GLCONFIG, "LOD slope for mipmapped textures" ); + gl_keeptjunctions = gEngfuncs.Cvar_Get( "gl_keeptjunctions", "1", FCVAR_GLCONFIG, "removing tjuncs causes blinking pixels" ); + gl_emboss_scale = gEngfuncs.Cvar_Get( "gl_emboss_scale", "0", FCVAR_GLCONFIG|FCVAR_LATCH, "fake bumpmapping scale" ); + gl_showtextures = gEngfuncs.pfnGetCvarPointer( "r_showtextures", 0 ); + gl_clear = gEngfuncs.pfnGetCvarPointer( "gl_clear", 0 ); + gl_test = gEngfuncs.Cvar_Get( "gl_test", "0", 0, "engine developer cvar for quick testing new features" ); + gl_wireframe = gEngfuncs.Cvar_Get( "gl_wireframe", "0", FCVAR_GLCONFIG|FCVAR_SPONLY, "show wireframe overlay" ); + gl_subdivide_size = gEngfuncs.Cvar_Get( "gl_subdivide_size", "256.0", FCVAR_GLCONFIG, "the division value for the sky brushes" ); + gl_round_down = gEngfuncs.Cvar_Get( "gl_round_down", "1", FCVAR_GLCONFIG, "round texture sizes to nearest POT value" ); + // these cvar not used by engine but some mods requires this + gl_depthoffset = gEngfuncs.Cvar_Get( "gl_depthoffset", "256.0", FCVAR_GLCONFIG, "depth offset for decals" ); + + // make sure gl_vsync is checked after vid_restart + SetBits( gl_vsync->flags, FCVAR_CHANGED ); + + vid_gamma = gEngfuncs.pfnGetCvarPointer( "gamma", 0 ); + vid_brightness = gEngfuncs.pfnGetCvarPointer( "brightness", 0 ); + + tracerred = gEngfuncs.Cvar_Get( "tracerred", "0.8", 0, "tracer red component weight ( 0 - 1.0 )" ); + tracergreen = gEngfuncs.Cvar_Get( "tracergreen", "0.8", 0, "tracer green component weight ( 0 - 1.0 )" ); + tracerblue = gEngfuncs.Cvar_Get( "tracerblue", "0.4", 0, "tracer blue component weight ( 0 - 1.0 )" ); + traceralpha = gEngfuncs.Cvar_Get( "traceralpha", "0.5", 0, "tracer alpha amount ( 0 - 1.0 )" ); + + cl_lightstyle_lerping = gEngfuncs.pfnGetCvarPointer( "cl_lightstyle_lerping", 0 ); + + gEngfuncs.Cmd_AddCommand( "r_info", R_RenderInfo_f, "display renderer info" ); + gEngfuncs.Cmd_AddCommand( "timerefresh", SCR_TimeRefresh_f, "turn quickly and print rendering statistcs" ); +} + +/* +================= +GL_RemoveCommands +================= +*/ +void GL_RemoveCommands( void ) +{ + gEngfuncs.Cmd_RemoveCommand( "r_info" ); +} + +/* +=============== +R_Init +=============== +*/ +qboolean R_Init( void ) +{ + if( glw_state.initialized ) + return true; + + if( vinit() < 0 ) + { + gEngfuncs.Host_Error( "Can't initialize video subsystem\nVRam unavailable" ); + return false; + } + + GL_InitCommands(); + GL_InitRandomTable(); + + GL_SetDefaultState(); + + // create the window and set up the context + if( !gEngfuncs.R_Init_Video( REF_GL )) // request GL context + { + GL_RemoveCommands(); + gEngfuncs.R_Free_Video(); +// Why? Host_Error again??? +// gEngfuncs.Host_Error( "Can't initialize video subsystem\nProbably driver was not installed" ); + return false; + } + + r_temppool = Mem_AllocPool( "Render Zone" ); + + GU_Init(); + R_InitImages(); + R_SpriteInit(); + R_StudioInit(); + R_AliasInit(); + R_ClearDecals(); + R_ClearScene(); + + return true; +} + +/* +=============== +R_Shutdown +=============== +*/ +void R_Shutdown( void ) +{ + if( !glw_state.initialized ) + return; + + GL_RemoveCommands(); + R_ShutdownImages(); + GU_Shutdown(); + + Mem_FreePool( &r_temppool ); + + // shut down OS specific OpenGL stuff like contexts, etc. + gEngfuncs.R_Free_Video(); +} + +void GL_SetupAttributes( int safegl ) +{ + +} diff --git a/ref_gu/gu_rlight.c b/ref_gu/gu_rlight.c new file mode 100644 index 000000000..c1820e1ca --- /dev/null +++ b/ref_gu/gu_rlight.c @@ -0,0 +1,491 @@ +/* +gl_rlight.c - dynamic and static lights +Copyright (C) 2010 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "pm_local.h" +#include "studio.h" +#include "xash3d_mathlib.h" +#include "ref_params.h" + +/* +============================================================================= + +DYNAMIC LIGHTS + +============================================================================= +*/ +/* +================== +CL_RunLightStyles + +================== +*/ +void CL_RunLightStyles( void ) +{ + int i, k, flight, clight; + float l, lerpfrac, backlerp; + float frametime = (gpGlobals->time - gpGlobals->oldtime); + float scale; + lightstyle_t *ls; + + if( !WORLDMODEL ) return; + + scale = r_lighting_modulate->value; + + // light animations + // 'm' is normal light, 'a' is no light, 'z' is double bright + for( i = 0, ls = gEngfuncs.GetLightStyle( 0 ); i < MAX_LIGHTSTYLES; i++, ls++ ) + { + if( !WORLDMODEL->lightdata ) + { + tr.lightstylevalue[i] = 256 * 256; + continue; + } + + if( !ENGINE_GET_PARM( PARAM_GAMEPAUSED ) && frametime <= 0.1f ) + ls->time += frametime; // evaluate local time + + flight = (int)Q_floor( ls->time * 10 ); + clight = (int)Q_ceil( ls->time * 10 ); + lerpfrac = ( ls->time * 10 ) - flight; + backlerp = 1.0f - lerpfrac; + + if( !ls->length ) + { + tr.lightstylevalue[i] = 256 * scale; + continue; + } + else if( ls->length == 1 ) + { + // single length style so don't bother interpolating + tr.lightstylevalue[i] = ls->map[0] * 22 * scale; + continue; + } + else if( !ls->interp || !CVAR_TO_BOOL( cl_lightstyle_lerping )) + { + tr.lightstylevalue[i] = ls->map[flight%ls->length] * 22 * scale; + continue; + } + + // interpolate animating light + // frame just gone + k = ls->map[flight % ls->length]; + l = (float)( k * 22.0f ) * backlerp; + + // upcoming frame + k = ls->map[clight % ls->length]; + l += (float)( k * 22.0f ) * lerpfrac; + + tr.lightstylevalue[i] = (int)l * scale; + } +} + +/* +============= +R_MarkLights +============= +*/ +void R_MarkLights( dlight_t *light, int bit, mnode_t *node ) +{ + float dist; + msurface_t *surf; + int i; + + if( !node || node->contents < 0 ) + return; + + dist = PlaneDiff( light->origin, node->plane ); + + if( dist > light->radius ) + { + R_MarkLights( light, bit, node->children[0] ); + return; + } + if( dist < -light->radius ) + { + R_MarkLights( light, bit, node->children[1] ); + return; + } + + // mark the polygons + surf = RI.currentmodel->surfaces + node->firstsurface; + + for( i = 0; i < node->numsurfaces; i++, surf++ ) + { + if( !BoundsAndSphereIntersect( surf->info->mins, surf->info->maxs, light->origin, light->radius )) + continue; // no intersection + + if( surf->dlightframe != tr.dlightframecount ) + { + surf->dlightbits = 0; + surf->dlightframe = tr.dlightframecount; + } + surf->dlightbits |= bit; + } + + R_MarkLights( light, bit, node->children[0] ); + R_MarkLights( light, bit, node->children[1] ); +} + +/* +============= +R_PushDlights +============= +*/ +void R_PushDlights( void ) +{ + dlight_t *l; + int i; + + tr.dlightframecount = tr.framecount; + + RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); + RI.currentmodel = RI.currententity->model; + + for( i = 0, l = gEngfuncs.GetDynamicLight( 0 ); i < MAX_DLIGHTS; i++, l++ ) + { + if( l->die < gpGlobals->time || !l->radius ) + continue; + + if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) + continue; + + R_MarkLights( l, 1<nodes ); + } +} + +/* +============= +R_CountDlights +============= +*/ +int R_CountDlights( void ) +{ + dlight_t *l; + int i, numDlights = 0; + + for( i = 0, l = gEngfuncs.GetDynamicLight( 0 ); i < MAX_DLIGHTS; i++, l++ ) + { + if( l->die < gpGlobals->time || !l->radius ) + continue; + + numDlights++; + } + + return numDlights; +} + +/* +============= +R_CountSurfaceDlights +============= +*/ +int R_CountSurfaceDlights( msurface_t *surf ) +{ + int i, numDlights = 0; + + for( i = 0; i < MAX_DLIGHTS; i++ ) + { + if(!( surf->dlightbits & BIT( i ))) + continue; // not lit by this light + + numDlights++; + } + + return numDlights; +} + +/* +======================================================================= + + AMBIENT LIGHTING + +======================================================================= +*/ +static vec3_t g_trace_lightspot; +static vec3_t g_trace_lightvec; +static float g_trace_fraction; + +/* +================= +R_RecursiveLightPoint +================= +*/ +static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, float p2f, colorVec *cv, const vec3_t start, const vec3_t end ) +{ + float front, back, frac, midf; + int i, map, side, size; + float ds, dt, s, t; + int sample_size; + color24 *lm, *dm; + mextrasurf_t *info; + msurface_t *surf; + mtexinfo_t *tex; + matrix3x4 tbn; + vec3_t mid; + + // didn't hit anything + if( !node || node->contents < 0 ) + { + cv->r = cv->g = cv->b = cv->a = 0; + return false; + } + + // calculate mid point + front = PlaneDiff( start, node->plane ); + back = PlaneDiff( end, node->plane ); + + side = front < 0; + if(( back < 0 ) == side ) + return R_RecursiveLightPoint( model, node->children[side], p1f, p2f, cv, start, end ); + + frac = front / ( front - back ); + + VectorLerp( start, frac, end, mid ); + midf = p1f + ( p2f - p1f ) * frac; + + // co down front side + if( R_RecursiveLightPoint( model, node->children[side], p1f, midf, cv, start, mid )) + return true; // hit something + + if(( back < 0 ) == side ) + { + cv->r = cv->g = cv->b = cv->a = 0; + return false; // didn't hit anything + } + + // check for impact on this node + surf = model->surfaces + node->firstsurface; + VectorCopy( mid, g_trace_lightspot ); + + for( i = 0; i < node->numsurfaces; i++, surf++ ) + { + int smax, tmax; + + tex = surf->texinfo; + info = surf->info; + + if( FBitSet( surf->flags, SURF_DRAWTILED )) + continue; // no lightmaps + + s = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3]; + t = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3]; + + if( s < info->lightmapmins[0] || t < info->lightmapmins[1] ) + continue; + + ds = s - info->lightmapmins[0]; + dt = t - info->lightmapmins[1]; + + if ( ds > info->lightextents[0] || dt > info->lightextents[1] ) + continue; + + cv->r = cv->g = cv->b = cv->a = 0; + + if( !surf->samples ) + return true; + + sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); + smax = (info->lightextents[0] / sample_size) + 1; + tmax = (info->lightextents[1] / sample_size) + 1; + ds /= sample_size; + dt /= sample_size; + + lm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds ); + g_trace_fraction = midf; + size = smax * tmax; + dm = NULL; + + if( surf->info->deluxemap ) + { + vec3_t faceNormal; + + if( FBitSet( surf->flags, SURF_PLANEBACK )) + VectorNegate( surf->plane->normal, faceNormal ); + else VectorCopy( surf->plane->normal, faceNormal ); + + // compute face TBN +#if 1 + Vector4Set( tbn[0], surf->info->lmvecs[0][0], surf->info->lmvecs[0][1], surf->info->lmvecs[0][2], 0.0f ); + Vector4Set( tbn[1], -surf->info->lmvecs[1][0], -surf->info->lmvecs[1][1], -surf->info->lmvecs[1][2], 0.0f ); + Vector4Set( tbn[2], faceNormal[0], faceNormal[1], faceNormal[2], 0.0f ); +#else + Vector4Set( tbn[0], surf->info->lmvecs[0][0], -surf->info->lmvecs[1][0], faceNormal[0], 0.0f ); + Vector4Set( tbn[1], surf->info->lmvecs[0][1], -surf->info->lmvecs[1][1], faceNormal[1], 0.0f ); + Vector4Set( tbn[2], surf->info->lmvecs[0][2], -surf->info->lmvecs[1][2], faceNormal[2], 0.0f ); +#endif + VectorNormalize( tbn[0] ); + VectorNormalize( tbn[1] ); + VectorNormalize( tbn[2] ); + dm = surf->info->deluxemap + Q_rint( dt ) * smax + Q_rint( ds ); + } + + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + { + uint scale = tr.lightstylevalue[surf->styles[map]]; + + if( tr.ignore_lightgamma ) + { + cv->r += lm->r * scale; + cv->g += lm->g * scale; + cv->b += lm->b * scale; + } + else + { + cv->r += gEngfuncs.LightToTexGamma( lm->r ) * scale; + cv->g += gEngfuncs.LightToTexGamma( lm->g ) * scale; + cv->b += gEngfuncs.LightToTexGamma( lm->b ) * scale; + } + lm += size; // skip to next lightmap + + if( dm != NULL ) + { + vec3_t srcNormal, lightNormal; + float f = (1.0f / 128.0f); + + VectorSet( srcNormal, ((float)dm->r - 128.0f) * f, ((float)dm->g - 128.0f) * f, ((float)dm->b - 128.0f) * f ); + Matrix3x4_VectorIRotate( tbn, srcNormal, lightNormal ); // turn to world space + VectorScale( lightNormal, (float)scale * -1.0f, lightNormal ); // turn direction from light + VectorAdd( g_trace_lightvec, lightNormal, g_trace_lightvec ); + dm += size; // skip to next deluxmap + } + } + + return true; + } + + // go down back side + return R_RecursiveLightPoint( model, node->children[!side], midf, p2f, cv, mid, end ); +} + +/* +================= +R_LightVec + +check bspmodels to get light from +================= +*/ +colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec ) +{ + float last_fraction; + int i, maxEnts = 1; + colorVec light, cv; + + if( lspot ) VectorClear( lspot ); + if( lvec ) VectorClear( lvec ); + + if( WORLDMODEL && WORLDMODEL->lightdata ) + { + light.r = light.g = light.b = light.a = 0; + last_fraction = 1.0f; + + // get light from bmodels too + if( CVAR_TO_BOOL( r_lighting_extended )) + maxEnts = MAX_PHYSENTS; + + // check all the bsp-models + for( i = 0; i < maxEnts; i++ ) + { + physent_t *pe = gEngfuncs.EV_GetPhysent( i ); + vec3_t offset, start_l, end_l; + mnode_t *pnodes; + matrix4x4 matrix; + + if( !pe ) + break; + + if( !pe->model || pe->model->type != mod_brush ) + continue; // skip non-bsp models + + pnodes = &pe->model->nodes[pe->model->hulls[0].firstclipnode]; + VectorSubtract( pe->model->hulls[0].clip_mins, vec3_origin, offset ); + VectorAdd( offset, pe->origin, offset ); + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + + // rotate start and end into the models frame of reference + if( !VectorIsNull( pe->angles )) + { + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + } + + VectorClear( g_trace_lightspot ); + VectorClear( g_trace_lightvec ); + g_trace_fraction = 1.0f; + + if( !R_RecursiveLightPoint( pe->model, pnodes, 0.0f, 1.0f, &cv, start_l, end_l )) + continue; // didn't hit anything + + if( g_trace_fraction < last_fraction ) + { + if( lspot ) VectorCopy( g_trace_lightspot, lspot ); + if( lvec ) VectorNormalize2( g_trace_lightvec, lvec ); + light.r = Q_min(( cv.r >> 7 ), 255 ); + light.g = Q_min(( cv.g >> 7 ), 255 ); + light.b = Q_min(( cv.b >> 7 ), 255 ); + last_fraction = g_trace_fraction; + + if(( light.r + light.g + light.b ) != 0 ) + break; // we get light now + } + } + } + else + { + light.r = light.g = light.b = 255; + light.a = 0; + } + + return light; +} + +/* +================= +R_LightVec + +check bspmodels to get light from +================= +*/ +colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec ) +{ + colorVec light = R_LightVecInternal( start, end, lspot, lvec ); + + if( CVAR_TO_BOOL( r_lighting_extended ) && lspot != NULL && lvec != NULL ) + { + // trying to get light from ceiling (but ignore gradient analyze) + if(( light.r + light.g + light.b ) == 0 ) + return R_LightVecInternal( end, start, lspot, lvec ); + } + + return light; +} + +/* +================= +R_LightPoint + +light from floor +================= +*/ +colorVec R_LightPoint( const vec3_t p0 ) +{ + vec3_t p1; + + VectorSet( p1, p0[0], p0[1], p0[2] - 2048.0f ); + + return R_LightVec( p0, p1, NULL, NULL ); +} diff --git a/ref_gu/gu_rmain.c b/ref_gu/gu_rmain.c new file mode 100644 index 000000000..ea74802ff --- /dev/null +++ b/ref_gu/gu_rmain.c @@ -0,0 +1,1345 @@ +/* +gu_rmain.c - renderer main loop +Copyright (C) 2010 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "xash3d_mathlib.h" +#include "library.h" +#include "beamdef.h" +#include "particledef.h" +#include "entity_types.h" + +#define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA ) +float gldepthmin, gldepthmax; + +ref_instance_t RI; + +static int R_RankForRenderMode( int rendermode ) +{ + switch( rendermode ) + { + case kRenderTransTexture: + return 1; // draw second + case kRenderTransAdd: + return 2; // draw third + case kRenderGlow: + return 3; // must be last! + } + return 0; +} + +void R_AllowFog( qboolean allowed ) +{ +#if 1 + if( allowed ) + { + if( glState.isFogEnabled ) + sceGuEnable( GU_FOG ); + } + else + { + if( glState.isFogEnabled ) + sceGuDisable( GU_FOG ); + } +#else + if( allowed ) + { + if( glState.isFogEnabled ) + pglEnable( GL_FOG ); + } + else + { + if( glState.isFogEnabled ) + pglDisable( GL_FOG ); + } +#endif +} + +/* +=============== +R_OpaqueEntity + +Opaque entity can be brush or studio model but sprite +=============== +*/ +static qboolean R_OpaqueEntity( cl_entity_t *ent ) +{ + if( R_GetEntityRenderMode( ent ) == kRenderNormal ) + return true; + return false; +} + +/* +=============== +R_TransEntityCompare + +Sorting translucent entities by rendermode then by distance +=============== +*/ +static int R_TransEntityCompare( const void *a, const void *b ) +{ + cl_entity_t *ent1, *ent2; + vec3_t vecLen, org; + float dist1, dist2; + int rendermode1; + int rendermode2; + + ent1 = *(cl_entity_t **)a; + ent2 = *(cl_entity_t **)b; + rendermode1 = R_GetEntityRenderMode( ent1 ); + rendermode2 = R_GetEntityRenderMode( ent2 ); + + // sort by distance + if( ent1->model->type != mod_brush || rendermode1 != kRenderTransAlpha ) + { + VectorAverage( ent1->model->mins, ent1->model->maxs, org ); + VectorAdd( ent1->origin, org, org ); + VectorSubtract( RI.vieworg, org, vecLen ); + dist1 = DotProduct( vecLen, vecLen ); + } + else dist1 = 1000000000; + + if( ent2->model->type != mod_brush || rendermode2 != kRenderTransAlpha ) + { + VectorAverage( ent2->model->mins, ent2->model->maxs, org ); + VectorAdd( ent2->origin, org, org ); + VectorSubtract( RI.vieworg, org, vecLen ); + dist2 = DotProduct( vecLen, vecLen ); + } + else dist2 = 1000000000; + + if( dist1 > dist2 ) + return -1; + if( dist1 < dist2 ) + return 1; + + // then sort by rendermode + if( R_RankForRenderMode( rendermode1 ) > R_RankForRenderMode( rendermode2 )) + return 1; + if( R_RankForRenderMode( rendermode1 ) < R_RankForRenderMode( rendermode2 )) + return -1; + + return 0; +} + +/* +=============== +R_WorldToScreen + +Convert a given point from world into screen space +Returns true if we behind to screen +=============== +*/ +int R_WorldToScreen( const vec3_t point, vec3_t screen ) +{ + matrix4x4 worldToScreen; + qboolean behind; + float w; + + if( !point || !screen ) + return true; + + Matrix4x4_Copy( worldToScreen, RI.worldviewProjectionMatrix ); + screen[0] = worldToScreen[0][0] * point[0] + worldToScreen[0][1] * point[1] + worldToScreen[0][2] * point[2] + worldToScreen[0][3]; + screen[1] = worldToScreen[1][0] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[1][2] * point[2] + worldToScreen[1][3]; + w = worldToScreen[3][0] * point[0] + worldToScreen[3][1] * point[1] + worldToScreen[3][2] * point[2] + worldToScreen[3][3]; + screen[2] = 0.0f; // just so we have something valid here + + if( w < 0.001f ) + { + screen[0] *= 100000; + screen[1] *= 100000; + behind = true; + } + else + { + float invw = 1.0f / w; + screen[0] *= invw; + screen[1] *= invw; + behind = false; + } + + return behind; +} + +/* +=============== +R_ScreenToWorld + +Convert a given point from screen into world space +=============== +*/ +void R_ScreenToWorld( const vec3_t screen, vec3_t point ) +{ + matrix4x4 screenToWorld; + float w; + + if( !point || !screen ) + return; + + Matrix4x4_Invert_Full( screenToWorld, RI.worldviewProjectionMatrix ); + + point[0] = screen[0] * screenToWorld[0][0] + screen[1] * screenToWorld[0][1] + screen[2] * screenToWorld[0][2] + screenToWorld[0][3]; + point[1] = screen[0] * screenToWorld[1][0] + screen[1] * screenToWorld[1][1] + screen[2] * screenToWorld[1][2] + screenToWorld[1][3]; + point[2] = screen[0] * screenToWorld[2][0] + screen[1] * screenToWorld[2][1] + screen[2] * screenToWorld[2][2] + screenToWorld[2][3]; + w = screen[0] * screenToWorld[3][0] + screen[1] * screenToWorld[3][1] + screen[2] * screenToWorld[3][2] + screenToWorld[3][3]; + if( w != 0.0f ) VectorScale( point, ( 1.0f / w ), point ); +} + +/* +=============== +R_PushScene +=============== +*/ +void R_PushScene( void ) +{ + if( ++tr.draw_stack_pos >= MAX_DRAW_STACK ) + gEngfuncs.Host_Error( "draw stack overflow\n" ); + + tr.draw_list = &tr.draw_stack[tr.draw_stack_pos]; +} + +/* +=============== +R_PopScene +=============== +*/ +void R_PopScene( void ) +{ + if( --tr.draw_stack_pos < 0 ) + gEngfuncs.Host_Error( "draw stack underflow\n" ); + tr.draw_list = &tr.draw_stack[tr.draw_stack_pos]; +} + +/* +=============== +R_ClearScene +=============== +*/ +void R_ClearScene( void ) +{ + tr.draw_list->num_solid_entities = 0; + tr.draw_list->num_trans_entities = 0; + tr.draw_list->num_beam_entities = 0; + + // clear the scene befor start new frame + if( gEngfuncs.drawFuncs->R_ClearScene != NULL ) + gEngfuncs.drawFuncs->R_ClearScene(); + +} + +/* +=============== +R_AddEntity +=============== +*/ +qboolean R_AddEntity( struct cl_entity_s *clent, int type ) +{ + if( !r_drawentities->value ) + return false; // not allow to drawing + + if( !clent || !clent->model ) + return false; // if set to invisible, skip + + if( FBitSet( clent->curstate.effects, EF_NODRAW )) + return false; // done + + if( !R_ModelOpaque( clent->curstate.rendermode ) && CL_FxBlend( clent ) <= 0 ) + return true; // invisible + + switch( type ) + { + case ET_FRAGMENTED: + r_stats.c_client_ents++; + break; + case ET_TEMPENTITY: + r_stats.c_active_tents_count++; + break; + default: break; + } + + if( R_OpaqueEntity( clent )) + { + // opaque + if( tr.draw_list->num_solid_entities >= MAX_VISIBLE_PACKET ) + return false; + + tr.draw_list->solid_entities[tr.draw_list->num_solid_entities] = clent; + tr.draw_list->num_solid_entities++; + } + else + { + // translucent + if( tr.draw_list->num_trans_entities >= MAX_VISIBLE_PACKET ) + return false; + + tr.draw_list->trans_entities[tr.draw_list->num_trans_entities] = clent; + tr.draw_list->num_trans_entities++; + } + + return true; +} + +/* +============= +R_Clear +============= +*/ +static void R_Clear( int bitMask ) +{ + int bits; + + if( ENGINE_GET_PARM( PARM_DEV_OVERVIEW )) + sceGuClearColor( 0xff00ff00 ); // green background (Valve rules) + else sceGuClearColor( 0xff7f7f7f ); + + bits = GU_DEPTH_BUFFER_BIT; + + if( glState.stencilEnabled ) + bits |= GU_STENCIL_BUFFER_BIT; + + bits &= bitMask; + + sceGuClear( bits ); + + // change ordering for overview + if( RI.drawOrtho ) + { + gldepthmin = 65535.0f; + gldepthmax = 0.0f; + } + else + { + gldepthmin = 0.0f; + gldepthmax = 65535.0f; + } + + sceGuDepthFunc( GU_LEQUAL ); + sceGuDepthRange( ( int )gldepthmin, ( int )gldepthmax ); +} + +//============================================================================= +/* +=============== +R_GetFarClip +=============== +*/ +static float R_GetFarClip( void ) +{ + if( WORLDMODEL && RI.drawWorld ) + return MOVEVARS->zmax * 1.73f; + return 2048.0f; +} + +/* +=============== +R_SetupFrustum +=============== +*/ +void R_SetupFrustum( void ) +{ + const ref_overview_t *ov = gEngfuncs.GetOverviewParms(); + + if( RP_NORMALPASS() && ( ENGINE_GET_PARM( PARM_WATER_LEVEL ) >= 3 )) + { + RI.fov_x = atan( tan( DEG2RAD( RI.fov_x ) / 2 ) * ( 0.97f + sin( gpGlobals->time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f); + RI.fov_y = atan( tan( DEG2RAD( RI.fov_y ) / 2 ) * ( 1.03f - sin( gpGlobals->time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f); + } + + // build the transformation matrix for the given view angles + AngleVectors( RI.viewangles, RI.vforward, RI.vright, RI.vup ); + + if( !r_lockfrustum->value ) + { + VectorCopy( RI.vieworg, RI.cullorigin ); + VectorCopy( RI.vforward, RI.cull_vforward ); + VectorCopy( RI.vright, RI.cull_vright ); + VectorCopy( RI.vup, RI.cull_vup ); + } + + if( RI.drawOrtho ) + GL_FrustumInitOrtho( &RI.frustum, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar ); + else GL_FrustumInitProj( &RI.frustum, 0.0f, R_GetFarClip(), RI.fov_x, RI.fov_y ); // NOTE: we ignore nearplane here (mirrors only) +} + +/* +============= +R_SetupProjectionMatrix +============= +*/ +static void R_SetupProjectionMatrix( matrix4x4 m ) +{ + GLfloat xMin, xMax, yMin, yMax, zNear, zFar; + + if( RI.drawOrtho ) + { + const ref_overview_t *ov = gEngfuncs.GetOverviewParms(); + Matrix4x4_CreateOrtho( m, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar ); + return; + } + + RI.farClip = R_GetFarClip(); + + zNear = 4.0f; + zFar = max( 256.0f, RI.farClip ); + + yMax = zNear * tan( RI.fov_y * M_PI_F / 360.0f ); + yMin = -yMax; + + xMax = zNear * tan( RI.fov_x * M_PI_F / 360.0f ); + xMin = -xMax; + + Matrix4x4_CreateProjection( m, xMax, xMin, yMax, yMin, zNear, zFar ); +} + +/* +============= +R_SetupModelviewMatrix +============= +*/ +static void R_SetupModelviewMatrix( matrix4x4 m ) +{ + Matrix4x4_CreateModelview( m ); + Matrix4x4_ConcatRotate( m, -RI.viewangles[2], 1, 0, 0 ); + Matrix4x4_ConcatRotate( m, -RI.viewangles[0], 0, 1, 0 ); + Matrix4x4_ConcatRotate( m, -RI.viewangles[1], 0, 0, 1 ); + Matrix4x4_ConcatTranslate( m, -RI.vieworg[0], -RI.vieworg[1], -RI.vieworg[2] ); +} + +/* +============= +R_LoadIdentity +============= +*/ +void R_LoadIdentity( void ) +{ + if( tr.modelviewIdentity ) + return; + + Matrix4x4_LoadIdentity( RI.objectMatrix ); +#if 1 + sceGumMatrixMode( GU_MODEL ); + GL_LoadMatrix( RI.objectMatrix ); + sceGumUpdateMatrix(); +#else + Matrix4x4_Copy( RI.modelviewMatrix, RI.worldviewMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.modelviewMatrix ); +#endif + tr.modelviewIdentity = true; +} + +/* +============= +R_RotateForEntity +============= +*/ +void R_RotateForEntity( cl_entity_t *e ) +{ + float scale = 1.0f; + + if( e == gEngfuncs.GetEntityByIndex( 0 ) ) + { + R_LoadIdentity(); + return; + } + + if( e->model->type != mod_brush && e->curstate.scale > 0.0f ) + scale = e->curstate.scale; + + Matrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, scale ); +#if 1 + sceGumMatrixMode( GU_MODEL ); + GL_LoadMatrix( RI.objectMatrix ); + sceGumUpdateMatrix(); +#else + Matrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.modelviewMatrix ); +#endif + tr.modelviewIdentity = false; +} + +/* +============= +R_TranslateForEntity +============= +*/ +void R_TranslateForEntity( cl_entity_t *e ) +{ + float scale = 1.0f; + + if( e == gEngfuncs.GetEntityByIndex( 0 ) ) + { + printf("e == gEngfuncs.GetEntityByIndex\n"); + R_LoadIdentity(); + return; + } + + if( e->model->type != mod_brush && e->curstate.scale > 0.0f ) + scale = e->curstate.scale; + + Matrix4x4_CreateFromEntity( RI.objectMatrix, vec3_origin, e->origin, scale ); +#if 1 + sceGumMatrixMode( GU_MODEL ); + GL_LoadMatrix( RI.objectMatrix ); + sceGumUpdateMatrix(); +#else + Matrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.modelviewMatrix ); +#endif + tr.modelviewIdentity = false; +} + +/* +=============== +R_FindViewLeaf +=============== +*/ +void R_FindViewLeaf( void ) +{ + RI.oldviewleaf = RI.viewleaf; + RI.viewleaf = gEngfuncs.Mod_PointInLeaf( RI.pvsorigin, WORLDMODEL->nodes ); +} + +/* +=============== +R_SetupFrame +=============== +*/ +static void R_SetupFrame( void ) +{ + // setup viewplane dist + RI.viewplanedist = DotProduct( RI.vieworg, RI.vforward ); + + // NOTE: this request is the fps-killer on some NVidia drivers +#if 1 + glState.isFogEnabled = sceGuGetStatus( GU_FOG ); +#else + glState.isFogEnabled = pglIsEnabled( GL_FOG ); +#endif + +#if 0 + // sort translucents entities by rendermode and distance + qsort( tr.draw_list->trans_entities, tr.draw_list->num_trans_entities, sizeof( cl_entity_t* ), R_TransEntityCompare ); +#endif + + // current viewleaf + if( RI.drawWorld ) + { + RI.isSkyVisible = false; // unknown at this moment + R_FindViewLeaf(); + } +} + +/* +============= +R_SetupGL +============= +*/ +void R_SetupGL( qboolean set_gl_state ) +{ + R_SetupModelviewMatrix( RI.worldviewMatrix ); + R_SetupProjectionMatrix( RI.projectionMatrix ); + + Matrix4x4_Concat( RI.worldviewProjectionMatrix, RI.projectionMatrix, RI.worldviewMatrix ); + + if( !set_gl_state ) return; + + if( RP_NORMALPASS( )) + { + int x, x2, y, y2, w, h; + // set up viewport (main, playersetup) +#if 1 + x = RI.viewport[0]; + x2 = RI.viewport[0] + RI.viewport[2]; + y = gpGlobals->height - RI.viewport[1]; + y2 = gpGlobals->height - ( RI.viewport[1] + RI.viewport[3] ); +#else + x = floor( RI.viewport[0] * gpGlobals->width / gpGlobals->width ); + x2 = ceil(( RI.viewport[0] + RI.viewport[2] ) * gpGlobals->width / gpGlobals->width ); + y = floor( gpGlobals->height - RI.viewport[1] * gpGlobals->height / gpGlobals->height ); + y2 = ceil( gpGlobals->height - ( RI.viewport[1] + RI.viewport[3] ) * gpGlobals->height / gpGlobals->height ); +#endif + w = x2 - x; + h = y - y2; + + sceGuViewport( 2048 /*- ( gpGlobals->width >> 1 ) + x + ( w >> 1 )*/, + 2048 + ( gpGlobals->height >> 1 ) - y2 - ( h >> 1 ), + w, h ); + sceGuScissor( x, gpGlobals->height - y2 - h, x + w, gpGlobals->height - y2 ); + } + else + { + // envpass, mirrorpass + sceGuViewport(2048 - ( gpGlobals->width >> 1 ) + RI.viewport[0] + ( RI.viewport[2] >> 1 ), + 2048 + ( gpGlobals->height >> 1 ) - RI.viewport[1] - ( RI.viewport[3] >> 1 ), + RI.viewport[2], RI.viewport[3] ); + sceGuScissor( RI.viewport[0], gpGlobals->height - RI.viewport[1] - RI.viewport[3], + RI.viewport[0] + RI.viewport[2], gpGlobals->height - RI.viewport[1] ); + } + + sceGumMatrixMode( GU_PROJECTION ); + GL_LoadMatrix( RI.projectionMatrix ); + + sceGumMatrixMode( GU_VIEW ); + GL_LoadMatrix( RI.worldviewMatrix ); + + sceGumUpdateMatrix(); //Apply a matrix transformation + sceGumMatrixMode( GU_MODEL ); + +/* not supported + if( FBitSet( RI.params, RP_CLIPPLANE )) + { + GLdouble clip[4]; + mplane_t *p = &RI.clipPlane; + + clip[0] = p->normal[0]; + clip[1] = p->normal[1]; + clip[2] = p->normal[2]; + clip[3] = -p->dist; + + pglClipPlane( GL_CLIP_PLANE0, clip ); + pglEnable( GL_CLIP_PLANE0 ); + } +*/ + GL_Cull( GL_FRONT ); + + sceGuDisable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuColor( GUCOLOR4F( 1.0f, 1.0f, 1.0f, 1.0f ) ); + + GU_ClipSetWorldFrustum( RI.worldviewProjectionMatrix ); + +} + +/* +============= +R_EndGL +============= +*/ +static void R_EndGL( void ) +{ +#if 0 + if( RI.params & RP_CLIPPLANE ) + pglDisable( GL_CLIP_PLANE0 ); +#endif +} + +/* +============= +R_RecursiveFindWaterTexture + +using to find source waterleaf with +watertexture to grab fog values from it +============= +*/ +static gl_texture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down ) +{ + gl_texture_t *tex = NULL; + + // assure the initial node is not null + // we could check it here, but we would rather check it + // outside the call to get rid of one additional recursion level + Assert( node != NULL ); + + // ignore solid nodes + if( node->contents == CONTENTS_SOLID ) + return NULL; + + if( node->contents < 0 ) + { + mleaf_t *pleaf; + msurface_t **mark; + int i, c; + + // ignore non-liquid leaves + if( node->contents != CONTENTS_WATER && node->contents != CONTENTS_LAVA && node->contents != CONTENTS_SLIME ) + return NULL; + + // find texture + pleaf = (mleaf_t *)node; + mark = pleaf->firstmarksurface; + c = pleaf->nummarksurfaces; + + for( i = 0; i < c; i++, mark++ ) + { + if( (*mark)->flags & SURF_DRAWTURB && (*mark)->texinfo && (*mark)->texinfo->texture ) + return R_GetTexture( (*mark)->texinfo->texture->gl_texturenum ); + } + + // texture not found + return NULL; + } + + // this is a regular node + // traverse children + if( node->children[0] && ( node->children[0] != ignore )) + { + tex = R_RecursiveFindWaterTexture( node->children[0], node, true ); + if( tex ) return tex; + } + + if( node->children[1] && ( node->children[1] != ignore )) + { + tex = R_RecursiveFindWaterTexture( node->children[1], node, true ); + if( tex ) return tex; + } + + // for down recursion, return immediately + if( down ) return NULL; + + // texture not found, step up if any + if( node->parent ) + return R_RecursiveFindWaterTexture( node->parent, node, false ); + + // top-level node, bail out + return NULL; +} + +/* +============= +R_CheckFog + +check for underwater fog +Using backward recursion to find waterline leaf +from underwater leaf (idea: XaeroX) +============= +*/ +static void R_CheckFog( void ) +{ + cl_entity_t *ent; + gl_texture_t *tex; + int i, cnt, count; + + // quake global fog + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) + { + if( !MOVEVARS->fog_settings ) + { +#if 1 + if( sceGuGetStatus( GU_FOG ) ) + sceGuDisable( GU_FOG ); +#else + if( pglIsEnabled( GL_FOG ) ) + pglDisable( GL_FOG ); +#endif + RI.fogEnabled = false; + return; + } + + // quake-style global fog + RI.fogColor[0] = ((MOVEVARS->fog_settings & 0xFF000000) >> 24) / 255.0f; + RI.fogColor[1] = ((MOVEVARS->fog_settings & 0xFF0000) >> 16) / 255.0f; + RI.fogColor[2] = ((MOVEVARS->fog_settings & 0xFF00) >> 8) / 255.0f; + RI.fogDensity = ((MOVEVARS->fog_settings & 0xFF) / 255.0f) * 0.01f; + RI.fogStart = RI.fogEnd = 0.0f; + RI.fogColor[3] = 1.0f; + RI.fogCustom = false; + RI.fogEnabled = true; + RI.fogSkybox = true; + return; + } + + RI.fogEnabled = false; + + if( RI.onlyClientDraw || ENGINE_GET_PARM( PARM_WATER_LEVEL ) < 3 || !RI.drawWorld || !RI.viewleaf ) + { + if( RI.cached_waterlevel == 3 ) + { + // in some cases waterlevel jumps from 3 to 1. Catch it + RI.cached_waterlevel = ENGINE_GET_PARM( PARM_WATER_LEVEL ); + RI.cached_contents = CONTENTS_EMPTY; + if( !RI.fogCustom ) + { + glState.isFogEnabled = false; +#if 1 + sceGuDisable( GU_FOG ); +#else + pglDisable( GL_FOG ); +#endif + } + } + return; + } + + ent = gEngfuncs.CL_GetWaterEntity( RI.vieworg ); + if( ent && ent->model && ent->model->type == mod_brush && ent->curstate.skin < 0 ) + cnt = ent->curstate.skin; + else cnt = RI.viewleaf->contents; + + RI.cached_waterlevel = ENGINE_GET_PARM( PARM_WATER_LEVEL ); + + if( !IsLiquidContents( RI.cached_contents ) && IsLiquidContents( cnt )) + { + tex = NULL; + + // check for water texture + if( ent && ent->model && ent->model->type == mod_brush ) + { + msurface_t *surf; + + count = ent->model->nummodelsurfaces; + + for( i = 0, surf = &ent->model->surfaces[ent->model->firstmodelsurface]; i < count; i++, surf++ ) + { + if( surf->flags & SURF_DRAWTURB && surf->texinfo && surf->texinfo->texture ) + { + tex = R_GetTexture( surf->texinfo->texture->gl_texturenum ); + RI.cached_contents = ent->curstate.skin; + break; + } + } + } + else + { + tex = R_RecursiveFindWaterTexture( RI.viewleaf->parent, NULL, false ); + if( tex ) RI.cached_contents = RI.viewleaf->contents; + } + + if( !tex ) return; // no valid fogs + + // copy fog params + RI.fogColor[0] = tex->fogParams[0] / 255.0f; + RI.fogColor[1] = tex->fogParams[1] / 255.0f; + RI.fogColor[2] = tex->fogParams[2] / 255.0f; + RI.fogDensity = tex->fogParams[3] * 0.000025f; + RI.fogStart = RI.fogEnd = 0.0f; + RI.fogColor[3] = 1.0f; + RI.fogCustom = false; + RI.fogEnabled = true; + RI.fogSkybox = true; + } + else + { + RI.fogCustom = false; + RI.fogEnabled = true; + RI.fogSkybox = true; + } +} + +/* +============= +R_CheckGLFog + +special condition for Spirit 1.9 +that used direct calls of glFog-functions +============= +*/ +static void R_CheckGLFog( void ) +{ +#ifdef HACKS_RELATED_HLMODS + if(( !RI.fogEnabled && !RI.fogCustom ) && pglIsEnabled( GL_FOG ) && VectorIsNull( RI.fogColor )) + { + // fill the fog color from GL-state machine +#if 0 + pglGetFloatv( GL_FOG_COLOR, RI.fogColor ); +#endif + RI.fogSkybox = true; + } +#endif +} + +/* +============= +R_DrawFog + +============= +*/ +void R_DrawFog( void ) +{ + if( !RI.fogEnabled ) return; +#if 1 + sceGuEnable( GU_FOG ); + glState.fogDensity = RI.fogDensity; + glState.fogColor = GUCOLOR4F( RI.fogColor[0], RI.fogColor[1], RI.fogColor[2], glState.fogDensity ); + sceGuFog( glState.fogStart, glState.fogEnd, glState.fogColor ); +#else + pglEnable( GL_FOG ); + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) + pglFogi( GL_FOG_MODE, GL_EXP2 ); + else pglFogi( GL_FOG_MODE, GL_EXP ); + pglFogf( GL_FOG_DENSITY, RI.fogDensity ); + pglFogfv( GL_FOG_COLOR, RI.fogColor ); + pglHint( GL_FOG_HINT, GL_NICEST ); +#endif +} + +/* +============= +R_DrawEntitiesOnList +============= +*/ +void R_DrawEntitiesOnList( void ) +{ + int i; + + tr.blend = 1.0f; + + // first draw solid entities + for( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ ) + { + RI.currententity = tr.draw_list->solid_entities[i]; + RI.currentmodel = RI.currententity->model; + + Assert( RI.currententity != NULL ); + Assert( RI.currentmodel != NULL ); + + switch( RI.currentmodel->type ) + { + case mod_brush: + R_DrawBrushModel( RI.currententity ); + break; + case mod_alias: + R_DrawAliasModel( RI.currententity ); + break; + case mod_studio: + R_DrawStudioModel( RI.currententity ); + break; + default: + break; + } + } + + // quake-specific feature + R_DrawAlphaTextureChains(); + + // draw sprites seperately, because of alpha blending + for( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ ) + { + RI.currententity = tr.draw_list->solid_entities[i]; + RI.currentmodel = RI.currententity->model; + + Assert( RI.currententity != NULL ); + Assert( RI.currentmodel != NULL ); + + switch( RI.currentmodel->type ) + { + case mod_sprite: + R_DrawSpriteModel( RI.currententity ); + break; + } + } + + if( !RI.onlyClientDraw ) + { + gEngfuncs.CL_DrawEFX( tr.frametime, false ); + } + + if( RI.drawWorld ) + gEngfuncs.pfnDrawNormalTriangles(); + + // then draw translucent entities + for( i = 0; i < tr.draw_list->num_trans_entities && !RI.onlyClientDraw; i++ ) + { + RI.currententity = tr.draw_list->trans_entities[i]; + RI.currentmodel = RI.currententity->model; + + // handle studiomodels with custom rendermodes on texture + if( RI.currententity->curstate.rendermode != kRenderNormal ) + tr.blend = CL_FxBlend( RI.currententity ) / 255.0f; + else tr.blend = 1.0f; // draw as solid but sorted by distance + + if( tr.blend <= 0.0f ) continue; + + Assert( RI.currententity != NULL ); + Assert( RI.currentmodel != NULL ); + + switch( RI.currentmodel->type ) + { + case mod_brush: + R_DrawBrushModel( RI.currententity ); + break; + case mod_alias: + R_DrawAliasModel( RI.currententity ); + break; + case mod_studio: + R_DrawStudioModel( RI.currententity ); + break; + case mod_sprite: + R_DrawSpriteModel( RI.currententity ); + break; + default: + break; + } + } + + if( RI.drawWorld ) + { + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + gEngfuncs.pfnDrawTransparentTriangles (); + } + + if( !RI.onlyClientDraw ) + { + R_AllowFog( false ); + gEngfuncs.CL_DrawEFX( tr.frametime, true ); + R_AllowFog( true ); + } + + sceGuDisable( GU_BLEND ); // Trinity Render issues + + if( !RI.onlyClientDraw ) + R_DrawViewModel(); + gEngfuncs.CL_ExtraUpdate(); +} + +/* +================ +R_RenderScene + +R_SetupRefParams must be called right before +================ +*/ +void R_RenderScene( void ) +{ + if( !WORLDMODEL && RI.drawWorld ) + gEngfuncs.Host_Error( "R_RenderView: NULL worldmodel\n" ); + + // frametime is valid only for normal pass + if( RP_NORMALPASS( )) + tr.frametime = gpGlobals->time - gpGlobals->oldtime; + else tr.frametime = 0.0; + + // begin a new frame + tr.framecount++; + + R_PushDlights(); + + R_SetupFrustum(); + R_SetupFrame(); + R_SetupGL( true ); + R_Clear( ~0 ); + + R_MarkLeaves(); + R_DrawFog (); + + R_CheckGLFog(); + R_DrawWorld(); + R_CheckFog(); + + gEngfuncs.CL_ExtraUpdate (); // don't let sound get messed up if going slow + + R_DrawEntitiesOnList(); + + R_DrawWaterSurfaces(); + + R_EndGL(); +} + +/* +=============== +R_CheckGamma +=============== +*/ +void R_CheckGamma( void ) +{ + if( gEngfuncs.R_DoResetGamma( )) + { + // paranoia cubemaps uses this + gEngfuncs.BuildGammaTable( 1.8f, 0.0f ); + + // paranoia cubemap rendering + if( gEngfuncs.drawFuncs->GL_BuildLightmaps ) + gEngfuncs.drawFuncs->GL_BuildLightmaps( ); + } + else if( FBitSet( vid_gamma->flags, FCVAR_CHANGED ) || FBitSet( vid_brightness->flags, FCVAR_CHANGED )) + { + gEngfuncs.BuildGammaTable( vid_gamma->value, vid_brightness->value ); + glConfig.softwareGammaUpdate = true; + GL_RebuildLightmaps(); + glConfig.softwareGammaUpdate = false; + + ClearBits( vid_brightness->flags, FCVAR_CHANGED ); + ClearBits( vid_gamma->flags, FCVAR_CHANGED ); + } +} + +/* +=============== +R_BeginFrame +=============== +*/ +void R_BeginFrame( qboolean clearScene ) +{ + glConfig.softwareGammaUpdate = false; // in case of possible fails + + if(( gl_clear->value || ENGINE_GET_PARM( PARM_DEV_OVERVIEW )) && + clearScene && ENGINE_GET_PARM( PARM_CONNSTATE ) != ca_cinematic ) + { + sceGuClear( GU_COLOR_BUFFER_BIT ); + } + + R_CheckGamma(); + + R_Set2DMode( true ); + // draw buffer stuff +#if 0 + pglDrawBuffer( GL_BACK ); + + // update texture parameters + if( FBitSet( gl_texture_nearest->flags|gl_lightmap_nearest->flags|gl_texture_anisotropy->flags|gl_texture_lodbias->flags, FCVAR_CHANGED )) + R_SetTextureParameters(); +#endif + gEngfuncs.CL_ExtraUpdate (); +} + +/* +=============== +R_SetupRefParams + +set initial params for renderer +=============== +*/ +void R_SetupRefParams( const ref_viewpass_t *rvp ) +{ + RI.params = RP_NONE; + RI.drawWorld = FBitSet( rvp->flags, RF_DRAW_WORLD ); + RI.onlyClientDraw = FBitSet( rvp->flags, RF_ONLY_CLIENTDRAW ); + RI.farClip = 0; + + if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP )) + RI.drawOrtho = FBitSet( rvp->flags, RF_DRAW_OVERVIEW ); + else RI.drawOrtho = false; + + // setup viewport + RI.viewport[0] = rvp->viewport[0]; + RI.viewport[1] = rvp->viewport[1]; + RI.viewport[2] = rvp->viewport[2]; + RI.viewport[3] = rvp->viewport[3]; + + // calc FOV + RI.fov_x = rvp->fov_x; + RI.fov_y = rvp->fov_y; + + VectorCopy( rvp->vieworigin, RI.vieworg ); + VectorCopy( rvp->viewangles, RI.viewangles ); + VectorCopy( rvp->vieworigin, RI.pvsorigin ); +} + +/* +=============== +R_RenderFrame +=============== +*/ +void R_RenderFrame( const ref_viewpass_t *rvp ) +{ + if( r_norefresh->value ) + return; + + // setup the initial render params + R_SetupRefParams( rvp ); + + // completely override rendering + if( gEngfuncs.drawFuncs->GL_RenderFrame != NULL ) + { + tr.fCustomRendering = true; + + if( gEngfuncs.drawFuncs->GL_RenderFrame( rvp )) + { + R_GatherPlayerLight(); + tr.realframecount++; + tr.fResetVis = true; + return; + } + } + + tr.fCustomRendering = false; + if( !RI.onlyClientDraw ) + R_RunViewmodelEvents(); + + tr.realframecount++; // right called after viewmodel events + R_RenderScene(); + + return; +} + +/* +=============== +R_EndFrame +=============== +*/ +void R_EndFrame( void ) +{ + // flush any remaining 2D bits + R_Set2DMode( false ); + + // finish rendering. + sceGuFinish(); + sceGuSync( GU_SYNC_FINISH, GU_SYNC_WAIT ); + + gEngfuncs.GL_SwapBuffers(); // vsync + + // swap the buffers. + sceGuSwapBuffers(); + + void* p_swap = guRender.disp_buffer; + guRender.disp_buffer = guRender.draw_buffer; + guRender.draw_buffer = p_swap; + + // start a new render. + sceGuStart( GU_DIRECT, guRender.context_list ); +} + +/* +=============== +R_DrawCubemapView +=============== +*/ +void R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size ) +{ + ref_viewpass_t rvp; + + // basic params + rvp.flags = rvp.viewentity = 0; + SetBits( rvp.flags, RF_DRAW_WORLD ); + SetBits( rvp.flags, RF_DRAW_CUBEMAP ); + + rvp.viewport[0] = rvp.viewport[1] = 0; + rvp.viewport[2] = rvp.viewport[3] = size; + rvp.fov_x = rvp.fov_y = 90.0f; // this is a final fov value + + // setup origin & angles + VectorCopy( origin, rvp.vieworigin ); + VectorCopy( angles, rvp.viewangles ); + + R_RenderFrame( &rvp ); + + RI.viewleaf = NULL; // force markleafs next frame +} + +/* +=============== +CL_FxBlend +=============== +*/ +int CL_FxBlend( cl_entity_t *e ) +{ + int blend = 0; + float offset, dist; + vec3_t tmp; + + offset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx + + switch( e->curstate.renderfx ) + { + case kRenderFxPulseSlowWide: + blend = e->curstate.renderamt + 0x40 * sin( gpGlobals->time * 2 + offset ); + break; + case kRenderFxPulseFastWide: + blend = e->curstate.renderamt + 0x40 * sin( gpGlobals->time * 8 + offset ); + break; + case kRenderFxPulseSlow: + blend = e->curstate.renderamt + 0x10 * sin( gpGlobals->time * 2 + offset ); + break; + case kRenderFxPulseFast: + blend = e->curstate.renderamt + 0x10 * sin( gpGlobals->time * 8 + offset ); + break; + case kRenderFxFadeSlow: + if( RP_NORMALPASS( )) + { + if( e->curstate.renderamt > 0 ) + e->curstate.renderamt -= 1; + else e->curstate.renderamt = 0; + } + blend = e->curstate.renderamt; + break; + case kRenderFxFadeFast: + if( RP_NORMALPASS( )) + { + if( e->curstate.renderamt > 3 ) + e->curstate.renderamt -= 4; + else e->curstate.renderamt = 0; + } + blend = e->curstate.renderamt; + break; + case kRenderFxSolidSlow: + if( RP_NORMALPASS( )) + { + if( e->curstate.renderamt < 255 ) + e->curstate.renderamt += 1; + else e->curstate.renderamt = 255; + } + blend = e->curstate.renderamt; + break; + case kRenderFxSolidFast: + if( RP_NORMALPASS( )) + { + if( e->curstate.renderamt < 252 ) + e->curstate.renderamt += 4; + else e->curstate.renderamt = 255; + } + blend = e->curstate.renderamt; + break; + case kRenderFxStrobeSlow: + blend = 20 * sin( gpGlobals->time * 4 + offset ); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxStrobeFast: + blend = 20 * sin( gpGlobals->time * 16 + offset ); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxStrobeFaster: + blend = 20 * sin( gpGlobals->time * 36 + offset ); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxFlickerSlow: + blend = 20 * (sin( gpGlobals->time * 2 ) + sin( gpGlobals->time * 17 + offset )); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxFlickerFast: + blend = 20 * (sin( gpGlobals->time * 16 ) + sin( gpGlobals->time * 23 + offset )); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxHologram: + case kRenderFxDistort: + VectorCopy( e->origin, tmp ); + VectorSubtract( tmp, RI.vieworg, tmp ); + dist = DotProduct( tmp, RI.vforward ); + + // turn off distance fade + if( e->curstate.renderfx == kRenderFxDistort ) + dist = 1; + + if( dist <= 0 ) + { + blend = 0; + } + else + { + e->curstate.renderamt = 180; + if( dist <= 100 ) blend = e->curstate.renderamt; + else blend = (int) ((1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * e->curstate.renderamt ); + blend += gEngfuncs.COM_RandomLong( -32, 31 ); + } + break; + default: + blend = e->curstate.renderamt; + break; + } + + blend = bound( 0, blend, 255 ); + + return blend; +} diff --git a/ref_gu/gu_rmath.c b/ref_gu/gu_rmath.c new file mode 100644 index 000000000..60c530a83 --- /dev/null +++ b/ref_gu/gu_rmath.c @@ -0,0 +1,290 @@ +/* +gl_rmath.c - renderer mathlib +Copyright (C) 2010 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "xash3d_mathlib.h" + +/* +======================================================================== + + Matrix4x4 operations (private to renderer) + +======================================================================== +*/ +void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ) +{ +#if 1 + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C100, 0 + %1\n" // C100 = in1[0] + "lv.q C110, 16 + %1\n" // C110 = in1[1] + "lv.q C120, 32 + %1\n" // C120 = in1[2] + "lv.q C130, 48 + %1\n" // C130 = in1[3] + "lv.q C200, 0 + %2\n" // C200 = in2[0] + "lv.q C210, 16 + %2\n" // C210 = in2[1] + "lv.q C220, 32 + %2\n" // C220 = in2[2] + "lv.q C230, 48 + %2\n" // C230 = in2[3] + "vmmul.q E000, E100, E200\n" // E000 = E100 * E200 + "sv.q C000, 0 + %0\n" // out[0] = C000 + "sv.q C010, 16 + %0\n" // out[1] = C010 + "sv.q C020, 32 + %0\n" // out[2] = C020 + "sv.q C030, 48 + %0\n" // out[3] = C030 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in1 ), "m"( *in2 ) + ); +#else + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0] + in1[0][3] * in2[3][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1] + in1[0][3] * in2[3][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2] + in1[0][3] * in2[3][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3] * in2[3][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0] + in1[1][3] * in2[3][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1] + in1[1][3] * in2[3][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2] + in1[1][3] * in2[3][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3] * in2[3][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0] + in1[2][3] * in2[3][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1] + in1[2][3] * in2[3][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2] + in1[2][3] * in2[3][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3] * in2[3][3]; + out[3][0] = in1[3][0] * in2[0][0] + in1[3][1] * in2[1][0] + in1[3][2] * in2[2][0] + in1[3][3] * in2[3][0]; + out[3][1] = in1[3][0] * in2[0][1] + in1[3][1] * in2[1][1] + in1[3][2] * in2[2][1] + in1[3][3] * in2[3][1]; + out[3][2] = in1[3][0] * in2[0][2] + in1[3][1] * in2[1][2] + in1[3][2] * in2[2][2] + in1[3][3] * in2[3][2]; + out[3][3] = in1[3][0] * in2[0][3] + in1[3][1] * in2[1][3] + in1[3][2] * in2[2][3] + in1[3][3] * in2[3][3]; +#endif +} + +/* +================ +Matrix4x4_CreateProjection + +NOTE: produce quake style world orientation +================ +*/ +void Matrix4x4_CreateProjection( matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar ) +{ + out[0][0] = ( 2.0f * zNear ) / ( xMax - xMin ); + out[1][1] = ( 2.0f * zNear ) / ( yMax - yMin ); + out[2][2] = -( zFar + zNear ) / ( zFar - zNear ); + out[3][3] = out[0][1] = out[1][0] = out[3][0] = out[0][3] = out[3][1] = out[1][3] = 0.0f; + + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[0][2] = ( xMax + xMin ) / ( xMax - xMin ); + out[1][2] = ( yMax + yMin ) / ( yMax - yMin ); + out[3][2] = -1.0f; + out[2][3] = -( 2.0f * zFar * zNear ) / ( zFar - zNear ); +} + +void Matrix4x4_CreateOrtho( matrix4x4 out, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar ) +{ + out[0][0] = 2.0f / (xRight - xLeft); + out[1][1] = 2.0f / (yTop - yBottom); + out[2][2] = -2.0f / (zFar - zNear); + out[3][3] = 1.0f; + out[0][1] = out[0][2] = out[1][0] = out[1][2] = out[3][0] = out[3][1] = out[3][2] = 0.0f; + + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[0][3] = -(xRight + xLeft) / (xRight - xLeft); + out[1][3] = -(yTop + yBottom) / (yTop - yBottom); + out[2][3] = -(zFar + zNear) / (zFar - zNear); +} + +/* +================ +Matrix4x4_CreateModelview + +NOTE: produce quake style world orientation +================ +*/ +void Matrix4x4_CreateModelview( matrix4x4 out ) +{ + out[0][0] = out[1][1] = out[2][2] = 0.0f; + out[3][0] = out[0][3] = 0.0f; + out[3][1] = out[1][3] = 0.0f; + out[3][2] = out[2][3] = 0.0f; + out[3][3] = 1.0f; + out[1][0] = out[0][2] = out[2][1] = 0.0f; + out[2][0] = out[0][1] = -1.0f; + out[1][2] = 1.0f; +} + +void Matrix4x4_ToFMatrix4( const matrix4x4 in, ScePspFMatrix4 *out ) +{ + // transpose matrix + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C000, 0 + %1\n" // C000 = in->x + "lv.q C010, 16 + %1\n" // C010 = in->y + "lv.q C020, 32 + %1\n" // C020 = in->z + "lv.q C030, 48 + %1\n" // C030 = in->w + "sv.q R000, 0 + %0\n" // out->x = R000 + "sv.q R001, 16 + %0\n" // out->y = R010 + "sv.q R002, 32 + %0\n" // out->z = R020 + "sv.q R003, 48 + %0\n" // out->w = R030 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ) + ); +} + +void Matrix4x4_FromFMatrix4( matrix4x4 out, ScePspFMatrix4 *in ) +{ + // transpose matrix + __asm__ ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.q C000, 0 + %1\n" // C000 = in->x + "lv.q C010, 16 + %1\n" // C010 = in->y + "lv.q C020, 32 + %1\n" // C020 = in->z + "lv.q C030, 48 + %1\n" // C030 = in->w + "sv.q R000, 0 + %0\n" // out->x = R000 + "sv.q R001, 16 + %0\n" // out->y = R010 + "sv.q R002, 32 + %0\n" // out->z = R020 + "sv.q R003, 48 + %0\n" // out->w = R030 + ".set pop\n" // restore assembler option + : "=m"( *out ) + : "m"( *in ) + ); +} + +void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) +{ + out[0][0] = 1.0f; + out[0][1] = 0.0f; + out[0][2] = 0.0f; + out[0][3] = x; + out[1][0] = 0.0f; + out[1][1] = 1.0f; + out[1][2] = 0.0f; + out[1][3] = y; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = 1.0f; + out[2][3] = z; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ) +{ + float len, c, s; + + len = x * x + y * y + z * z; + if( len != 0.0f ) len = 1.0f / sqrt( len ); + x *= len; + y *= len; + z *= len; + + angle *= (-M_PI_F / 180.0f); + SinCos( angle, &s, &c ); + + out[0][0]=x * x + c * (1 - x * x); + out[0][1]=x * y * (1 - c) + z * s; + out[0][2]=z * x * (1 - c) - y * s; + out[0][3]=0.0f; + out[1][0]=x * y * (1 - c) - z * s; + out[1][1]=y * y + c * (1 - y * y); + out[1][2]=y * z * (1 - c) + x * s; + out[1][3]=0.0f; + out[2][0]=z * x * (1 - c) + y * s; + out[2][1]=y * z * (1 - c) - x * s; + out[2][2]=z * z + c * (1 - z * z); + out[2][3]=0.0f; + out[3][0]=0.0f; + out[3][1]=0.0f; + out[3][2]=0.0f; + out[3][3]=1.0f; +} + +void Matrix4x4_CreateScale( matrix4x4 out, float x ) +{ + out[0][0] = x; + out[0][1] = 0.0f; + out[0][2] = 0.0f; + out[0][3] = 0.0f; + out[1][0] = 0.0f; + out[1][1] = x; + out[1][2] = 0.0f; + out[1][3] = 0.0f; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = x; + out[2][3] = 0.0f; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ) +{ + out[0][0] = x; + out[0][1] = 0.0f; + out[0][2] = 0.0f; + out[0][3] = 0.0f; + out[1][0] = 0.0f; + out[1][1] = y; + out[1][2] = 0.0f; + out[1][3] = 0.0f; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = z; + out[2][3] = 0.0f; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ) +{ + matrix4x4 base, temp; + + Matrix4x4_Copy( base, out ); + Matrix4x4_CreateTranslate( temp, x, y, z ); + Matrix4x4_Concat( out, base, temp ); +} + +void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z ) +{ + matrix4x4 base, temp; + + Matrix4x4_Copy( base, out ); + Matrix4x4_CreateRotate( temp, angle, x, y, z ); + Matrix4x4_Concat( out, base, temp ); +} + +void Matrix4x4_ConcatScale( matrix4x4 out, float x ) +{ + matrix4x4 base, temp; + + Matrix4x4_Copy( base, out ); + Matrix4x4_CreateScale( temp, x ); + Matrix4x4_Concat( out, base, temp ); +} + +void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ) +{ + matrix4x4 base, temp; + + Matrix4x4_Copy( base, out ); + Matrix4x4_CreateScale3( temp, x, y, z ); + Matrix4x4_Concat( out, base, temp ); +} diff --git a/ref_gu/gu_rmisc.c b/ref_gu/gu_rmisc.c new file mode 100644 index 000000000..3c2bc6920 --- /dev/null +++ b/ref_gu/gu_rmisc.c @@ -0,0 +1,190 @@ +/* +gl_rmisc.c - renderer misceallaneous +Copyright (C) 2010 Uncle Mike + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "shake.h" +#include "screenfade.h" +#include "cdll_int.h" + +static void R_ParseDetailTextures( const char *filename ) +{ + byte *afile; + char *pfile; + string token, texname; + string detail_texname; + string detail_path; + float xScale, yScale; + texture_t *tex; + int i; + + afile = gEngfuncs.COM_LoadFile( filename, NULL, false ); + if( !afile ) return; + + pfile = (char *)afile; + + // format: 'texturename' 'detailtexture' 'xScale' 'yScale' + while(( pfile = gEngfuncs.COM_ParseFile( pfile, token )) != NULL ) + { + texname[0] = '\0'; + detail_texname[0] = '\0'; + + // read texname + if( token[0] == '{' ) + { + // NOTE: COM_ParseFile handled some symbols seperately + // this code will be fix it + pfile = gEngfuncs.COM_ParseFile( pfile, token ); + Q_strncat( texname, "{", sizeof( texname )); + Q_strncat( texname, token, sizeof( texname )); + } + else Q_strncpy( texname, token, sizeof( texname )); + + // read detailtexture name + pfile = gEngfuncs.COM_ParseFile( pfile, token ); + Q_strncat( detail_texname, token, sizeof( detail_texname )); + + // trying the scales or '{' + pfile = gEngfuncs.COM_ParseFile( pfile, token ); + + // read second part of detailtexture name + if( token[0] == '{' ) + { + Q_strncat( detail_texname, token, sizeof( detail_texname )); + pfile = gEngfuncs.COM_ParseFile( pfile, token ); // read scales + Q_strncat( detail_texname, token, sizeof( detail_texname )); + pfile = gEngfuncs.COM_ParseFile( pfile, token ); // parse scales + } + + Q_snprintf( detail_path, sizeof( detail_path ), "gfx/%s", detail_texname ); + + // read scales + xScale = Q_atof( token ); + + pfile = gEngfuncs.COM_ParseFile( pfile, token ); + yScale = Q_atof( token ); + + if( xScale <= 0.0f || yScale <= 0.0f ) + continue; + + // search for existing texture and uploading detail texture + for( i = 0; i < WORLDMODEL->numtextures; i++ ) + { + tex = WORLDMODEL->textures[i]; + + if( Q_stricmp( tex->name, texname )) + continue; + + tex->dt_texturenum = GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR ); + + // texture is loaded + if( tex->dt_texturenum ) + { + gl_texture_t *glt; + + glt = R_GetTexture( tex->gl_texturenum ); + glt->xscale = xScale; + glt->yscale = yScale; + } + break; + } + } + + Mem_Free( afile ); +} + +void R_NewMap( void ) +{ + texture_t *tx; + int i; + + R_ClearDecals(); // clear all level decals + + R_StudioResetPlayerModels(); + + // upload detailtextures + if( CVAR_TO_BOOL( r_detailtextures )) + { + string mapname, filepath; + + Q_strncpy( mapname, WORLDMODEL->name, sizeof( mapname )); + COM_StripExtension( mapname ); + Q_sprintf( filepath, "%s_detail.txt", mapname ); + + R_ParseDetailTextures( filepath ); + } + + if( gEngfuncs.pfnGetCvarFloat( "v_dark" )) + { + screenfade_t *sf = gEngfuncs.GetScreenFade(); + float fadetime = 5.0f; + client_textmessage_t *title; + + title = gEngfuncs.pfnTextMessageGet( "GAMETITLE" ); + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) + fadetime = 1.0f; + + if( title ) + { + // get settings from titles.txt + sf->fadeEnd = title->holdtime + title->fadeout; + sf->fadeReset = title->fadeout; + } + else sf->fadeEnd = sf->fadeReset = fadetime; + + sf->fadeFlags = FFADE_IN; + sf->fader = sf->fadeg = sf->fadeb = 0; + sf->fadealpha = 255; + sf->fadeSpeed = (float)sf->fadealpha / sf->fadeReset; + sf->fadeReset += gpGlobals->time; + sf->fadeEnd += sf->fadeReset; + + gEngfuncs.Cvar_SetValue( "v_dark", 0.0f ); + } + + // clear out efrags in case the level hasn't been reloaded + for( i = 0; i < WORLDMODEL->numleafs; i++ ) + WORLDMODEL->leafs[i+1].efrags = NULL; + + glState.isFogEnabled = false; + tr.skytexturenum = -1; +#if 1 + sceGuDisable( GU_FOG ); +#else + pglDisable( GL_FOG ); +#endif + // clearing texture chains + for( i = 0; i < WORLDMODEL->numtextures; i++ ) + { + if( !WORLDMODEL->textures[i] ) + continue; + + tx = WORLDMODEL->textures[i]; + + if( !Q_strncmp( tx->name, "sky", 3 ) && tx->width == ( tx->height * 2 )) + tr.skytexturenum = i; + + tx->texturechain = NULL; + } + + R_SetupSky( MOVEVARS->skyName ); + + GL_BuildLightmaps (); +#if 0 + R_GenerateVBO(); +#endif + if( gEngfuncs.drawFuncs->R_NewMap != NULL ) + gEngfuncs.drawFuncs->R_NewMap(); + +} diff --git a/ref_gu/gu_rpart.c b/ref_gu/gu_rpart.c new file mode 100644 index 000000000..40b80ec91 --- /dev/null +++ b/ref_gu/gu_rpart.c @@ -0,0 +1,359 @@ +/* +gu_rpart.c - particles and tracers +Copyright (C) 2010 Uncle Mike +Copyright (C) 2020 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "r_efx.h" +#include "event_flags.h" +#include "entity_types.h" +#include "triangleapi.h" +#include "pm_local.h" +#include "cl_tent.h" +#include "studio.h" + +static float gTracerSize[11] = { 1.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; +static color24 gTracerColors[] = +{ +{ 255, 255, 255 }, // White +{ 255, 0, 0 }, // Red +{ 0, 255, 0 }, // Green +{ 0, 0, 255 }, // Blue +{ 0, 0, 0 }, // Tracer default, filled in from cvars, etc. +{ 255, 167, 17 }, // Yellow-orange sparks +{ 255, 130, 90 }, // Yellowish streaks (garg) +{ 55, 60, 144 }, // Blue egon streak +{ 255, 130, 90 }, // More Yellowish streaks (garg) +{ 255, 140, 90 }, // More Yellowish streaks (garg) +{ 200, 130, 90 }, // More red streaks (garg) +{ 255, 120, 70 }, // Darker red streaks (garg) +}; + +/* +================ +CL_DrawParticles + +update particle color, position, free expired and draw it +================ +*/ +void CL_DrawParticles( double frametime, particle_t *cl_active_particles, float partsize ) +{ + particle_t *p; + vec3_t right, up; + color24 *pColor; + int alpha; + float size; + uint vert_count; + uint vert_color; + + if( !cl_active_particles ) + return; // nothing to draw? + + vert_count = 0; + vert_color = 0; + + sceGuEnable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + + sceGuDepthMask( GU_TRUE ); + + if( r_fast_particles->value ) + { + gu_vert_fcv_t* const out = ( gu_vert_fcv_t* )extGuBeginPacket( NULL ); + + sceGuDisable( GU_TEXTURE_2D ); + + for( p = cl_active_particles; p; p = p->next ) + { + if(( p->type != pt_blob ) || ( p->packedColor == 255 )) + { + p->color = bound( 0, p->color, 255 ); + pColor = gEngfuncs.CL_GetPaletteColor( p->color ); + + alpha = 255 * (p->die - gpGlobals->time) * 16.0f; + if( alpha > 255 || p->type == pt_static ) + alpha = 255; + + out[vert_count].c = GUCOLOR4UB( gEngfuncs.LightToTexGamma( pColor->r ), + gEngfuncs.LightToTexGamma( pColor->g ), + gEngfuncs.LightToTexGamma( pColor->b ), alpha ); + out[vert_count].x = p->org[0]; + out[vert_count].y = p->org[1]; + out[vert_count].z = p->org[2]; + vert_count++; + + r_stats.c_particle_count++; + } + + gEngfuncs.CL_ThinkParticle( frametime, p ); + } + + if( vert_count ) + { + extGuEndPacket( ( void * )( out + vert_count ) ); + sceGuDrawArray( GU_POINTS, GU_COLOR_8888 | GU_VERTEX_32BITF, vert_count, 0, out ); + } + + sceGuEnable( GU_TEXTURE_2D ); + } + else + { + GL_Bind( XASH_TEXTURE0, tr.particleTexture ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + + gu_vert_ftcv_t* const out = ( gu_vert_ftcv_t* )extGuBeginPacket( NULL ); + + for( p = cl_active_particles; p; p = p->next ) + { + if(( p->type != pt_blob ) || ( p->packedColor == 255 )) + { + size = partsize; // get initial size of particle + + // scale up to keep particles from disappearing + size += (p->org[0] - RI.vieworg[0]) * RI.cull_vforward[0]; + size += (p->org[1] - RI.vieworg[1]) * RI.cull_vforward[1]; + size += (p->org[2] - RI.vieworg[2]) * RI.cull_vforward[2]; + + if( size < 20.0f ) size = partsize; + else size = partsize + size * 0.002f; + + // scale the axes by radius + VectorScale( RI.cull_vright, size, right ); + VectorScale( RI.cull_vup, size, up ); + + p->color = bound( 0, p->color, 255 ); + pColor = gEngfuncs.CL_GetPaletteColor( p->color ); + + alpha = 255 * (p->die - gpGlobals->time) * 16.0f; + if( alpha > 255 || p->type == pt_static ) + alpha = 255; + vert_color = GUCOLOR4UB( gEngfuncs.LightToTexGamma( pColor->r ), + gEngfuncs.LightToTexGamma( pColor->g ), + gEngfuncs.LightToTexGamma( pColor->b ), alpha ); + + out[vert_count].u = 0.0f; + out[vert_count].v = 0.0f; + out[vert_count].c = vert_color; + out[vert_count].x = p->org[0] + right[0] + up[0]; + out[vert_count].y = p->org[1] + right[1] + up[1]; + out[vert_count].z = p->org[2] + right[2] + up[2]; + vert_count++; + out[vert_count].u = 1.0f; + out[vert_count].v = 1.0f; + out[vert_count].c = vert_color; + out[vert_count].x = p->org[0] - right[0] - up[0]; + out[vert_count].y = p->org[1] - right[1] - up[1]; + out[vert_count].z = p->org[2] - right[2] - up[2]; + vert_count++; + + r_stats.c_particle_count++; + } + + gEngfuncs.CL_ThinkParticle( frametime, p ); + } + + if( vert_count ) + { + extGuEndPacket(( void * )( out + vert_count )); + sceGuDrawArray( GU_SPRITES, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF, vert_count, 0, out ); + } + } + + sceGuDepthMask( GU_FALSE ); +} + +/* +================ +CL_CullTracer + +check tracer bbox +================ +*/ +static qboolean CL_CullTracer( particle_t *p, const vec3_t start, const vec3_t end ) +{ + vec3_t mins, maxs; + int i; + + // compute the bounding box + for( i = 0; i < 3; i++ ) + { + if( start[i] < end[i] ) + { + mins[i] = start[i]; + maxs[i] = end[i]; + } + else + { + mins[i] = end[i]; + maxs[i] = start[i]; + } + + // don't let it be zero sized + if( mins[i] == maxs[i] ) + { + maxs[i] += gTracerSize[p->type] * 2.0f; + } + } + + // check bbox + return R_CullBox( mins, maxs ); +} + +/* +================ +CL_DrawTracers + +update tracer color, position, free expired and draw it +================ +*/ +void CL_DrawTracers( double frametime, particle_t *cl_active_tracers ) +{ + float scale, atten, gravity; + vec3_t screenLast, screen; + vec3_t start, end, delta; + particle_t *p; + + // update tracer color if this is changed + if( FBitSet( tracerred->flags|tracergreen->flags|tracerblue->flags|traceralpha->flags, FCVAR_CHANGED )) + { + color24 *customColors = &gTracerColors[4]; + customColors->r = (byte)(tracerred->value * traceralpha->value * 255); + customColors->g = (byte)(tracergreen->value * traceralpha->value * 255); + customColors->b = (byte)(tracerblue->value * traceralpha->value * 255); + ClearBits( tracerred->flags, FCVAR_CHANGED ); + ClearBits( tracergreen->flags, FCVAR_CHANGED ); + ClearBits( tracerblue->flags, FCVAR_CHANGED ); + ClearBits( traceralpha->flags, FCVAR_CHANGED ); + } + + if( !cl_active_tracers ) + return; // nothing to draw? + + sceGuEnable( GU_BLEND ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, GUBLEND1 ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuDepthMask( GU_TRUE ); + sceGuDisable( GU_TEXTURE_2D ); + + gravity = frametime * MOVEVARS->gravity; + scale = 1.0 - (frametime * 0.9); + if( scale < 0.0f ) scale = 0.0f; + + for( p = cl_active_tracers; p; p = p->next ) + { + atten = (p->die - gpGlobals->time); + if( atten > 0.1f ) atten = 0.1f; + + VectorScale( p->vel, ( p->ramp * atten ), delta ); + VectorAdd( p->org, delta, end ); + VectorCopy( p->org, start ); + + if( !CL_CullTracer( p, start, end )) + { + vec3_t verts[4], tmp2; + vec3_t tmp, normal; + color24 *pColor; + + // Transform point into screen space + TriWorldToScreen( start, screen ); + TriWorldToScreen( end, screenLast ); + + // build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + + // build point along noraml line (normal is -y, x) + VectorScale( RI.cull_vup, tmp[0] * gTracerSize[p->type], normal ); + VectorScale( RI.cull_vright, -tmp[1] * gTracerSize[p->type], tmp2 ); + VectorSubtract( normal, tmp2, normal ); + + // compute four vertexes + VectorSubtract( start, normal, verts[0] ); + VectorAdd( start, normal, verts[1] ); + VectorAdd( verts[0], delta, verts[2] ); + VectorAdd( verts[1], delta, verts[3] ); + + if( p->color > sizeof( gTracerColors ) / sizeof( color24 ) ) + { + gEngfuncs.Con_Printf( S_ERROR "UserTracer with color > %d\n", sizeof( gTracerColors ) / sizeof( color24 )); + p->color = 0; + } + + pColor = &gTracerColors[p->color]; + sceGuColor( GUCOLOR4UB( pColor->r, pColor->g, pColor->b, p->packedColor )); + + gu_vert_fv_t* const out = ( gu_vert_fv_t* )sceGuGetMemory( sizeof( gu_vert_fv_t ) * 4 ); + out[0].x = verts[2][0]; + out[0].y = verts[2][1]; + out[0].z = verts[2][2]; + out[1].x = verts[3][0]; + out[1].y = verts[3][1]; + out[1].z = verts[3][2]; + out[2].x = verts[1][0]; + out[2].y = verts[1][1]; + out[2].z = verts[1][2]; + out[3].x = verts[0][0]; + out[3].y = verts[0][1]; + out[3].z = verts[0][2]; + sceGuDrawArray( GU_TRIANGLE_FAN, GU_VERTEX_32BITF, 4, 0, out ); + } + + // evaluate position + VectorMA( p->org, frametime, p->vel, p->org ); + + if( p->type == pt_grav ) + { + p->vel[0] *= scale; + p->vel[1] *= scale; + p->vel[2] -= gravity; + + p->packedColor = 255 * (p->die - gpGlobals->time) * 2; + if( p->packedColor > 255 ) p->packedColor = 255; + } + else if( p->type == pt_slowgrav ) + { + p->vel[2] = gravity * 0.05f; + } + } + + sceGuEnable( GU_TEXTURE_2D ); + sceGuDepthMask( GU_FALSE ); +} + +/* +=============== +CL_DrawParticlesExternal + +allow to draw effects from custom renderer +=============== +*/ +void CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime ) +{ + ref_instance_t oldRI = RI; + + memcpy( &oldRI, &RI, sizeof( ref_instance_t )); + R_SetupRefParams( rvp ); + R_SetupFrustum(); + R_SetupGL( false ); // don't touch GL-states + tr.frametime = frametime; + + gEngfuncs.CL_DrawEFX( frametime, trans_pass ); + + // restore internal state + memcpy( &RI, &oldRI, sizeof( ref_instance_t )); +} diff --git a/ref_gu/gu_rsurf.c b/ref_gu/gu_rsurf.c new file mode 100644 index 000000000..1c53814c0 --- /dev/null +++ b/ref_gu/gu_rsurf.c @@ -0,0 +1,2347 @@ +/* +gu_rsurf.c - surface-related refresh code +Copyright (C) 2010 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "xash3d_mathlib.h" +#include "mod_local.h" + +typedef struct +{ + int allocated[BLOCK_SIZE_MAX]; + int current_lightmap_texture; + msurface_t *dynamic_surfaces; + msurface_t *lightmap_surfaces[MAX_LIGHTMAPS]; + byte lightmap_buffer[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*LIGHTMAP_BPP]; +} gllightmapstate_t; + +static int nColinElim; // stats +static vec2_t world_orthocenter; +static vec2_t world_orthohalf; +static uint r_blocklights[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*3]; +static mextrasurf_t *fullbright_surfaces[MAX_TEXTURES]; +static mextrasurf_t *detail_surfaces[MAX_TEXTURES]; +static int rtable[MOD_FRAMES][MOD_FRAMES]; +static qboolean draw_alpha_surfaces = false; +static qboolean draw_fullbrights = false; +static qboolean draw_details = false; +static msurface_t *skychain = NULL; +static gllightmapstate_t gl_lms; +static void LM_UploadBlock( qboolean dynamic ); + +byte *Mod_GetCurrentVis( void ) +{ + if( gEngfuncs.drawFuncs->Mod_GetCurrentVis && tr.fCustomRendering ) + return gEngfuncs.drawFuncs->Mod_GetCurrentVis(); + return RI.visbytes; +} + +void Mod_SetOrthoBounds( const float *mins, const float *maxs ) +{ + if( gEngfuncs.drawFuncs->GL_OrthoBounds ) + { + gEngfuncs.drawFuncs->GL_OrthoBounds( mins, maxs ); + } + + Vector2Average( maxs, mins, world_orthocenter ); + Vector2Subtract( maxs, world_orthocenter, world_orthohalf ); +} + +static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs ) +{ + int i, j; + float *v; + + ClearBounds( mins, maxs ); + + for( i = 0, v = verts; i < numverts; i++ ) + { + for( j = 0; j < 3; j++, v++ ) + { + if( *v < mins[j] ) mins[j] = *v; + if( *v > maxs[j] ) maxs[j] = *v; + } + } +} + +static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts ) +{ + vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE]; + mextrasurf_t *warpinfo = warpface->info; + float dist[SUBDIVIDE_SIZE]; + float m, frac, s, t, *v; + int i, j, k, f, b; + float sample_size; + vec3_t mins, maxs; + glpoly_t *poly; + model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + + if( numverts > ( SUBDIVIDE_SIZE - 4 )) + gEngfuncs.Host_Error( "Mod_SubdividePolygon: too many vertexes on face ( %i )\n", numverts ); + + sample_size = gEngfuncs.Mod_SampleSizeForFace( warpface ); + BoundPoly( numverts, verts, mins, maxs ); + + for( i = 0; i < 3; i++ ) + { + m = ( mins[i] + maxs[i] ) * 0.5f; + m = gl_subdivide_size->value * floor( m / gl_subdivide_size->value + 0.5f ); + if( maxs[i] - m < 8 ) continue; + if( m - mins[i] < 8 ) continue; + + // cut it + v = verts + i; + for( j = 0; j < numverts; j++, v += 3 ) + dist[j] = *v - m; + + // wrap cases + dist[j] = dist[0]; + v -= i; + VectorCopy( verts, v ); + + f = b = 0; + v = verts; + for( j = 0; j < numverts; j++, v += 3 ) + { + if( dist[j] >= 0 ) + { + VectorCopy( v, front[f] ); + f++; + } + + if( dist[j] <= 0 ) + { + VectorCopy (v, back[b]); + b++; + } + + if( dist[j] == 0 || dist[j+1] == 0 ) + continue; + + if(( dist[j] > 0 ) != ( dist[j+1] > 0 )) + { + // clip point + frac = dist[j] / ( dist[j] - dist[j+1] ); + for( k = 0; k < 3; k++ ) + front[f][k] = back[b][k] = v[k] + frac * (v[3+k] - v[k]); + f++; + b++; + } + } + + SubdividePolygon_r( warpface, f, front[0] ); + SubdividePolygon_r( warpface, b, back[0] ); + return; + } + + if( numverts != 4 ) + ClearBits( warpface->flags, SURF_DRAWTURB_QUADS ); + + // add a point in the center to help keep warp valid + poly = Mem_Calloc( loadmodel->mempool, sizeof( glpoly_t ) + ( numverts * 2 - 1 ) * sizeof( gu_vert_t ) ); + poly->next = warpface->polys; + poly->flags = warpface->flags; + warpface->polys = poly; + poly->numverts = numverts; + + for( i = 0; i < numverts; i++, verts += 3 ) + { + if( FBitSet( warpface->flags, SURF_DRAWTURB )) + { + s = DotProduct( verts, warpface->texinfo->vecs[0] ); + t = DotProduct( verts, warpface->texinfo->vecs[1] ); + } + else + { + s = DotProduct( verts, warpface->texinfo->vecs[0] ) + warpface->texinfo->vecs[0][3]; + t = DotProduct( verts, warpface->texinfo->vecs[1] ) + warpface->texinfo->vecs[1][3]; + s /= warpface->texinfo->texture->width; + t /= warpface->texinfo->texture->height; + } + + poly->verts[i].uv[0] = s; + poly->verts[i].uv[1] = t; + VectorCopy( verts, poly->verts[i].xyz ); + + // for speed reasons + if( !FBitSet( warpface->flags, SURF_DRAWTURB )) + { + // lightmap texture coordinates + s = DotProduct( verts, warpinfo->lmvecs[0] ) + warpinfo->lmvecs[0][3]; + s -= warpinfo->lightmapmins[0]; + s += warpface->light_s * sample_size; + s += sample_size * 0.5f; + s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + + t = DotProduct( verts, warpinfo->lmvecs[1] ) + warpinfo->lmvecs[1][3]; + t -= warpinfo->lightmapmins[1]; + t += warpface->light_t * sample_size; + t += sample_size * 0.5f; + t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; + + poly->verts[i + numverts].uv[0] = s; + poly->verts[i + numverts].uv[1] = t; + VectorCopy( verts, poly->verts[i + numverts].xyz ); + } + } +} + +void GL_SetupFogColorForSurfaces( void ) +{ + vec3_t fogColor; + float factor, div; + + if( !glState.isFogEnabled) + return; + + if( RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture ) + { +#if 1 + glState.fogColor = GUCOLOR4F( RI.fogColor[0], RI.fogColor[1], RI.fogColor[2], glState.fogDensity ); + sceGuFog( glState.fogStart, glState.fogEnd, glState.fogColor ); +#else + pglFogfv( GL_FOG_COLOR, RI.fogColor ); +#endif + return; + } + + div = (r_detailtextures->value) ? 2.0f : 1.0f; + factor = (r_detailtextures->value) ? 3.0f : 2.0f; + fogColor[0] = pow( RI.fogColor[0] / div, ( 1.0f / factor )); + fogColor[1] = pow( RI.fogColor[1] / div, ( 1.0f / factor )); + fogColor[2] = pow( RI.fogColor[2] / div, ( 1.0f / factor )); +#if 1 + glState.fogColor = GUCOLOR4F( fogColor[0], fogColor[1], fogColor[2], glState.fogDensity ); + sceGuFog( glState.fogStart, glState.fogEnd, glState.fogColor ); +#else + pglFogfv( GL_FOG_COLOR, fogColor ); +#endif +} + +void GL_ResetFogColor( void ) +{ + // restore fog here +#if 1 + if( !glState.isFogEnabled ) + return; + + glState.fogColor = GUCOLOR4F( RI.fogColor[0], RI.fogColor[1], RI.fogColor[2], glState.fogDensity ); + sceGuFog( glState.fogStart, glState.fogEnd, glState.fogColor ); +#else + if( glState.isFogEnabled ) + pglFogfv( GL_FOG_COLOR, RI.fogColor ); +#endif +} + +/* +================ +GL_SubdivideSurface + +Breaks a polygon up along axial 64 unit +boundaries so that turbulent and sky warps +can be done reasonably. +================ +*/ +void GL_SubdivideSurface( msurface_t *fa ) +{ + vec3_t verts[SUBDIVIDE_SIZE]; + int numverts; + int i, lindex; + float *vec; + model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + + // convert edges back to a normal polygon + numverts = 0; + for( i = 0; i < fa->numedges; i++ ) + { + lindex = loadmodel->surfedges[fa->firstedge + i]; + + if( lindex > 0 ) vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position; + else vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position; + VectorCopy( vec, verts[numverts] ); + numverts++; + } + + SetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state + + // do subdivide + SubdividePolygon_r( fa, numverts, verts[0] ); +} + +/* +================ +GL_BuildPolygonFromSurface +================ +*/ +void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) +{ + int i, lindex, lnumverts; + medge_t *pedges, *r_pedge; + mextrasurf_t *info = fa->info; + float sample_size; + texture_t *tex; + gl_texture_t *glt; + float *vec; + float s, t; + glpoly_t *poly; + int num_removed = 0; + + if( !mod || !fa->texinfo || !fa->texinfo->texture ) + return; // bad polygon ? + + if( FBitSet( fa->flags, SURF_CONVEYOR ) && fa->texinfo->texture->gl_texturenum != 0 ) + { + glt = R_GetTexture( fa->texinfo->texture->gl_texturenum ); + tex = fa->texinfo->texture; + Assert( glt != NULL && tex != NULL ); + + // update conveyor widths for keep properly speed of scrolling + glt->srcWidth = tex->width; + glt->srcHeight = tex->height; + } + + sample_size = gEngfuncs.Mod_SampleSizeForFace( fa ); + + // reconstruct the polygon + pedges = mod->edges; + lnumverts = fa->numedges; + + // detach if already created, reconstruct again + poly = fa->polys; + fa->polys = NULL; + + // quake simple models (healthkits etc) need to be reconstructed their polys because LM coords has changed after the map change + // PSP GU-friendly format s1t1 xyz s2t2 xyz (Idea from PSP Quake) + poly = Mem_Realloc( mod->mempool, poly, sizeof( glpoly_t ) + ( lnumverts * 2 - 1 ) * sizeof( gu_vert_t )); + poly->next = fa->polys; + poly->flags = fa->flags; + fa->polys = poly; + poly->numverts = lnumverts; + + for( i = 0; i < lnumverts; i++ ) + { + lindex = mod->surfedges[fa->firstedge + i]; + + if( lindex > 0 ) + { + r_pedge = &pedges[lindex]; + vec = mod->vertexes[r_pedge->v[0]].position; + } + else + { + r_pedge = &pedges[-lindex]; + vec = mod->vertexes[r_pedge->v[1]].position; + } + + s = DotProduct( vec, fa->texinfo->vecs[0] ) + fa->texinfo->vecs[0][3]; + s /= fa->texinfo->texture->width; + + t = DotProduct( vec, fa->texinfo->vecs[1] ) + fa->texinfo->vecs[1][3]; + t /= fa->texinfo->texture->height; + + poly->verts[i].uv[0] = s; + poly->verts[i].uv[1] = t; + VectorCopy( vec, poly->verts[i].xyz ); + + // lightmap texture coordinates + s = DotProduct( vec, info->lmvecs[0] ) + info->lmvecs[0][3]; + s -= info->lightmapmins[0]; + s += fa->light_s * sample_size; + s += sample_size * 0.5f; + s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + + t = DotProduct( vec, info->lmvecs[1] ) + info->lmvecs[1][3]; + t -= info->lightmapmins[1]; + t += fa->light_t * sample_size; + t += sample_size * 0.5f; + t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; + + poly->verts[i + poly->numverts].uv[0] = s; + poly->verts[i + poly->numverts].uv[1] = t; + VectorCopy( vec, poly->verts[i + poly->numverts].xyz ); + } + + // remove co-linear points - Ed + if( !CVAR_TO_BOOL( gl_keeptjunctions ) && !FBitSet( fa->flags, SURF_UNDERWATER )) + { + for( i = 0; i < lnumverts; i++ ) + { + vec3_t v1, v2; + gu_vert_t *prev, *this, *next; + + prev = &poly->verts[(i + lnumverts - 1) % lnumverts]; + next = &poly->verts[(i + 1) % lnumverts]; + this = &poly->verts[i]; + + VectorSubtract( this->xyz, prev->xyz, v1 ); + VectorNormalize( v1 ); + VectorSubtract( next->xyz, prev->xyz, v2 ); + VectorNormalize( v2 ); + + // skip co-linear points + if(( fabs( v1[0] - v2[0] ) <= 0.001f) && (fabs( v1[1] - v2[1] ) <= 0.001f) && (fabs( v1[2] - v2[2] ) <= 0.001f)) + { + int j, k; + + for( j = i + 1; j < lnumverts; j++ ) + { + poly->verts[j - 1] = poly->verts[j]; + poly->verts[poly->numverts + j - 1] = poly->verts[poly->numverts + j]; + } + + // retry next vertex next time, which is now current vertex + lnumverts--; + nColinElim++; + num_removed++; + i--; + } + } + + if (num_removed > 0) + { + for (i = poly->numverts; i < poly->numverts + lnumverts; i++) + poly->verts[i - num_removed] = poly->verts[i]; + } + } + + poly->numverts = lnumverts; +} + + +/* +=============== +R_TextureAnim + +Returns the proper texture for a given time and base texture, do not process random tiling +=============== +*/ +texture_t *R_TextureAnim( texture_t *b ) +{ + texture_t *base = b; + int count, reletive; + + if( RI.currententity->curstate.frame ) + { + if( base->alternate_anims ) + base = base->alternate_anims; + } + + if( !base->anim_total ) + return base; + if( base->name[0] == '-' ) + { + return b; // already tiled + } + else + { + int speed; + + // Quake1 textures uses 10 frames per second + if( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL )) + speed = 10; + else speed = 20; + + reletive = (int)(gpGlobals->time * speed) % base->anim_total; + } + + + count = 0; + + while( base->anim_min > reletive || base->anim_max <= reletive ) + { + base = base->anim_next; + + if( !base || ++count > MOD_FRAMES ) + return b; + } + + return base; +} + +/* +=============== +R_TextureAnimation + +Returns the proper texture for a given time and surface +=============== +*/ +texture_t *R_TextureAnimation( msurface_t *s ) +{ + texture_t *base = s->texinfo->texture; + int count, reletive; + + if( RI.currententity && RI.currententity->curstate.frame ) + { + if( base->alternate_anims ) + base = base->alternate_anims; + } + + if( !base->anim_total ) + return base; + + if( base->name[0] == '-' ) + { + int tx = (int)((s->texturemins[0] + (base->width << 16)) / base->width) % MOD_FRAMES; + int ty = (int)((s->texturemins[1] + (base->height << 16)) / base->height) % MOD_FRAMES; + + reletive = rtable[tx][ty] % base->anim_total; + } + else + { + int speed; + + // Quake1 textures uses 10 frames per second + if( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL )) + speed = 10; + else speed = 20; + + reletive = (int)(gpGlobals->time * speed) % base->anim_total; + } + + count = 0; + + while( base->anim_min > reletive || base->anim_max <= reletive ) + { + base = base->anim_next; + + if( !base || ++count > MOD_FRAMES ) + return s->texinfo->texture; + } + + return base; +} + +/* +=============== +R_AddDynamicLights +=============== +*/ +void R_AddDynamicLights( msurface_t *surf ) +{ + float dist, rad, minlight; + int lnum, s, t, sd, td, smax, tmax; + float sl, tl, sacc, tacc; + vec3_t impact, origin_l; + mextrasurf_t *info = surf->info; + int sample_frac = 1.0; + float sample_size; + mtexinfo_t *tex; + dlight_t *dl; + uint *bl; + + // no dlighted surfaces here + if( !R_CountSurfaceDlights( surf )) return; + + sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); + smax = (info->lightextents[0] / sample_size) + 1; + tmax = (info->lightextents[1] / sample_size) + 1; + tex = surf->texinfo; + + if( FBitSet( tex->flags, TEX_WORLD_LUXELS )) + { + if( surf->texinfo->faceinfo ) + sample_frac = surf->texinfo->faceinfo->texture_step; + else if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP )) + sample_frac = LM_SAMPLE_EXTRASIZE; + else sample_frac = LM_SAMPLE_SIZE; + } + + for( lnum = 0, dl = gEngfuncs.GetDynamicLight( 0 ); lnum < MAX_DLIGHTS; lnum++, dl++ ) + { + if( !FBitSet( surf->dlightbits, BIT( lnum ))) + continue; // not lit by this light + + // transform light origin to local bmodel space + if( !tr.modelviewIdentity ) + Matrix4x4_VectorITransform( RI.objectMatrix, dl->origin, origin_l ); + else VectorCopy( dl->origin, origin_l ); + + rad = dl->radius; + dist = PlaneDiff( origin_l, surf->plane ); + rad -= fabs( dist ); + + // rad is now the highest intensity on the plane + minlight = dl->minlight; + if( rad < minlight ) + continue; + + minlight = rad - minlight; + + if( surf->plane->type < 3 ) + { + VectorCopy( origin_l, impact ); + impact[surf->plane->type] -= dist; + } + else VectorMA( origin_l, -dist, surf->plane->normal, impact ); + + sl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; + tl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; + bl = r_blocklights; + + for( t = 0, tacc = 0; t < tmax; t++, tacc += sample_size ) + { + td = (tl - tacc) * sample_frac; + if( td < 0 ) td = -td; + + for( s = 0, sacc = 0; s < smax; s++, sacc += sample_size, bl += 3 ) + { + sd = (sl - sacc) * sample_frac; + if( sd < 0 ) sd = -sd; + + if( sd > td ) dist = sd + (td >> 1); + else dist = td + (sd >> 1); + + if( dist < minlight ) + { + bl[0] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.r )) / 256; + bl[1] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.g )) / 256; + bl[2] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.b )) / 256; + } + } + } + } +} + +/* +================ +R_SetCacheState +================ +*/ +void R_SetCacheState( msurface_t *surf ) +{ + int maps; + + for( maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++ ) + { + surf->cached_light[maps] = tr.lightstylevalue[surf->styles[maps]]; + } +} + +/* +============================================================================= + + LIGHTMAP ALLOCATION + +============================================================================= +*/ +static void LM_InitBlock( void ) +{ + memset( gl_lms.allocated, 0, sizeof( gl_lms.allocated )); +} + +static int LM_AllocBlock( int w, int h, int *x, int *y ) +{ + int i, j; + int best, best2; + + best = BLOCK_SIZE; + + for( i = 0; i < BLOCK_SIZE - w; i++ ) + { + best2 = 0; + + for( j = 0; j < w; j++ ) + { + if( gl_lms.allocated[i+j] >= best ) + break; + if( gl_lms.allocated[i+j] > best2 ) + best2 = gl_lms.allocated[i+j]; + } + + if( j == w ) + { + // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if( best + h > BLOCK_SIZE ) + return false; + + for( i = 0; i < w; i++ ) + gl_lms.allocated[*x + i] = best + h; + + return true; +} + +static void LM_UploadBlock( qboolean dynamic ) +{ + int i; + + if( dynamic ) + { + int height = 0; + + for( i = 0; i < BLOCK_SIZE; i++ ) + { + if( gl_lms.allocated[i] > height ) + height = gl_lms.allocated[i]; + } + + GL_UpdateTexture( tr.dlightTexture, 0, 0, BLOCK_SIZE, height, gl_lms.lightmap_buffer ); + GL_Bind( XASH_TEXTURE0, tr.dlightTexture ); + } + else + { + rgbdata_t r_lightmap; + char lmName[16]; + + i = gl_lms.current_lightmap_texture; + + // upload static lightmaps only during loading + memset( &r_lightmap, 0, sizeof( r_lightmap )); + Q_snprintf( lmName, sizeof( lmName ), "*lightmap%i", i ); + + r_lightmap.width = BLOCK_SIZE; + r_lightmap.height = BLOCK_SIZE; + r_lightmap.type = LIGHTMAP_FORMAT; + r_lightmap.size = r_lightmap.width * r_lightmap.height * LIGHTMAP_BPP; + r_lightmap.flags = IMAGE_HAS_COLOR; + r_lightmap.buffer = gl_lms.lightmap_buffer; + tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_NOMIPMAP|TF_ATLAS_PAGE ); + + if( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS ) + gEngfuncs.Host_Error( "AllocBlock: full\n" ); + } +} + +/* +================= +R_BuildLightmap + +Combine and scale multiple lightmaps into the floating +format in r_blocklights +================= +*/ +static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride, qboolean dynamic ) +{ + int smax, tmax; + uint *bl, scale; + int i, map, size, s, t; + int sample_size; + mextrasurf_t *info = surf->info; + color24 *lm; + byte color[3]; + + sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + size = smax * tmax; + + lm = surf->samples; + + memset( r_blocklights, 0, sizeof( uint ) * size * 3 ); + + // add all the lightmaps + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255 && lm; map++ ) + { + scale = tr.lightstylevalue[surf->styles[map]]; + + for( i = 0, bl = r_blocklights; i < size; i++, bl += 3, lm++ ) + { + bl[0] += gEngfuncs.LightToTexGamma( lm->r ) * scale; + bl[1] += gEngfuncs.LightToTexGamma( lm->g ) * scale; + bl[2] += gEngfuncs.LightToTexGamma( lm->b ) * scale; + } + } + + // add all the dynamic lights + if( surf->dlightframe == tr.framecount && dynamic ) + R_AddDynamicLights( surf ); + + // Put into texture format +#if 0 + stride -= (smax << 2); + bl = r_blocklights; + + for( t = 0; t < tmax; t++, dest += stride ) + { + for( s = 0; s < smax; s++ ) + { + dest[0] = Q_min((bl[0] >> 7), 255 ); + dest[1] = Q_min((bl[1] >> 7), 255 ); + dest[2] = Q_min((bl[2] >> 7), 255 ); + dest[3] = 255; + + bl += 3; + dest += 4; + } + } +#else + stride -= smax * LIGHTMAP_BPP; + bl = r_blocklights; + + for( t = 0; t < tmax; t++, dest += stride ) + { + for( s = 0; s < smax; s++ ) + { + color[0] = Q_min((bl[0] >> 7), 255 ); + color[1] = Q_min((bl[1] >> 7), 255 ); + color[2] = Q_min((bl[2] >> 7), 255 ); + color[3] = 255; +#if LIGHTMAP_BPP == 1 + dest[0] = ( color[0] >> 5 ) & 0x07; + dest[0] |= ( color[1] >> 2 ) & 0x38; + dest[0] |= ( color[2] ) & 0xc0; +#elif LIGHTMAP_BPP == 2 + dest[0] = ( color[0] >> 3 ) & 0x1f; + dest[0] |= ( color[1] << 3 ) & 0xe0; + dest[1] = ( color[1] >> 5 ) & 0x07; + dest[1] |= ( color[2] ) & 0xf8; +#elif LIGHTMAP_BPP == 3 + dest[0] = color[0]; + dest[1] = color[1]; + dest[2] = color[2]; +#else + dest[0] = color[0]; + dest[1] = color[1]; + dest[2] = color[2]; + dest[3] = 255; +#endif + bl += 3; + dest += LIGHTMAP_BPP; + } + } +#endif +} + +/* +================ +DrawGLPoly +================ +*/ +void DrawGLPoly( glpoly_t *p, float xScale, float yScale ) +{ + float *v; + float sOffset, sy; + float tOffset, cy; + cl_entity_t *e = RI.currententity; + int i; + qboolean hasScale = false; + qboolean hasOffset = false; + + if( !p ) return; + + if( FBitSet( p->flags, SURF_DRAWTILED )) + GL_ResetFogColor(); + + if( p->flags & SURF_CONVEYOR ) + { + float flConveyorSpeed = 0.0f; + float flRate, flAngle; + gl_texture_t *texture; + + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && RI.currententity == gEngfuncs.GetEntityByIndex( 0 ) ) + { + // same as doom speed + flConveyorSpeed = -35.0f; + } + else + { + flConveyorSpeed = (e->curstate.rendercolor.g<<8|e->curstate.rendercolor.b) / 16.0f; + if( e->curstate.rendercolor.r ) flConveyorSpeed = -flConveyorSpeed; + } + + texture = R_GetTexture( glState.currentTexture ); + + flRate = fabs( flConveyorSpeed ) / (float)texture->srcWidth; + flAngle = ( flConveyorSpeed >= 0 ) ? 180 : 0; + + SinCos( flAngle * ( M_PI_F / 180.0f ), &sy, &cy ); + sOffset = gpGlobals->time * cy * flRate; + tOffset = gpGlobals->time * sy * flRate; + + // make sure that we are positive + if( sOffset < 0.0f ) sOffset += 1.0f + -(int)sOffset; + if( tOffset < 0.0f ) tOffset += 1.0f + -(int)tOffset; + + // make sure that we are in a [0,1] range + sOffset = sOffset - (int)sOffset; + tOffset = tOffset - (int)tOffset; + + hasOffset = true; + } + + if( xScale != 0.0f && yScale != 0.0f ) + hasScale = true; + + if( hasScale ) sceGuTexScale( xScale, yScale ); + if( hasOffset ) sceGuTexOffset( sOffset, tOffset ); + + if ( GU_ClipIsRequired( &p->verts[0], p->numverts ) ) + { + // clip the polygon. + gu_vert_t* cv; + int cvc; + + GU_Clip( &p->verts[0], p->numverts, &cv, &cvc ); + if( cvc ) + { +#if CLIPPING_DEBUGGING + sceGuDisable(GU_TEXTURE_2D); + sceGuColor( 0xff0000ff ); +#endif + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, cvc, 0, cv ); +#if CLIPPING_DEBUGGING + sceGuEnable(GU_TEXTURE_2D); + sceGuColor( 0xffffffff ); +#endif + } + } + else sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, p->numverts, 0, &p->verts[0] ); + + if( hasScale ) sceGuTexScale( 1.0f, 1.0f ); + if( hasOffset ) sceGuTexOffset( 0.0f, 0.0f ); + + if( FBitSet( p->flags, SURF_DRAWTILED )) + GL_SetupFogColorForSurfaces(); +} + +/* +================ +DrawGLPolyChain + +Render lightmaps +================ +*/ +void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset ) +{ + qboolean dynamic = true; + + if( soffset == 0.0f && toffset == 0.0f ) + dynamic = false; + + if( dynamic ) sceGuTexOffset( -soffset, -toffset ); + + for( ; p != NULL; p = p->chain ) + { + float *v; + int i; + + if ( GU_ClipIsRequired( &p->verts[p->numverts], p->numverts ) ) + { + // clip the polygon. + gu_vert_t* cv; + int cvc; + + GU_Clip( &p->verts[p->numverts], p->numverts, &cv, &cvc ); + if( cvc ) sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, cvc, 0, cv ); + } + else sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, p->numverts, 0, &p->verts[p->numverts] ); + } + + if( dynamic ) sceGuTexOffset( 0.0f, 0.0f ); +} + +_inline qboolean R_HasLightmap( void ) +{ + if( CVAR_TO_BOOL( r_fullbright ) || !WORLDMODEL->lightdata ) + return false; + + if( RI.currententity ) + { + if( RI.currententity->curstate.effects & EF_FULLBRIGHT ) + return false; // disabled by user + + // check for rendermode + switch( RI.currententity->curstate.rendermode ) + { + case kRenderTransTexture: + case kRenderTransColor: + case kRenderTransAdd: + case kRenderGlow: + return false; // no lightmaps + } + } + + return true; +} + +/* +================ +R_BlendLightmaps +================ +*/ +void R_BlendLightmaps( void ) +{ + msurface_t *surf, *newsurf = NULL; + int i; + + if( !R_HasLightmap() ) + return; + + GL_SetupFogColorForSurfaces (); + + if( !CVAR_TO_BOOL( r_lightmap )) + sceGuEnable( GU_BLEND ); + else sceGuDisable( GU_BLEND ); + + // lightmapped solid surfaces + sceGuDepthMask( GU_TRUE ); + sceGuDepthFunc( GU_EQUAL ); + + sceGuDisable( GU_ALPHA_TEST ); + sceGuBlendFunc( GU_ADD, GU_FIX, GU_SRC_COLOR, GUBLEND0, 0 ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + + // render static lightmaps first + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + if( gl_lms.lightmap_surfaces[i] ) + { + GL_Bind( XASH_TEXTURE0, tr.lightmapTextures[i] ); + + for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain ) + { + if( surf->polys ) DrawGLPolyChain( surf->polys, 0.0f, 0.0f ); + } + } + } + + // render dynamic lightmaps + if( CVAR_TO_BOOL( r_dynamic )) + { + LM_InitBlock(); +#if 0 + GL_Bind( XASH_TEXTURE0, tr.dlightTexture ); +#endif + newsurf = gl_lms.dynamic_surfaces; + + for( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->info->lightmapchain ) + { + int smax, tmax; + int sample_size; + mextrasurf_t *info = surf->info; + byte *base; + + sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + + if( LM_AllocBlock( smax, tmax, &surf->info->dlight_s, &surf->info->dlight_t )) + { + base = gl_lms.lightmap_buffer; + base += ( surf->info->dlight_t * BLOCK_SIZE + surf->info->dlight_s ) * LIGHTMAP_BPP; + + R_BuildLightMap( surf, base, BLOCK_SIZE * LIGHTMAP_BPP, true ); + } + else + { + msurface_t *drawsurf; + + // upload what we have so far + LM_UploadBlock( true ); + + // draw all surfaces that use this lightmap + for( drawsurf = newsurf; drawsurf != surf; drawsurf = drawsurf->info->lightmapchain ) + { + if( drawsurf->polys ) + { + DrawGLPolyChain( drawsurf->polys, + ( drawsurf->light_s - drawsurf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ), + ( drawsurf->light_t - drawsurf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE )); + } + } + + newsurf = drawsurf; + + // clear the block + LM_InitBlock(); + + // try uploading the block now + if( !LM_AllocBlock( smax, tmax, &surf->info->dlight_s, &surf->info->dlight_t )) + gEngfuncs.Host_Error( "AllocBlock: full\n" ); + + base = gl_lms.lightmap_buffer; + base += ( surf->info->dlight_t * BLOCK_SIZE + surf->info->dlight_s ) * LIGHTMAP_BPP; + + R_BuildLightMap( surf, base, BLOCK_SIZE * LIGHTMAP_BPP, true ); + } + } + + // draw remainder of dynamic lightmaps that haven't been uploaded yet + if( newsurf ) LM_UploadBlock( true ); + + for( surf = newsurf; surf != NULL; surf = surf->info->lightmapchain ) + { + if( surf->polys ) + { + DrawGLPolyChain( surf->polys, + ( surf->light_s - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ), + ( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE )); + } + } + } + + sceGuDisable( GU_BLEND ); + sceGuDepthMask( GU_FALSE ); + sceGuDepthFunc( GU_LEQUAL ); + sceGuTexFunc( GU_TFX_REPLACE, GU_TCC_RGBA ); + sceGuColor( 0xffffffff ); + + // restore fog here + GL_ResetFogColor(); +} + +/* +================ +R_RenderFullbrights +================ +*/ +void R_RenderFullbrights( void ) +{ + mextrasurf_t *es, *p; + int i; + + if( !draw_fullbrights ) + return; + + R_AllowFog( false ); + + sceGuEnable( GU_BLEND ); + sceGuDepthMask( GU_TRUE ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuBlendFunc( GU_ADD, GU_FIX, GU_FIX, GUBLEND1, GUBLEND1 ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + + for( i = 1; i < MAX_TEXTURES; i++ ) + { + es = fullbright_surfaces[i]; + if( !es ) continue; + + GL_Bind( XASH_TEXTURE0, i ); + + for( p = es; p; p = p->lumachain ) + DrawGLPoly( p->surf->polys, 0.0f, 0.0f ); + + fullbright_surfaces[i] = NULL; + es->lumachain = NULL; + } + + sceGuDisable( GU_BLEND ); + sceGuDepthMask( GU_FALSE ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuTexFunc( GU_TFX_REPLACE, GU_TCC_RGBA ); + + draw_fullbrights = false; + R_AllowFog( true ); +} + +/* +================ +R_RenderDetails +================ +*/ +void R_RenderDetails( void ) +{ + gl_texture_t *glt; + mextrasurf_t *es, *p; + msurface_t *fa; + int i; + + if( !draw_details ) + return; + + GL_SetupFogColorForSurfaces(); + + sceGuEnable( GU_BLEND ); + sceGuBlendFunc( GU_ADD, GU_DST_COLOR, GU_SRC_COLOR, 0, 0 ); + sceGuTexFunc( GU_TFX_DECAL, GU_TCC_RGBA ); + sceGuDepthFunc( GU_EQUAL ); + + for( i = 1; i < MAX_TEXTURES; i++ ) + { + es = detail_surfaces[i]; + if( !es ) continue; + + GL_Bind( XASH_TEXTURE0, i ); + + for( p = es; p; p = p->detailchain ) + { + fa = p->surf; + glt = R_GetTexture( fa->texinfo->texture->gl_texturenum ); // get texture scale + + DrawGLPoly( fa->polys, glt->xscale, glt->yscale ); + } + + detail_surfaces[i] = NULL; + es->detailchain = NULL; + } + + sceGuDisable( GU_BLEND ); + sceGuTexFunc( GU_TFX_REPLACE, GU_TCC_RGBA ); + sceGuDepthFunc( GU_LEQUAL ); + + draw_details = false; + + // restore fog here + GL_ResetFogColor(); +} + +/* +================ +R_RenderBrushPoly +================ +*/ +void R_RenderBrushPoly( msurface_t *fa, int cull_type ) +{ + qboolean is_dynamic = false; + int maps; + texture_t *t; + + r_stats.c_world_polys++; + + if( fa->flags & SURF_DRAWSKY ) + return; // already handled + + t = R_TextureAnimation( fa ); + + GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); + + if( FBitSet( fa->flags, SURF_DRAWTURB )) + { + // warp texture, no lightmaps + EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE)); + return; + } + + if( t->fb_texturenum ) + { + fa->info->lumachain = fullbright_surfaces[t->fb_texturenum]; + fullbright_surfaces[t->fb_texturenum] = fa->info; + draw_fullbrights = true; + } + + if( CVAR_TO_BOOL( r_detailtextures )) + { + if( glState.isFogEnabled ) + { + // don't apply detail textures for windows in the fog + if( RI.currententity->curstate.rendermode != kRenderTransTexture ) + { + if( t->dt_texturenum ) + { + fa->info->detailchain = detail_surfaces[t->dt_texturenum]; + detail_surfaces[t->dt_texturenum] = fa->info; + } + else + { + // draw stub detail texture for underwater surfaces + fa->info->detailchain = detail_surfaces[tr.grayTexture]; + detail_surfaces[tr.grayTexture] = fa->info; + } + draw_details = true; + } + } + else if( t->dt_texturenum ) + { + fa->info->detailchain = detail_surfaces[t->dt_texturenum]; + detail_surfaces[t->dt_texturenum] = fa->info; + draw_details = true; + } + } + + DrawGLPoly( fa->polys, 0.0f, 0.0f ); + + if( RI.currententity->curstate.rendermode == kRenderNormal ) + { + // batch decals to draw later + if( tr.num_draw_decals < MAX_DECAL_SURFS && fa->pdecals ) + tr.draw_decals[tr.num_draw_decals++] = fa; + } + else + { + // if rendermode != kRenderNormal draw decals sequentially + DrawSurfaceDecals( fa, true, (cull_type == CULL_BACKSIDE)); + } + + if( FBitSet( fa->flags, SURF_DRAWTILED )) + return; // no lightmaps anyway + + // check for lightmap modification + for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ ) + { + if( tr.lightstylevalue[fa->styles[maps]] != fa->cached_light[maps] ) + goto dynamic; + } + + // dynamic this frame or dynamic previously + if( fa->dlightframe == tr.framecount ) + { +dynamic: + // NOTE: at this point we have only valid textures + if( r_dynamic->value ) is_dynamic = true; + } + + if( is_dynamic ) + { + if(( fa->styles[maps] >= 32 || fa->styles[maps] == 0 || fa->styles[maps] == 20 ) && ( fa->dlightframe != tr.framecount )) + { + byte temp[132*132*LIGHTMAP_BPP]; + mextrasurf_t *info = fa->info; + int sample_size; + int smax, tmax; + + sample_size = gEngfuncs.Mod_SampleSizeForFace( fa ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + + R_BuildLightMap( fa, temp, smax * LIGHTMAP_BPP, true ); + R_SetCacheState( fa ); + +#if 1 + GL_UpdateTexture( tr.lightmapTextures[fa->lightmaptexturenum], fa->light_s, fa->light_t, smax, tmax, temp ); +#else + GL_Bind( XASH_TEXTURE0, tr.lightmapTextures[fa->lightmaptexturenum] ); + + pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax, + GL_RGBA, GL_UNSIGNED_BYTE, temp ); +#endif + fa->info->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum]; + gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa; + } + else + { + fa->info->lightmapchain = gl_lms.dynamic_surfaces; + gl_lms.dynamic_surfaces = fa; + } + } + else + { + fa->info->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum]; + gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa; + } +} + +/* +================ +R_DrawTextureChains +================ +*/ +void R_DrawTextureChains( void ) +{ + int i; + msurface_t *s; + texture_t *t; + + // make sure what color is reset + sceGuColor( 0xffffffff ); + + R_LoadIdentity(); // set identity matrix + + GL_SetupFogColorForSurfaces(); + + // restore worldmodel + RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); + RI.currentmodel = RI.currententity->model; + + if( ENGINE_GET_PARM( PARM_SKY_SPHERE ) ) + { + sceGuDisable( GU_TEXTURE_2D ); + sceGuColor( 0xffffffff ); + } + + // clip skybox surfaces + for( s = skychain; s != NULL; s = s->texturechain ) + R_AddSkyBoxSurface( s ); + + if( ENGINE_GET_PARM( PARM_SKY_SPHERE ) ) + { + sceGuEnable( GU_TEXTURE_2D ); + if( skychain ) + R_DrawClouds(); + skychain = NULL; + } + + for( i = 0; i < WORLDMODEL->numtextures; i++ ) + { + t = WORLDMODEL->textures[i]; + if( !t ) continue; + + s = t->texturechain; + + if( !s || ( i == tr.skytexturenum )) + continue; + + if(( s->flags & SURF_DRAWTURB ) && MOVEVARS->wateralpha < 1.0f ) + continue; // draw translucent water later + + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && FBitSet( s->flags, SURF_TRANSPARENT )) + { + draw_alpha_surfaces = true; + continue; // draw transparent surfaces later + } + + for( ; s != NULL; s = s->texturechain ) + R_RenderBrushPoly( s, CULL_VISIBLE ); + t->texturechain = NULL; + } +} + +/* +================ +R_DrawAlphaTextureChains +================ +*/ +void R_DrawAlphaTextureChains( void ) +{ + int i; + msurface_t *s; + texture_t *t; + + if( !draw_alpha_surfaces ) + return; + + memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); + gl_lms.dynamic_surfaces = NULL; + + // make sure what color is reset + sceGuColor( 0xffffffff ); + + R_LoadIdentity(); // set identity matrix + + sceGuDisable( GU_BLEND ); + sceGuEnable( GU_ALPHA_TEST ); + sceGuAlphaFunc( GU_GREATER, 0x40, 0xff ); + + GL_SetupFogColorForSurfaces(); + + // restore worldmodel + RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); + RI.currentmodel = RI.currententity->model; + RI.currententity->curstate.rendermode = kRenderTransAlpha; + draw_alpha_surfaces = false; + + for( i = 0; i < WORLDMODEL->numtextures; i++ ) + { + t = WORLDMODEL->textures[i]; + if( !t ) continue; + + s = t->texturechain; + + if( !s || !FBitSet( s->flags, SURF_TRANSPARENT )) + continue; + + for( ; s != NULL; s = s->texturechain ) + R_RenderBrushPoly( s, CULL_VISIBLE ); + t->texturechain = NULL; + } + + GL_ResetFogColor(); + R_BlendLightmaps(); + RI.currententity->curstate.rendermode = kRenderNormal; // restore world rendermode + + sceGuAlphaFunc( GU_GREATER, DEFAULT_ALPHATEST, 0xff ); +} + +/* +================ +R_DrawWaterSurfaces +================ +*/ +void R_DrawWaterSurfaces( void ) +{ + int i; + msurface_t *s; + texture_t *t; + + if( !RI.drawWorld || RI.onlyClientDraw ) + return; + + // non-transparent water is already drawed + if( MOVEVARS->wateralpha >= 1.0f ) + return; + + // restore worldmodel + RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); + RI.currentmodel = RI.currententity->model; + + // go back to the world matrix + R_LoadIdentity(); + + sceGuEnable( GU_BLEND ); + sceGuDepthMask( GU_TRUE ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuColor( GUCOLOR4F( 1.0f, 1.0f, 1.0f, MOVEVARS->wateralpha ) ); + + for( i = 0; i < WORLDMODEL->numtextures; i++ ) + { + t = WORLDMODEL->textures[i]; + if( !t ) continue; + + s = t->texturechain; + if( !s ) continue; + + if( !FBitSet( s->flags, SURF_DRAWTURB )) + continue; + + // set modulate mode explicitly + GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); + + for( ; s; s = s->texturechain ) + EmitWaterPolys( s, false ); + + t->texturechain = NULL; + } + + sceGuDisable( GU_BLEND ); + sceGuDepthMask( GU_FALSE ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuTexFunc( GU_TFX_REPLACE, GU_TCC_RGBA ); + sceGuColor( 0xffffffff ); +} + +/* +================= +R_SurfaceCompare + +compare translucent surfaces +================= +*/ +static int R_SurfaceCompare( const void *a, const void *b ) +{ + msurface_t *surf1, *surf2; + vec3_t org1, org2; + float len1, len2; + + surf1 = (msurface_t *)((sortedface_t *)a)->surf; + surf2 = (msurface_t *)((sortedface_t *)b)->surf; + + VectorAdd( RI.currententity->origin, surf1->info->origin, org1 ); + VectorAdd( RI.currententity->origin, surf2->info->origin, org2 ); + + // compare by plane dists + len1 = DotProduct( org1, RI.vforward ) - RI.viewplanedist; + len2 = DotProduct( org2, RI.vforward ) - RI.viewplanedist; + + if( len1 > len2 ) + return -1; + if( len1 < len2 ) + return 1; + + return 0; +} + +void R_SetRenderMode( cl_entity_t *e ) +{ + switch( e->curstate.rendermode ) + { + case kRenderNormal: + sceGuColor( 0xffffffff ); + break; + case kRenderTransColor: + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuColor( GUCOLOR4UB( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, e->curstate.renderamt ) ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuDisable( GU_TEXTURE_2D ); + sceGuEnable( GU_BLEND ); + break; + case kRenderTransAdd: + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuColor( GUCOLOR4F( tr.blend, tr.blend, tr.blend, 1.0f ) ); + sceGuBlendFunc( GU_ADD, GU_FIX, GU_FIX, GUBLEND1, GUBLEND1 ); + sceGuDepthMask( GU_TRUE ); + sceGuEnable( GU_BLEND ); + break; + case kRenderTransAlpha: + sceGuEnable( GU_ALPHA_TEST ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) + { + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuColor( GUCOLOR4F( 1.0f, 1.0f, 1.0f, tr.blend ) ); + sceGuEnable( GU_BLEND ); + } + else + { + sceGuColor( 0xffffffff ); + sceGuDisable( GU_BLEND ); + } + sceGuAlphaFunc( GU_GREATER, 0x40, 0xff ); + break; + default: + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuColor( GUCOLOR4F( 1.0f, 1.0f, 1.0f, tr.blend ) ); + sceGuDepthMask( GU_TRUE ); + sceGuEnable( GU_BLEND ); + break; + } +} + +/* +================= +R_DrawBrushModel +================= +*/ +void R_DrawBrushModel( cl_entity_t *e ) +{ + int i, k, num_sorted; + vec3_t origin_l, oldorigin; + int old_rendermode; + vec3_t mins, maxs; + int cull_type; + msurface_t *psurf; + model_t *clmodel; + qboolean rotated; + dlight_t *l; + + if( !RI.drawWorld ) return; + + clmodel = e->model; + + if( !VectorIsNull( e->angles )) + { + for( i = 0; i < 3; i++ ) + { + mins[i] = e->origin[i] - clmodel->radius; + maxs[i] = e->origin[i] + clmodel->radius; + } + rotated = true; + } + else + { + VectorAdd( e->origin, clmodel->mins, mins ); + VectorAdd( e->origin, clmodel->maxs, maxs ); + rotated = false; + } + + if( R_CullBox( mins, maxs )) + return; + + memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); + old_rendermode = e->curstate.rendermode; + gl_lms.dynamic_surfaces = NULL; + + if( rotated ) R_RotateForEntity( e ); + else R_TranslateForEntity( e ); + + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && FBitSet( clmodel->flags, MODEL_TRANSPARENT )) + e->curstate.rendermode = kRenderTransAlpha; + + e->visframe = tr.realframecount; // visible + + if( rotated ) Matrix4x4_VectorITransform( RI.objectMatrix, RI.cullorigin, tr.modelorg ); + else VectorSubtract( RI.cullorigin, e->origin, tr.modelorg ); + + // calculate dynamic lighting for bmodel + for( k = 0, l = gEngfuncs.GetDynamicLight( 0 ); k < MAX_DLIGHTS; k++, l++ ) + { + if( l->die < gpGlobals->time || !l->radius ) + continue; + + VectorCopy( l->origin, oldorigin ); // save lightorigin + Matrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l ); + VectorCopy( origin_l, l->origin ); // move light in bmodel space + R_MarkLights( l, 1<nodes + clmodel->hulls[0].firstclipnode ); + VectorCopy( oldorigin, l->origin ); // restore lightorigin + } + + // setup the rendermode + R_SetRenderMode( e ); + GL_SetupFogColorForSurfaces (); + + if( e->curstate.rendermode == kRenderTransAdd ) + { + R_AllowFog( false ); + } + + psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; + + GU_ClipSetModelFrustum( RI.objectMatrix ); + + // sorting is not required, +Z_Realloc in Mod_LoadSubmodels (mod_bmodel.c) + for( i = 0; i < clmodel->nummodelsurfaces; i++, psurf++ ) + { + if( FBitSet( psurf->flags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) + { + if( psurf->plane->type != PLANE_Z && !FBitSet( e->curstate.effects, EF_WATERSIDES )) + continue; + if( mins[2] + 1.0f >= psurf->plane->dist ) + continue; + } + + cull_type = R_CullSurface( psurf, &RI.frustum, RI.frustum.clipFlags ); + + if( cull_type >= CULL_FRUSTUM ) + continue; + + if( cull_type == CULL_BACKSIDE ) + { + if( !FBitSet( psurf->flags, SURF_DRAWTURB ) && !( psurf->pdecals && e->curstate.rendermode == kRenderTransTexture )) + continue; + } + R_RenderBrushPoly( psurf, cull_type ); + } + + if( e->curstate.rendermode == kRenderTransColor ) + sceGuEnable( GU_TEXTURE_2D ); + + DrawDecalsBatch(); + GL_ResetFogColor(); + R_BlendLightmaps(); + R_RenderFullbrights(); + R_RenderDetails(); + + // restore fog here + if( e->curstate.rendermode == kRenderTransAdd ) + R_AllowFog( true ); + + e->curstate.rendermode = old_rendermode; + + sceGuDisable( GU_ALPHA_TEST ); + sceGuAlphaFunc( GU_GREATER, DEFAULT_ALPHATEST, 0xff ); + sceGuDisable( GU_BLEND ); + sceGuDepthMask( GU_FALSE ); + + R_DrawModelHull(); // draw before restore + GU_ClipRestoreWorldFrustum(); + R_LoadIdentity(); // restore worldmatrix +} + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ +/* +================ +R_RecursiveWorldNode +================ +*/ +void R_RecursiveWorldNode( mnode_t *node, uint clipflags ) +{ + int i, clipped; + msurface_t *surf, **mark; + mleaf_t *pleaf; + int c, side; + float dot; +loc0: + if( node->contents == CONTENTS_SOLID ) + return; // hit a solid leaf + + if( node->visframe != tr.visframecount ) + return; + + if( clipflags && !CVAR_TO_BOOL( r_nocull )) + { + for( i = 0; i < 6; i++ ) + { + const mplane_t *p = &RI.frustum.planes[i]; + + if( !FBitSet( clipflags, BIT( i ))) + continue; + + clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p ); + if( clipped == 2 ) return; + if( clipped == 1 ) ClearBits( clipflags, BIT( i )); + } + } + + // if a leaf node, draw stuff + if( node->contents < 0 ) + { + pleaf = (mleaf_t *)node; + + mark = pleaf->firstmarksurface; + c = pleaf->nummarksurfaces; + + if( c ) + { + do + { + (*mark)->visframe = tr.framecount; + mark++; + } while( --c ); + } + + // deal with model fragments in this leaf + if( pleaf->efrags ) + gEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount ); + + r_stats.c_world_leafs++; + return; + } + + // node is just a decision point, so go down the apropriate sides + + // find which side of the node we are on + dot = PlaneDiff( tr.modelorg, node->plane ); + side = (dot >= 0.0f) ? 0 : 1; + + // recurse down the children, front side first + R_RecursiveWorldNode( node->children[side], clipflags ); + + // draw stuff + for( c = node->numsurfaces, surf = WORLDMODEL->surfaces + node->firstsurface; c; c--, surf++ ) + { + if( R_CullSurface( surf, &RI.frustum, clipflags )) + continue; + + if( surf->flags & SURF_DRAWSKY ) + { + // make sky chain to right clip the skybox + surf->texturechain = skychain; + skychain = surf; + } + else + { + surf->texturechain = surf->texinfo->texture->texturechain; + surf->texinfo->texture->texturechain = surf; + } + } + + // recurse down the back side + node = node->children[!side]; + goto loc0; +} + +/* +================ +R_CullNodeTopView + +cull node by user rectangle (simple scissor) +================ +*/ +qboolean R_CullNodeTopView( mnode_t *node ) +{ + vec2_t delta, size; + vec3_t center, half; + + // build the node center and half-diagonal + VectorAverage( node->minmaxs, node->minmaxs + 3, center ); + VectorSubtract( node->minmaxs + 3, center, half ); + + // cull against the screen frustum or the appropriate area's frustum. + Vector2Subtract( center, world_orthocenter, delta ); + Vector2Add( half, world_orthohalf, size ); + + return ( fabs( delta[0] ) > size[0] ) || ( fabs( delta[1] ) > size[1] ); +} + +/* +================ +R_DrawTopViewLeaf +================ +*/ +static void R_DrawTopViewLeaf( mleaf_t *pleaf, uint clipflags ) +{ + msurface_t **mark, *surf; + int i; + + for( i = 0, mark = pleaf->firstmarksurface; i < pleaf->nummarksurfaces; i++, mark++ ) + { + surf = *mark; + + // don't process the same surface twice + if( surf->visframe == tr.framecount ) + continue; + + surf->visframe = tr.framecount; + + if( R_CullSurface( surf, &RI.frustum, clipflags )) + continue; + + if(!( surf->flags & SURF_DRAWSKY )) + { + surf->texturechain = surf->texinfo->texture->texturechain; + surf->texinfo->texture->texturechain = surf; + } + } + + // deal with model fragments in this leaf + if( pleaf->efrags ) + gEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount ); + + r_stats.c_world_leafs++; +} + +/* +================ +R_DrawWorldTopView +================ +*/ +void R_DrawWorldTopView( mnode_t *node, uint clipflags ) +{ + int i, c, clipped; + msurface_t *surf; + + do + { + if( node->contents == CONTENTS_SOLID ) + return; // hit a solid leaf + + if( node->visframe != tr.visframecount ) + return; + + if( clipflags && !r_nocull->value ) + { + for( i = 0; i < 6; i++ ) + { + const mplane_t *p = &RI.frustum.planes[i]; + + if( !FBitSet( clipflags, BIT( i ))) + continue; + + clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p ); + if( clipped == 2 ) return; + if( clipped == 1 ) ClearBits( clipflags, BIT( i )); + } + } + + // cull against the screen frustum or the appropriate area's frustum. + if( R_CullNodeTopView( node )) + return; + + // if a leaf node, draw stuff + if( node->contents < 0 ) + { + R_DrawTopViewLeaf( (mleaf_t *)node, clipflags ); + return; + } + + // draw stuff + for( c = node->numsurfaces, surf = WORLDMODEL->surfaces + node->firstsurface; c; c--, surf++ ) + { + // don't process the same surface twice + if( surf->visframe == tr.framecount ) + continue; + + surf->visframe = tr.framecount; + + if( R_CullSurface( surf, &RI.frustum, clipflags )) + continue; + + if(!( surf->flags & SURF_DRAWSKY )) + { + surf->texturechain = surf->texinfo->texture->texturechain; + surf->texinfo->texture->texturechain = surf; + } + } + + // recurse down both children, we don't care the order... + R_DrawWorldTopView( node->children[0], clipflags ); + node = node->children[1]; + + } while( node ); +} + +/* +============= +R_DrawTriangleOutlines +============= +*/ +void R_DrawTriangleOutlines( void ) +{ +#if 0 + int i, j; + msurface_t *surf; + glpoly_t *p; + float *v; + + if( !gl_wireframe->value ) + return; + + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + // render static surfaces first + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain ) + { + p = surf->polys; + for( ; p != NULL; p = p->chain ) + { + pglBegin( GL_POLYGON ); + v = p->verts[0]; + for( j = 0; j < p->numverts; j++, v += VERTEXSIZE ) + pglVertex3fv( v ); + pglEnd (); + } + } + } + + // render surfaces with dynamic lightmaps + for( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->info->lightmapchain ) + { + p = surf->polys; + + for( ; p != NULL; p = p->chain ) + { + pglBegin( GL_POLYGON ); + v = p->verts[0]; + for( j = 0; j < p->numverts; j++, v += VERTEXSIZE ) + pglVertex3fv( v ); + pglEnd (); + } + } + + pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + pglEnable( GL_DEPTH_TEST ); + pglEnable( GL_TEXTURE_2D ); +#endif +} + +/* +============= +R_DrawWorld +============= +*/ +void R_DrawWorld( void ) +{ + double start, end; + + // paranoia issues: when gl_renderer is "0" we need have something valid for currententity + // to prevent crashing until HeadShield drawing. + RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); + RI.currentmodel = RI.currententity->model; + + if( !RI.drawWorld || RI.onlyClientDraw ) + return; + + VectorCopy( RI.cullorigin, tr.modelorg ); + memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); + memset( fullbright_surfaces, 0, sizeof( fullbright_surfaces )); + memset( detail_surfaces, 0, sizeof( detail_surfaces )); + + gl_lms.dynamic_surfaces = NULL; + + sceGuDisable( GU_ALPHA_TEST ); + sceGuDisable( GU_BLEND ); + + tr.blend = 1.0f; + + R_ClearSkyBox (); + + start = gEngfuncs.pfnTime(); + if( RI.drawOrtho ) + R_DrawWorldTopView( WORLDMODEL->nodes, RI.frustum.clipFlags ); + else R_RecursiveWorldNode( WORLDMODEL->nodes, RI.frustum.clipFlags ); + end = gEngfuncs.pfnTime(); + + r_stats.t_world_node = end - start; + + start = gEngfuncs.pfnTime(); + + R_DrawTextureChains(); + + if( !ENGINE_GET_PARM( PARM_DEV_OVERVIEW )) + { + DrawDecalsBatch(); + GL_ResetFogColor(); + R_BlendLightmaps(); + R_RenderFullbrights(); + R_RenderDetails(); + + if( skychain ) + R_DrawSkyBox(); + } + + end = gEngfuncs.pfnTime(); + + r_stats.t_world_draw = end - start; + tr.num_draw_decals = 0; + skychain = NULL; + + R_DrawTriangleOutlines (); + + R_DrawWorldHull(); +} + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current leaf +=============== +*/ +void R_MarkLeaves( void ) +{ + qboolean novis = false; + qboolean force = false; + mleaf_t *leaf = NULL; + mnode_t *node; + vec3_t test; + int i; + + if( !RI.drawWorld ) return; + + if( FBitSet( r_novis->flags, FCVAR_CHANGED ) || tr.fResetVis ) + { + // force recalc viewleaf + ClearBits( r_novis->flags, FCVAR_CHANGED ); + tr.fResetVis = false; + RI.viewleaf = NULL; + } + + VectorCopy( RI.pvsorigin, test ); + + if( RI.viewleaf != NULL ) + { + // merge two leafs that can be a crossed-line contents + if( RI.viewleaf->contents == CONTENTS_EMPTY ) + { + VectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] - 16.0f ); + leaf = gEngfuncs.Mod_PointInLeaf( test, WORLDMODEL->nodes ); + } + else + { + VectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] + 16.0f ); + leaf = gEngfuncs.Mod_PointInLeaf( test, WORLDMODEL->nodes ); + } + + if(( leaf->contents != CONTENTS_SOLID ) && ( RI.viewleaf != leaf )) + force = true; + } + + if( RI.viewleaf == RI.oldviewleaf && RI.viewleaf != NULL && !force ) + return; + + // development aid to let you run around + // and see exactly where the pvs ends + if( r_lockpvs->value ) return; + + RI.oldviewleaf = RI.viewleaf; + tr.visframecount++; + + if( r_novis->value || RI.drawOrtho || !RI.viewleaf || !WORLDMODEL->visdata ) + novis = true; + + gEngfuncs.R_FatPVS( RI.pvsorigin, REFPVS_RADIUS, RI.visbytes, FBitSet( RI.params, RP_OLDVIEWLEAF ), novis ); + if( force && !novis ) gEngfuncs.R_FatPVS( test, REFPVS_RADIUS, RI.visbytes, true, novis ); + + for( i = 0; i < WORLDMODEL->numleafs; i++ ) + { + if( CHECKVISBIT( RI.visbytes, i )) + { + node = (mnode_t *)&WORLDMODEL->leafs[i+1]; + do + { + if( node->visframe == tr.visframecount ) + break; + node->visframe = tr.visframecount; + node = node->parent; + } while( node ); + } + } +} + +/* +======================== +GL_CreateSurfaceLightmap +======================== +*/ +void GL_CreateSurfaceLightmap( msurface_t *surf, model_t *loadmodel ) +{ + int smax, tmax; + int sample_size; + mextrasurf_t *info = surf->info; + byte *base; + + if( !loadmodel->lightdata ) + return; + + if( FBitSet( surf->flags, SURF_DRAWTILED ) ) + return; + + sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + + if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ) ) + { + LM_UploadBlock( false ); + LM_InitBlock(); + + if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ) ) + gEngfuncs.Host_Error( "AllocBlock: full\n" ); + } + + surf->lightmaptexturenum = gl_lms.current_lightmap_texture; + + base = gl_lms.lightmap_buffer; + base += ( surf->light_t * BLOCK_SIZE + surf->light_s ) * LIGHTMAP_BPP; + + R_SetCacheState( surf ); + R_BuildLightMap( surf, base, BLOCK_SIZE * LIGHTMAP_BPP, false ); +} + +/* +================== +GL_RebuildLightmaps + +Rebuilds the lightmap texture +when gamma is changed +================== +*/ +void GL_RebuildLightmaps( void ) +{ + int i, j; + model_t *m; + + if( !ENGINE_GET_PARM( PARM_CLIENT_ACTIVE ) ) + return; // wait for worldmodel + + ClearBits( vid_brightness->flags, FCVAR_CHANGED ); + ClearBits( vid_gamma->flags, FCVAR_CHANGED ); + + // release old lightmaps + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + if( !tr.lightmapTextures[i] ) break; + GL_FreeTexture( tr.lightmapTextures[i] ); + } + + memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures ) ); + gl_lms.current_lightmap_texture = 0; + + // setup all the lightstyles + CL_RunLightStyles(); + + LM_InitBlock(); + + for( i = 0; i < ENGINE_GET_PARM( PARM_NUMMODELS ); i++ ) + { + if( ( m = gEngfuncs.pfnGetModelByIndex( i + 1 ) ) == NULL ) + continue; + + if( m->name[0] == '*' || m->type != mod_brush ) + continue; + + for( j = 0; j < m->numsurfaces; j++ ) + GL_CreateSurfaceLightmap( m->surfaces + j, m ); + } + LM_UploadBlock( false ); + + if( gEngfuncs.drawFuncs->GL_BuildLightmaps ) + { + // build lightmaps on the client-side + gEngfuncs.drawFuncs->GL_BuildLightmaps( ); + } +} + +/* +================== +GL_BuildLightmaps + +Builds the lightmap texture +with all the surfaces from all brush models +================== +*/ +void GL_BuildLightmaps( void ) +{ + int i, j; + model_t *m; + + // release old lightmaps + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + if( !tr.lightmapTextures[i] ) break; + GL_FreeTexture( tr.lightmapTextures[i] ); + } + + memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures )); + memset( &RI, 0, sizeof( RI )); +#if 1 + tr.block_size = BLOCK_SIZE_DEFAULT; +#else + // update the lightmap blocksize + if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_LARGE_LIGHTMAPS )) + tr.block_size = BLOCK_SIZE_MAX; + else tr.block_size = BLOCK_SIZE_DEFAULT; +#endif + skychain = NULL; + + tr.framecount = tr.visframecount = 1; // no dlight cache + gl_lms.current_lightmap_texture = 0; + tr.modelviewIdentity = false; + tr.realframecount = 1; + nColinElim = 0; + + // setup the texture for dlights + R_InitDlightTexture(); + + // setup all the lightstyles + CL_RunLightStyles(); + + LM_InitBlock(); + + for( i = 0; i < ENGINE_GET_PARM( PARM_NUMMODELS ); i++ ) + { + if( ( m = gEngfuncs.pfnGetModelByIndex( i + 1 ) ) == NULL ) + continue; + + if( m->name[0] == '*' || m->type != mod_brush ) + continue; + + for( j = 0; j < m->numsurfaces; j++ ) + { + // clearing all decal chains + m->surfaces[j].pdecals = NULL; + m->surfaces[j].visframe = 0; + + GL_CreateSurfaceLightmap( m->surfaces + j, m ); + + if( m->surfaces[j].flags & SURF_DRAWTURB ) + continue; + + GL_BuildPolygonFromSurface( m, m->surfaces + j ); + } + + // clearing visframe + for( j = 0; j < m->numleafs; j++ ) + m->leafs[j+1].visframe = 0; + for( j = 0; j < m->numnodes; j++ ) + m->nodes[j].visframe = 0; + } + + LM_UploadBlock( false ); + + if( gEngfuncs.drawFuncs->GL_BuildLightmaps ) + { + // build lightmaps on the client-side + gEngfuncs.drawFuncs->GL_BuildLightmaps( ); + } + + // now gamma and brightness are valid + ClearBits( vid_brightness->flags, FCVAR_CHANGED ); + ClearBits( vid_gamma->flags, FCVAR_CHANGED ); +} + +void GL_InitRandomTable( void ) +{ + int tu, tv; + + // make random predictable + gEngfuncs.COM_SetRandomSeed( 255 ); + + for( tu = 0; tu < MOD_FRAMES; tu++ ) + { + for( tv = 0; tv < MOD_FRAMES; tv++ ) + { + rtable[tu][tv] = gEngfuncs.COM_RandomLong( 0, 0x7FFF ); + } + } + + gEngfuncs.COM_SetRandomSeed( 0 ); +} diff --git a/ref_gu/gu_sprite.c b/ref_gu/gu_sprite.c new file mode 100644 index 000000000..1e7881b68 --- /dev/null +++ b/ref_gu/gu_sprite.c @@ -0,0 +1,1020 @@ +/* +gu_sprite.c - sprite rendering +Copyright (C) 2010 Uncle Mike +Copyright (C) 2020 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "pm_local.h" +#include "sprite.h" +#include "studio.h" +#include "entity_types.h" +#include "cl_tent.h" + +// it's a Valve default value for LoadMapSprite (probably must be power of two) +#define MAPSPRITE_SIZE 128 +#define GLARE_FALLOFF 19000.0f + +cvar_t *r_sprite_lerping; +cvar_t *r_sprite_lighting; +char sprite_name[MAX_QPATH]; +char group_suffix[8]; +static uint r_texFlags = 0; +static int sprite_version; +float sprite_radius; + +/* +==================== +R_SpriteInit + +==================== +*/ +void R_SpriteInit( void ) +{ + r_sprite_lerping = gEngfuncs.Cvar_Get( "r_sprite_lerping", "1", FCVAR_ARCHIVE, "enables sprite animation lerping" ); + r_sprite_lighting = gEngfuncs.Cvar_Get( "r_sprite_lighting", "1", FCVAR_ARCHIVE, "enables sprite lighting (blood etc)" ); +} + +/* +==================== +R_SpriteLoadFrame + +upload a single frame +==================== +*/ +static const dframetype_t *R_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num ) +{ + dspriteframe_t pinframe; + mspriteframe_t *pspriteframe; + int gl_texturenum = 0; + char texname[128]; + int bytes = 1; + + memcpy( &pinframe, pin, sizeof(dspriteframe_t)); + + if( sprite_version == SPRITE_VERSION_32 ) + bytes = 4; + + // build uinque frame name + if( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite + { + Q_snprintf( texname, sizeof( texname ), "#HUD/%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 ); + gl_texturenum = GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags ); + } + else + { + Q_snprintf( texname, sizeof( texname ), "#%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 ); + gl_texturenum = GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags ); + } + + // setup frame description + pspriteframe = Mem_Malloc( mod->mempool, sizeof( mspriteframe_t )); + pspriteframe->width = pinframe.width; + pspriteframe->height = pinframe.height; + pspriteframe->up = pinframe.origin[1]; + pspriteframe->left = pinframe.origin[0]; + pspriteframe->down = pinframe.origin[1] - pinframe.height; + pspriteframe->right = pinframe.width + pinframe.origin[0]; + pspriteframe->gl_texturenum = gl_texturenum; + *ppframe = pspriteframe; + + return ( const dframetype_t* )(( const byte* )pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes ); +} + +/* +==================== +R_SpriteLoadGroup + +upload a group frames +==================== +*/ +static const dframetype_t *R_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum ) +{ + const dspritegroup_t *pingroup; + mspritegroup_t *pspritegroup; + const dspriteinterval_t *pin_intervals; + float *poutintervals; + int i, groupsize, numframes; + const void *ptemp; + + pingroup = (const dspritegroup_t *)pin; + numframes = pingroup->numframes; + + groupsize = sizeof( mspritegroup_t ) + (numframes - 1) * sizeof( pspritegroup->frames[0] ); + pspritegroup = Mem_Calloc( mod->mempool, groupsize ); + pspritegroup->numframes = numframes; + + *ppframe = (mspriteframe_t *)pspritegroup; + pin_intervals = (const dspriteinterval_t *)(pingroup + 1); + poutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float )); + pspritegroup->intervals = poutintervals; + + for( i = 0; i < numframes; i++ ) + { + *poutintervals = pin_intervals->interval; + if( *poutintervals <= 0.0f ) + *poutintervals = 1.0f; // set error value + poutintervals++; + pin_intervals++; + } + + ptemp = (const void *)pin_intervals; + for( i = 0; i < numframes; i++ ) + { + ptemp = R_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i ); + } + + return (const dframetype_t *)ptemp; +} + +/* +==================== +Mod_LoadSpriteModel + +load sprite model +==================== +*/ +void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags ) +{ + const dsprite_t *pin; + const short *numi = NULL; + const dframetype_t *pframetype; + msprite_t *psprite; + int i; + + pin = buffer; + psprite = mod->cache.data; + + if( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 ) + numi = NULL; + else if( pin->version == SPRITE_VERSION_HL ) + numi = (const short *)((const byte*)buffer + sizeof( dsprite_hl_t )); + + r_texFlags = texFlags; + sprite_version = pin->version; + Q_strncpy( sprite_name, mod->name, sizeof( sprite_name )); + COM_StripExtension( sprite_name ); + + if( numi == NULL ) + { + rgbdata_t *pal; + + pal = gEngfuncs.FS_LoadImage( "#id.pal", (byte *)&i, 768 ); + pframetype = (const dframetype_t *)((const byte*)buffer + sizeof( dsprite_q1_t )); // pinq1 + 1 + gEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data + } + else if( *numi == 256 ) + { + const byte *src = (const byte *)(numi+1); + rgbdata_t *pal; + + // install palette + switch( psprite->texFormat ) + { + case SPR_INDEXALPHA: + pal = gEngfuncs.FS_LoadImage( "#gradient.pal", src, 768 ); + break; + case SPR_ALPHTEST: + pal = gEngfuncs.FS_LoadImage( "#masked.pal", src, 768 ); + break; + default: + pal = gEngfuncs.FS_LoadImage( "#normal.pal", src, 768 ); + break; + } + + pframetype = (const dframetype_t *)(src + 768); + gEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data + } + else + { + gEngfuncs.Con_DPrintf( S_ERROR "%s has wrong number of palette colors %i (should be 256)\n", mod->name, *numi ); + return; + } + + if( mod->numframes < 1 ) + return; + + for( i = 0; i < mod->numframes; i++ ) + { + frametype_t frametype = pframetype->type; + psprite->frames[i].type = (spriteframetype_t)frametype; + + switch( frametype ) + { + case FRAME_SINGLE: + Q_strncpy( group_suffix, "frame", sizeof( group_suffix )); + pframetype = R_SpriteLoadFrame( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + break; + case FRAME_GROUP: + Q_strncpy( group_suffix, "group", sizeof( group_suffix )); + pframetype = R_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + break; + case FRAME_ANGLED: + Q_strncpy( group_suffix, "angle", sizeof( group_suffix )); + pframetype = R_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + break; + } + if( pframetype == NULL ) break; // technically an error + } + + if( loaded ) *loaded = true; // done +} + +/* +==================== +Mod_LoadMapSprite + +Loading a bitmap image as sprite with multiple frames +as pieces of input image +==================== +*/ +void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean *loaded ) +{ + byte *src, *dst; + rgbdata_t *pix, temp; + char texname[128]; + int i, j, x, y, w, h; + int xl, yl, xh, yh; + int linedelta, numframes; + mspriteframe_t *pspriteframe; + msprite_t *psprite; + + if( loaded ) *loaded = false; + Q_snprintf( texname, sizeof( texname ), "#%s", mod->name ); + gEngfuncs.Image_SetForceFlags( IL_OVERVIEW ); + pix = gEngfuncs.FS_LoadImage( texname, buffer, size ); + gEngfuncs.Image_ClearForceFlags(); + if( !pix ) return; // bad image or something else + + mod->type = mod_sprite; + r_texFlags = 0; // no custom flags for map sprites + + if( pix->width % MAPSPRITE_SIZE ) + w = pix->width - ( pix->width % MAPSPRITE_SIZE ); + else w = pix->width; + + if( pix->height % MAPSPRITE_SIZE ) + h = pix->height - ( pix->height % MAPSPRITE_SIZE ); + else h = pix->height; + + if( w < MAPSPRITE_SIZE ) w = MAPSPRITE_SIZE; + if( h < MAPSPRITE_SIZE ) h = MAPSPRITE_SIZE; + + // resample image if needed + gEngfuncs.Image_Process( &pix, w, h, IMAGE_FORCE_RGBA|IMAGE_RESAMPLE, 0.0f ); + + w = h = MAPSPRITE_SIZE; + + // check range + if( w > pix->width ) w = pix->width; + if( h > pix->height ) h = pix->height; + + // determine how many frames we needs + numframes = (pix->width * pix->height) / (w * h); + mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + psprite = Mem_Calloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames )); + mod->cache.data = psprite; // make link to extradata + + psprite->type = SPR_FWD_PARALLEL_ORIENTED; + psprite->texFormat = SPR_ALPHTEST; + psprite->numframes = mod->numframes = numframes; + psprite->radius = sqrt(((w >> 1) * (w >> 1)) + ((h >> 1) * (h >> 1))); + + mod->mins[0] = mod->mins[1] = -w / 2; + mod->maxs[0] = mod->maxs[1] = w / 2; + mod->mins[2] = -h / 2; + mod->maxs[2] = h / 2; + + // create a temporary pic + memset( &temp, 0, sizeof( temp )); + temp.width = w; + temp.height = h; + temp.type = pix->type; + temp.flags = pix->flags; + temp.size = w * h * gEngfuncs.Image_GetPFDesc(temp.type)->bpp; + temp.buffer = Mem_Malloc( r_temppool, temp.size ); + temp.palette = NULL; + + // chop the image and upload into video memory + for( i = xl = yl = 0; i < numframes; i++ ) + { + xh = xl + w; + yh = yl + h; + + src = pix->buffer + ( yl * pix->width + xl ) * 4; + linedelta = ( pix->width - w ) * 4; + dst = temp.buffer; + + // cut block from source + for( y = yl; y < yh; y++ ) + { + for( x = xl; x < xh; x++ ) + for( j = 0; j < 4; j++ ) + *dst++ = *src++; + src += linedelta; + } + + // build uinque frame name + Q_snprintf( texname, sizeof( texname ), "#MAP/%s_%i%i.spr", mod->name, i / 10, i % 10 ); + + psprite->frames[i].frameptr = Mem_Calloc( mod->mempool, sizeof( mspriteframe_t )); + pspriteframe = psprite->frames[i].frameptr; + pspriteframe->width = w; + pspriteframe->height = h; + pspriteframe->up = ( h >> 1 ); + pspriteframe->left = -( w >> 1 ); + pspriteframe->down = ( h >> 1 ) - h; + pspriteframe->right = w + -( w >> 1 ); + pspriteframe->gl_texturenum = GL_LoadTextureInternal( texname, &temp, TF_IMAGE ); + + xl += w; + if( xl >= pix->width ) + { + xl = 0; + yl += h; + } + } + + gEngfuncs.FS_FreeImage( pix ); + Mem_Free( temp.buffer ); + + if( loaded ) *loaded = true; +} + +/* +==================== +Mod_UnloadSpriteModel + +release sprite model and frames +==================== +*/ +void Mod_SpriteUnloadTextures( void *data ) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe; + int i, j; + + psprite = data; + + if( psprite ) + { + // release all textures + for( i = 0; i < psprite->numframes; i++ ) + { + if( psprite->frames[i].type == SPR_SINGLE ) + { + pspriteframe = psprite->frames[i].frameptr; + GL_FreeTexture( pspriteframe->gl_texturenum ); + } + else + { + pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr; + + for( j = 0; j < pspritegroup->numframes; j++ ) + { + pspriteframe = pspritegroup->frames[i]; + GL_FreeTexture( pspriteframe->gl_texturenum ); + } + } + } + } +} + +/* +================ +R_GetSpriteFrame + +assume pModel is valid +================ +*/ +mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe = NULL; + float *pintervals, fullinterval; + int i, numframes; + float targettime; + + Assert( pModel != NULL ); + psprite = pModel->cache.data; + + if( frame < 0 ) + { + frame = 0; + } + else if( frame >= psprite->numframes ) + { + if( frame > psprite->numframes ) + gEngfuncs.Con_Printf( S_WARN "R_GetSpriteFrame: no such frame %d (%s)\n", frame, pModel->name ); + frame = psprite->numframes - 1; + } + + if( psprite->frames[frame].type == SPR_SINGLE ) + { + pspriteframe = psprite->frames[frame].frameptr; + } + else if( psprite->frames[frame].type == SPR_GROUP ) + { + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pintervals = pspritegroup->intervals; + numframes = pspritegroup->numframes; + fullinterval = pintervals[numframes-1]; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by zero + targettime = gpGlobals->time - ((int)( gpGlobals->time / fullinterval )) * fullinterval; + + for( i = 0; i < (numframes - 1); i++ ) + { + if( pintervals[i] > targettime ) + break; + } + pspriteframe = pspritegroup->frames[i]; + } + else if( psprite->frames[frame].type == FRAME_ANGLED ) + { + int angleframe = (int)(Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7; + + // e.g. doom-style sprite monsters + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pspriteframe = pspritegroup->frames[angleframe]; + } + + return pspriteframe; +} + +/* +================ +R_GetSpriteFrameInterpolant + +NOTE: we using prevblending[0] and [1] for holds interval +between frames where are we lerping +================ +*/ +float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe ) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + int i, j, numframes, frame; + float lerpFrac, time, jtime, jinterval; + float *pintervals, fullinterval, targettime; + int m_fDoInterp; + + psprite = ent->model->cache.data; + frame = (int)ent->curstate.frame; + lerpFrac = 1.0f; + + // misc info + m_fDoInterp = (ent->curstate.effects & EF_NOINTERP) ? false : true; + + if( frame < 0 ) + { + frame = 0; + } + else if( frame >= psprite->numframes ) + { + gEngfuncs.Con_Reportf( S_WARN "R_GetSpriteFrameInterpolant: no such frame %d (%s)\n", frame, ent->model->name ); + frame = psprite->numframes - 1; + } + + if( psprite->frames[frame].type == FRAME_SINGLE ) + { + if( m_fDoInterp ) + { + if( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != FRAME_SINGLE ) + { + // this can be happens when rendering switched between single and angled frames + // or change model on replace delta-entity + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = gpGlobals->time; + lerpFrac = 1.0f; + } + + if( ent->latched.sequencetime < gpGlobals->time ) + { + if( frame != ent->latched.prevblending[1] ) + { + ent->latched.prevblending[0] = ent->latched.prevblending[1]; + ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = gpGlobals->time; + lerpFrac = 0.0f; + } + else lerpFrac = (gpGlobals->time - ent->latched.sequencetime) * 11.0f; + } + else + { + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = gpGlobals->time; + lerpFrac = 0.0f; + } + } + else + { + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + lerpFrac = 1.0f; + } + + if( ent->latched.prevblending[0] >= psprite->numframes ) + { + // reset interpolation on change model + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = gpGlobals->time; + lerpFrac = 0.0f; + } + + // get the interpolated frames + if( oldframe ) *oldframe = psprite->frames[ent->latched.prevblending[0]].frameptr; + if( curframe ) *curframe = psprite->frames[frame].frameptr; + } + else if( psprite->frames[frame].type == FRAME_GROUP ) + { + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pintervals = pspritegroup->intervals; + numframes = pspritegroup->numframes; + fullinterval = pintervals[numframes-1]; + jinterval = pintervals[1] - pintervals[0]; + time = gpGlobals->time; + jtime = 0.0f; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by zero + targettime = time - ((int)(time / fullinterval)) * fullinterval; + + // LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0, + // i instead measure the time of the first frame, hoping it is consistent + for( i = 0, j = numframes - 1; i < (numframes - 1); i++ ) + { + if( pintervals[i] > targettime ) + break; + j = i; + jinterval = pintervals[i] - jtime; + jtime = pintervals[i]; + } + + if( m_fDoInterp ) + lerpFrac = (targettime - jtime) / jinterval; + else j = i; // no lerping + + // get the interpolated frames + if( oldframe ) *oldframe = pspritegroup->frames[j]; + if( curframe ) *curframe = pspritegroup->frames[i]; + } + else if( psprite->frames[frame].type == FRAME_ANGLED ) + { + // e.g. doom-style sprite monsters + float yaw = ent->angles[YAW]; + int angleframe = (int)(Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7; + + if( m_fDoInterp ) + { + if( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != FRAME_ANGLED ) + { + // this can be happens when rendering switched between single and angled frames + // or change model on replace delta-entity + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = gpGlobals->time; + lerpFrac = 1.0f; + } + + if( ent->latched.sequencetime < gpGlobals->time ) + { + if( frame != ent->latched.prevblending[1] ) + { + ent->latched.prevblending[0] = ent->latched.prevblending[1]; + ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = gpGlobals->time; + lerpFrac = 0.0f; + } + else lerpFrac = (gpGlobals->time - ent->latched.sequencetime) * ent->curstate.framerate; + } + else + { + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = gpGlobals->time; + lerpFrac = 0.0f; + } + } + else + { + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + lerpFrac = 1.0f; + } + + pspritegroup = (mspritegroup_t *)psprite->frames[ent->latched.prevblending[0]].frameptr; + if( oldframe ) *oldframe = pspritegroup->frames[angleframe]; + + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + if( curframe ) *curframe = pspritegroup->frames[angleframe]; + } + + return lerpFrac; +} + +/* +================ +R_CullSpriteModel + +Cull sprite model by bbox +================ +*/ +qboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin ) +{ + vec3_t sprite_mins, sprite_maxs; + float scale = 1.0f; + + if( !e->model->cache.data ) + return true; + + if( e->curstate.scale > 0.0f ) + scale = e->curstate.scale; + + // scale original bbox (no rotation for sprites) + VectorScale( e->model->mins, scale, sprite_mins ); + VectorScale( e->model->maxs, scale, sprite_maxs ); + + sprite_radius = RadiusFromBounds( sprite_mins, sprite_maxs ); + + VectorAdd( sprite_mins, origin, sprite_mins ); + VectorAdd( sprite_maxs, origin, sprite_maxs ); + + return R_CullModel( e, sprite_mins, sprite_maxs ); +} + +/* +================ +R_GlowSightDistance + +Set sprite brightness factor +================ +*/ +static float R_SpriteGlowBlend( vec3_t origin, int rendermode, int renderfx, float *pscale ) +{ + float dist, brightness; + vec3_t glowDist; + pmtrace_t *tr; + + VectorSubtract( origin, RI.vieworg, glowDist ); + dist = VectorLength( glowDist ); + + if( RP_NORMALPASS( )) + { + tr = gEngfuncs.EV_VisTraceLine( RI.vieworg, origin, r_traceglow->value ? PM_GLASS_IGNORE : (PM_GLASS_IGNORE|PM_STUDIO_IGNORE)); + + if(( 1.0f - tr->fraction ) * dist > 8.0f ) + return 0.0f; + } + + if( renderfx == kRenderFxNoDissipation ) + return 1.0f; + + brightness = GLARE_FALLOFF / ( dist * dist ); + brightness = bound( 0.05f, brightness, 1.0f ); + *pscale *= dist * ( 1.0f / 200.0f ); + + return brightness; +} + +/* +================ +R_SpriteOccluded + +Do occlusion test for glow-sprites +================ +*/ +qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale ) +{ + if( e->curstate.rendermode == kRenderGlow ) + { + float blend; + vec3_t v; + + TriWorldToScreen( origin, v ); + + if( v[0] < RI.viewport[0] || v[0] > RI.viewport[0] + RI.viewport[2] ) + return true; // do scissor + if( v[1] < RI.viewport[1] || v[1] > RI.viewport[1] + RI.viewport[3] ) + return true; // do scissor + + blend = R_SpriteGlowBlend( origin, e->curstate.rendermode, e->curstate.renderfx, pscale ); + tr.blend *= blend; + + if( blend <= 0.01f ) + return true; // faded + } + else + { + if( R_CullSpriteModel( e, origin )) + return true; + } + + return false; +} + +/* +================= +R_DrawSpriteQuad +================= +*/ +static void R_DrawSpriteQuad( mspriteframe_t *frame, vec3_t org, vec3_t v_right, vec3_t v_up, float scale ) +{ + r_stats.c_sprite_polys++; + + gu_vert_t* const out = ( gu_vert_t* )sceGuGetMemory( sizeof( gu_vert_t ) * 4 ); + out[0].uv[0] = 0.0f; + out[0].uv[1] = 1.0f; + VectorMA( org, frame->down * scale, v_up, out[0].xyz ); + VectorMA( out[0].xyz, frame->left * scale, v_right, out[0].xyz ); + out[1].uv[0] = 0.0f; + out[1].uv[1] = 0.0f; + VectorMA( org, frame->up * scale, v_up, out[1].xyz ); + VectorMA( out[1].xyz, frame->left * scale, v_right, out[1].xyz ); + out[2].uv[0] = 1.0f; + out[2].uv[1] = 0.0f; + VectorMA( org, frame->up * scale, v_up, out[2].xyz ); + VectorMA( out[2].xyz, frame->right * scale, v_right, out[2].xyz ); + out[3].uv[0] = 1.0f; + out[3].uv[1] = 1.0f; + VectorMA( org, frame->down * scale, v_up, out[3].xyz ); + VectorMA( out[3].xyz, frame->right * scale, v_right, out[3].xyz ); + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, 4, 0, out ); +} + +static qboolean R_SpriteHasLightmap( cl_entity_t *e, int texFormat ) +{ + if( !r_sprite_lighting->value ) + return false; + + if( texFormat != SPR_ALPHTEST ) + return false; + + if( e->curstate.effects & EF_FULLBRIGHT ) + return false; + + if( e->curstate.renderamt <= 127 ) + return false; + + switch( e->curstate.rendermode ) + { + case kRenderNormal: + case kRenderTransAlpha: + case kRenderTransTexture: + break; + default: + return false; + } + + return true; +} + +/* +================= +R_SpriteAllowLerping +================= +*/ +static qboolean R_SpriteAllowLerping( cl_entity_t *e, msprite_t *psprite ) +{ + if( !r_sprite_lerping->value ) + return false; + + if( psprite->numframes <= 1 ) + return false; + + if( psprite->texFormat != SPR_ADDITIVE ) + return false; + + if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha ) + return false; + + return true; +} + +/* +================= +R_DrawSpriteModel +================= +*/ +void R_DrawSpriteModel( cl_entity_t *e ) +{ + mspriteframe_t *frame, *oldframe; + msprite_t *psprite; + model_t *model; + int i, type; + float angle, dot, sr, cr; + float lerp = 1.0f, ilerp, scale; + vec3_t v_forward, v_right, v_up; + vec3_t origin, color, color2 = { 0.0f }; + + if( RI.params & RP_ENVVIEW ) + return; + + model = e->model; + psprite = (msprite_t * )model->cache.data; + VectorCopy( e->origin, origin ); // set render origin + + // do movewith + if( e->curstate.aiment > 0 && e->curstate.movetype == MOVETYPE_FOLLOW ) + { + cl_entity_t *parent; + + parent = gEngfuncs.GetEntityByIndex( e->curstate.aiment ); + + if( parent && parent->model ) + { + if( parent->model->type == mod_studio && e->curstate.body > 0 ) + { + int num = bound( 1, e->curstate.body, MAXSTUDIOATTACHMENTS ); + VectorCopy( parent->attachment[num-1], origin ); + } + else VectorCopy( parent->origin, origin ); + } + } + + scale = e->curstate.scale; + if( !scale ) scale = 1.0f; + + if( R_SpriteOccluded( e, origin, &scale )) + return; // sprite culled + + r_stats.c_sprite_models_drawn++; + + if( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd ) + R_AllowFog( false ); + + // select properly rendermode + switch( e->curstate.rendermode ) + { + case kRenderTransAlpha: + sceGuDepthMask( GU_TRUE ); + // fallthrough + case kRenderTransColor: + case kRenderTransTexture: + sceGuEnable( GU_BLEND ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + break; + case kRenderGlow: + sceGuDisable( GU_DEPTH_TEST ); + // fallthrough + case kRenderTransAdd: + sceGuEnable( GU_BLEND ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, GUBLEND1 ); + sceGuDepthMask( GU_TRUE ); + break; + case kRenderNormal: + default: + sceGuDisable( GU_BLEND ); + break; + } + + // all sprites can have color + sceGuTexFunc(GU_TFX_MODULATE , GU_TCC_RGBA); + sceGuEnable( GU_ALPHA_TEST ); + + // NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug + if( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b ) + { + color[0] = (float)e->curstate.rendercolor.r * ( 1.0f / 255.0f ); + color[1] = (float)e->curstate.rendercolor.g * ( 1.0f / 255.0f ); + color[2] = (float)e->curstate.rendercolor.b * ( 1.0f / 255.0f ); + } + else + { + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 1.0f; + } + + if( R_SpriteHasLightmap( e, psprite->texFormat )) + { + colorVec lightColor = R_LightPoint( origin ); + // FIXME: collect light from dlights? + color2[0] = (float)lightColor.r * ( 1.0f / 255.0f ); + color2[1] = (float)lightColor.g * ( 1.0f / 255.0f ); + color2[2] = (float)lightColor.b * ( 1.0f / 255.0f ); + + // NOTE: sprites with 'lightmap' looks ugly when alpha func is GL_GREATER 0.0 + sceGuAlphaFunc( GU_GREATER, 0x7f, 0xff ); + } + + if( R_SpriteAllowLerping( e, psprite )) + lerp = R_GetSpriteFrameInterpolant( e, &oldframe, &frame ); + else frame = oldframe = R_GetSpriteFrame( model, e->curstate.frame, e->angles[YAW] ); + + type = psprite->type; + + // automatically roll parallel sprites if requested + if( e->angles[ROLL] != 0.0f && type == SPR_FWD_PARALLEL ) + type = SPR_FWD_PARALLEL_ORIENTED; + + switch( type ) + { + case SPR_ORIENTED: + AngleVectors( e->angles, v_forward, v_right, v_up ); + VectorScale( v_forward, 0.01f, v_forward ); // to avoid z-fighting + VectorSubtract( origin, v_forward, origin ); + break; + case SPR_FACING_UPRIGHT: + VectorSet( v_right, origin[1] - RI.vieworg[1], -(origin[0] - RI.vieworg[0]), 0.0f ); + VectorSet( v_up, 0.0f, 0.0f, 1.0f ); + VectorNormalize( v_right ); + break; + case SPR_FWD_PARALLEL_UPRIGHT: + dot = RI.vforward[2]; + if(( dot > 0.999848f ) || ( dot < -0.999848f )) // cos(1 degree) = 0.999848 + return; // invisible + VectorSet( v_up, 0.0f, 0.0f, 1.0f ); + VectorSet( v_right, RI.vforward[1], -RI.vforward[0], 0.0f ); + VectorNormalize( v_right ); + break; + case SPR_FWD_PARALLEL_ORIENTED: + angle = e->angles[ROLL] * (M_PI2 / 360.0f); + SinCos( angle, &sr, &cr ); + for( i = 0; i < 3; i++ ) + { + v_right[i] = (RI.vright[i] * cr + RI.vup[i] * sr); + v_up[i] = RI.vright[i] * -sr + RI.vup[i] * cr; + } + break; + case SPR_FWD_PARALLEL: // normal sprite + default: + VectorCopy( RI.vright, v_right ); + VectorCopy( RI.vup, v_up ); + break; + } + + if( psprite->facecull == SPR_CULL_NONE ) + GL_Cull( GL_NONE ); + + if( oldframe == frame ) + { + // draw the single non-lerped frame + sceGuColor( GUCOLOR4F( color[0], color[1], color[2], tr.blend) ); + GL_Bind( XASH_TEXTURE0, frame->gl_texturenum ); + R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); + } + else + { + // draw two combined lerped frames + lerp = bound( 0.0f, lerp, 1.0f ); + ilerp = 1.0f - lerp; + + if( ilerp != 0.0f ) + { + sceGuColor( GUCOLOR4F( color[0], color[1], color[2], tr.blend * ilerp ) ); + GL_Bind( XASH_TEXTURE0, oldframe->gl_texturenum ); + R_DrawSpriteQuad( oldframe, origin, v_right, v_up, scale ); + } + + if( lerp != 0.0f ) + { + sceGuColor( GUCOLOR4F( color[0], color[1], color[2], tr.blend * ilerp ) ); + GL_Bind( XASH_TEXTURE0, frame->gl_texturenum ); + R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); + } + } + + // draw the sprite 'lightmap' :-) + if( R_SpriteHasLightmap( e, psprite->texFormat )) + { + if( !r_lightmap->value ) + sceGuEnable( GU_BLEND ); + else sceGuDisable( GU_BLEND ); + sceGuDepthFunc( GU_EQUAL ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuBlendFunc( GU_ADD, GU_FIX, GU_SRC_COLOR, GUBLEND0, 0 ); + sceGuTexFunc( GU_TFX_MODULATE , GU_TCC_RGBA ); + + sceGuColor( GUCOLOR4F( color2[0], color2[1], color2[2], tr.blend ) ); + GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); + R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); + sceGuAlphaFunc( GU_GREATER, DEFAULT_ALPHATEST, 0xff ); + sceGuDepthFunc( GU_LEQUAL ); + } + + if( psprite->facecull == SPR_CULL_NONE ) + GL_Cull( GL_FRONT ); + + sceGuDisable( GU_ALPHA_TEST ); + sceGuDepthMask( GU_FALSE ); + + if( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd ) + R_AllowFog( true ); + + if( e->curstate.rendermode != kRenderNormal ) + { + sceGuDisable( GU_BLEND ); + sceGuTexFunc( GU_TFX_REPLACE, GU_TCC_RGBA ); + sceGuEnable( GU_DEPTH_TEST ); + } +} diff --git a/ref_gu/gu_studio.c b/ref_gu/gu_studio.c new file mode 100644 index 000000000..3c79d5269 --- /dev/null +++ b/ref_gu/gu_studio.c @@ -0,0 +1,4137 @@ +/* +gu_studio.c - studio model renderer +Copyright (C) 2010 Uncle Mike +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "xash3d_mathlib.h" +#include "const.h" +#include "r_studioint.h" +#include "triangleapi.h" +#include "studio.h" +#include "pm_local.h" +#include "cl_tent.h" +//#include "client.h" +#include "pmtrace.h" + +//#define STUDIO_DRAWELEMENTS + +#undef MAXSTUDIOVERTS +#define MAXSTUDIOVERTS 1024 + +#define EVENT_CLIENT 5000 // less than this value it's a server-side studio events +#define MAX_LOCALLIGHTS 4 + +#define STUDIOEXTRADATA( mod ) ((( mod ) && ( mod )->type == mod_studio ) ? ( mod )->cache.data : NULL ) + +typedef struct +{ + char name[MAX_OSPATH]; + char modelname[MAX_OSPATH]; + model_t *model; +} player_model_t; + +cvar_t *r_glowshellfreq; + +cvar_t r_shadows = { "r_shadows", "0", 0 }; + +static vec3_t hullcolor[8] = +{ +{ 1.0f, 1.0f, 1.0f }, +{ 1.0f, 0.5f, 0.5f }, +{ 0.5f, 1.0f, 0.5f }, +{ 1.0f, 1.0f, 0.5f }, +{ 0.5f, 0.5f, 1.0f }, +{ 1.0f, 0.5f, 1.0f }, +{ 0.5f, 1.0f, 1.0f }, +{ 1.0f, 1.0f, 1.0f }, +}; + +typedef struct sortedmesh_s +{ + mstudiomesh_t *mesh; + int flags; // face flags +} sortedmesh_t; + +typedef struct +{ + double time; + double frametime; + int framecount; // studio framecount + qboolean interpolate; + int rendermode; + float blend; // blend value + + // bones + matrix3x4 rotationmatrix; + matrix3x4 bonestransform[MAXSTUDIOBONES]; + matrix3x4 lighttransform[MAXSTUDIOBONES]; + + // boneweighting stuff + matrix3x4 worldtransform[MAXSTUDIOBONES]; + + // cached bones + matrix3x4 cached_bonestransform[MAXSTUDIOBONES]; + matrix3x4 cached_lighttransform[MAXSTUDIOBONES]; + char cached_bonenames[MAXSTUDIOBONES][32]; + int cached_numbones; // number of bones in cache + + sortedmesh_t meshes[MAXSTUDIOMESHES]; // sorted meshes + vec3_t verts[MAXSTUDIOVERTS]; + vec3_t norms[MAXSTUDIOVERTS]; + + // lighting state + float ambientlight; + float shadelight; + vec3_t lightvec; // averaging light direction + vec3_t lightspot; // shadow spot + vec3_t lightcolor; // averaging lightcolor + vec3_t blightvec[MAXSTUDIOBONES]; // bone light vecs + vec3_t lightvalues[MAXSTUDIOVERTS]; // precomputed lightvalues per each shared vertex of submodel + + // chrome stuff + vec3_t chrome_origin; + vec2_t chrome[MAXSTUDIOVERTS]; // texture coords for surface normals + vec3_t chromeright[MAXSTUDIOBONES]; // chrome vector "right" in bone reference frames + vec3_t chromeup[MAXSTUDIOBONES]; // chrome vector "up" in bone reference frames + int chromeage[MAXSTUDIOBONES]; // last time chrome vectors were updated + + // glowshell stuff + int normaltable[MAXSTUDIOVERTS]; // glowshell uses this + + // elights cache + int numlocallights; + int lightage[MAXSTUDIOBONES]; + dlight_t *locallight[MAX_LOCALLIGHTS]; + color24 locallightcolor[MAX_LOCALLIGHTS]; + vec4_t lightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS]; + vec3_t lightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS]; + float locallightR2[MAX_LOCALLIGHTS]; + + // playermodels + player_model_t player_models[MAX_CLIENTS]; + +#ifdef STUDIO_DRAWELEMENTS + // drawelements renderer + gu_vert_ftcv_t arrayverts[MAXSTUDIOVERTS]; + unsigned short arrayelems[MAXSTUDIOVERTS * 6]; + uint numverts; + uint numelems; +#endif // STUDIO_DRAWELEMENTS + +} studio_draw_state_t; + +// studio-related cvars +static cvar_t *r_studio_sort_textures; +static cvar_t *r_drawviewmodel; +cvar_t *cl_righthand = NULL; +static cvar_t *cl_himodels; +static cvar_t *r_studio_drawelements; + +static r_studio_interface_t *pStudioDraw; +static studio_draw_state_t g_studio; // global studio state + +// global variables +static qboolean m_fDoRemap; +mstudiomodel_t *m_pSubModel; +mstudiobodyparts_t *m_pBodyPart; +player_info_t *m_pPlayerInfo; +studiohdr_t *m_pStudioHeader; +float m_flGaitMovement; +int g_iBackFaceCull; +int g_nTopColor, g_nBottomColor; // remap colors +int g_nFaceFlags, g_nForceFaceFlags; + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + cl_himodels = gEngfuncs.Cvar_Get( "cl_himodels", "0", FCVAR_ARCHIVE, "draw high-resolution player models in multiplayer" ); + r_studio_sort_textures = gEngfuncs.Cvar_Get( "r_studio_sort_textures", "0", FCVAR_ARCHIVE, "change draw order for additive meshes" ); + r_drawviewmodel = gEngfuncs.Cvar_Get( "r_drawviewmodel", "1", 0, "draw firstperson weapon model" ); + r_studio_drawelements = gEngfuncs.Cvar_Get( "r_studio_drawelements", "0", FCVAR_ARCHIVE, "use glDrawElements for studiomodels" ); + + Matrix3x4_LoadIdentity( g_studio.rotationmatrix ); + r_glowshellfreq = gEngfuncs.Cvar_Get( "r_glowshellfreq", "2.2", 0, "glowing shell frequency update" ); + + // g-cont. cvar disabled by Valve +// gEngfuncs.Cvar_RegisterVariable( &r_shadows ); + + g_studio.interpolate = true; + + g_studio.framecount = 0; + m_fDoRemap = false; +} + +/* +================ +R_StudioSetupTimings + +init current time for a given model +================ +*/ +_inline void R_StudioSetupTimings( void ) +{ + if( RI.drawWorld ) + { + // synchronize with server time + g_studio.time = gpGlobals->time; + g_studio.frametime = gpGlobals->time - gpGlobals->oldtime; + } + else + { + // menu stuff + g_studio.time = gpGlobals->realtime; + g_studio.frametime = gpGlobals->frametime; + } +} + +/* +================ +R_AllowFlipViewModel + +should a flip the viewmodel if cl_righthand is set to 1 +================ +*/ +_inline qboolean R_AllowFlipViewModel( cl_entity_t *e ) +{ + if( cl_righthand && cl_righthand->value > 0 ) + { + if( e == gEngfuncs.GetViewModel() ) + return true; + } + + return false; +} + +/* +================ +R_StudioComputeBBox + +Compute a full bounding box for current sequence +================ +*/ +_inline qboolean R_StudioComputeBBox( vec3_t bbox[8] ) +{ + vec3_t studio_mins, studio_maxs; + vec3_t mins, maxs, p1, p2; + cl_entity_t *e = RI.currententity; + mstudioseqdesc_t *pseqdesc; + int i; + + if( !m_pStudioHeader ) + return false; + + // check if we have valid mins\maxs + if( !VectorCompare( vec3_origin, RI.currentmodel->mins )) + { + // clipping bounding box + VectorCopy( RI.currentmodel->mins, mins ); + VectorCopy( RI.currentmodel->maxs, maxs ); + } + else + { + ClearBounds( mins, maxs ); + } + + // check sequence range + if( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq ) + e->curstate.sequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + + // add sequence box to the model box + AddPointToBounds( pseqdesc->bbmin, mins, maxs ); + AddPointToBounds( pseqdesc->bbmax, mins, maxs ); + ClearBounds( studio_mins, studio_maxs ); + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + p1[0] = ( i & 1 ) ? mins[0] : maxs[0]; + p1[1] = ( i & 2 ) ? mins[1] : maxs[1]; + p1[2] = ( i & 4 ) ? mins[2] : maxs[2]; + + Matrix3x4_VectorTransform( g_studio.rotationmatrix, p1, p2 ); + AddPointToBounds( p2, studio_mins, studio_maxs ); + if( bbox ) VectorCopy( p2, bbox[i] ); + } + + if( !bbox && R_CullModel( e, studio_mins, studio_maxs )) + return false; // model culled + return true; // visible +} + +void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result ) +{ + vec4_t flWeight; + int i, numbones = 0; + float flTotal; + + for( i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( boneweights->bone[i] != -1 ) + numbones++; + } + + if( numbones == 4 ) + { + vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; + vec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]]; + vec4_t *boneMat3 = (vec4_t *)g_studio.worldtransform[boneweights->bone[3]]; + flWeight[0] = boneweights->weight[0] / 255.0f; + flWeight[1] = boneweights->weight[1] / 255.0f; + flWeight[2] = boneweights->weight[2] / 255.0f; + flWeight[3] = boneweights->weight[3] / 255.0f; + flTotal = flWeight[0] + flWeight[1] + flWeight[2] + flWeight[3]; + + if( flTotal < 1.0f ) flWeight[0] += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight[0] + boneMat1[0][0] * flWeight[1] + boneMat2[0][0] * flWeight[2] + boneMat3[0][0] * flWeight[3]; + result[0][1] = boneMat0[0][1] * flWeight[0] + boneMat1[0][1] * flWeight[1] + boneMat2[0][1] * flWeight[2] + boneMat3[0][1] * flWeight[3]; + result[0][2] = boneMat0[0][2] * flWeight[0] + boneMat1[0][2] * flWeight[1] + boneMat2[0][2] * flWeight[2] + boneMat3[0][2] * flWeight[3]; + result[0][3] = boneMat0[0][3] * flWeight[0] + boneMat1[0][3] * flWeight[1] + boneMat2[0][3] * flWeight[2] + boneMat3[0][3] * flWeight[3]; + result[1][0] = boneMat0[1][0] * flWeight[0] + boneMat1[1][0] * flWeight[1] + boneMat2[1][0] * flWeight[2] + boneMat3[1][0] * flWeight[3]; + result[1][1] = boneMat0[1][1] * flWeight[0] + boneMat1[1][1] * flWeight[1] + boneMat2[1][1] * flWeight[2] + boneMat3[1][1] * flWeight[3]; + result[1][2] = boneMat0[1][2] * flWeight[0] + boneMat1[1][2] * flWeight[1] + boneMat2[1][2] * flWeight[2] + boneMat3[1][2] * flWeight[3]; + result[1][3] = boneMat0[1][3] * flWeight[0] + boneMat1[1][3] * flWeight[1] + boneMat2[1][3] * flWeight[2] + boneMat3[1][3] * flWeight[3]; + result[2][0] = boneMat0[2][0] * flWeight[0] + boneMat1[2][0] * flWeight[1] + boneMat2[2][0] * flWeight[2] + boneMat3[2][0] * flWeight[3]; + result[2][1] = boneMat0[2][1] * flWeight[0] + boneMat1[2][1] * flWeight[1] + boneMat2[2][1] * flWeight[2] + boneMat3[2][1] * flWeight[3]; + result[2][2] = boneMat0[2][2] * flWeight[0] + boneMat1[2][2] * flWeight[1] + boneMat2[2][2] * flWeight[2] + boneMat3[2][2] * flWeight[3]; + result[2][3] = boneMat0[2][3] * flWeight[0] + boneMat1[2][3] * flWeight[1] + boneMat2[2][3] * flWeight[2] + boneMat3[2][3] * flWeight[3]; + } + else if( numbones == 3 ) + { + vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; + vec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]]; + flWeight[0] = boneweights->weight[0] / 255.0f; + flWeight[1] = boneweights->weight[1] / 255.0f; + flWeight[2] = boneweights->weight[2] / 255.0f; + flTotal = flWeight[0] + flWeight[1] + flWeight[2]; + + if( flTotal < 1.0f ) flWeight[0] += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight[0] + boneMat1[0][0] * flWeight[1] + boneMat2[0][0] * flWeight[2]; + result[0][1] = boneMat0[0][1] * flWeight[0] + boneMat1[0][1] * flWeight[1] + boneMat2[0][1] * flWeight[2]; + result[0][2] = boneMat0[0][2] * flWeight[0] + boneMat1[0][2] * flWeight[1] + boneMat2[0][2] * flWeight[2]; + result[0][3] = boneMat0[0][3] * flWeight[0] + boneMat1[0][3] * flWeight[1] + boneMat2[0][3] * flWeight[2]; + result[1][0] = boneMat0[1][0] * flWeight[0] + boneMat1[1][0] * flWeight[1] + boneMat2[1][0] * flWeight[2]; + result[1][1] = boneMat0[1][1] * flWeight[0] + boneMat1[1][1] * flWeight[1] + boneMat2[1][1] * flWeight[2]; + result[1][2] = boneMat0[1][2] * flWeight[0] + boneMat1[1][2] * flWeight[1] + boneMat2[1][2] * flWeight[2]; + result[1][3] = boneMat0[1][3] * flWeight[0] + boneMat1[1][3] * flWeight[1] + boneMat2[1][3] * flWeight[2]; + result[2][0] = boneMat0[2][0] * flWeight[0] + boneMat1[2][0] * flWeight[1] + boneMat2[2][0] * flWeight[2]; + result[2][1] = boneMat0[2][1] * flWeight[0] + boneMat1[2][1] * flWeight[1] + boneMat2[2][1] * flWeight[2]; + result[2][2] = boneMat0[2][2] * flWeight[0] + boneMat1[2][2] * flWeight[1] + boneMat2[2][2] * flWeight[2]; + result[2][3] = boneMat0[2][3] * flWeight[0] + boneMat1[2][3] * flWeight[1] + boneMat2[2][3] * flWeight[2]; + } + else if( numbones == 2 ) + { + vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; + flWeight[0] = boneweights->weight[0] / 255.0f; + flWeight[1] = boneweights->weight[1] / 255.0f; + flTotal = flWeight[0] + flWeight[1]; + + if( flTotal < 1.0f ) flWeight[0] += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight[0] + boneMat1[0][0] * flWeight[1]; + result[0][1] = boneMat0[0][1] * flWeight[0] + boneMat1[0][1] * flWeight[1]; + result[0][2] = boneMat0[0][2] * flWeight[0] + boneMat1[0][2] * flWeight[1]; + result[0][3] = boneMat0[0][3] * flWeight[0] + boneMat1[0][3] * flWeight[1]; + result[1][0] = boneMat0[1][0] * flWeight[0] + boneMat1[1][0] * flWeight[1]; + result[1][1] = boneMat0[1][1] * flWeight[0] + boneMat1[1][1] * flWeight[1]; + result[1][2] = boneMat0[1][2] * flWeight[0] + boneMat1[1][2] * flWeight[1]; + result[1][3] = boneMat0[1][3] * flWeight[0] + boneMat1[1][3] * flWeight[1]; + result[2][0] = boneMat0[2][0] * flWeight[0] + boneMat1[2][0] * flWeight[1]; + result[2][1] = boneMat0[2][1] * flWeight[0] + boneMat1[2][1] * flWeight[1]; + result[2][2] = boneMat0[2][2] * flWeight[0] + boneMat1[2][2] * flWeight[1]; + result[2][3] = boneMat0[2][3] * flWeight[0] + boneMat1[2][3] * flWeight[1]; + } + else + { + Matrix3x4_Copy( result, g_studio.worldtransform[boneweights->bone[0]] ); + } +} + +/* +=============== +pfnGetCurrentEntity + +=============== +*/ +static cl_entity_t *pfnGetCurrentEntity( void ) +{ + return RI.currententity; +} + +/* +=============== +pfnPlayerInfo + +=============== +*/ +player_info_t *pfnPlayerInfo( int index ) +{ + if( !RI.drawWorld ) + index = -1; + + return gEngfuncs.pfnPlayerInfo( index ); +} + +/* +=============== +pfnMod_ForName + +=============== +*/ +static model_t *pfnMod_ForName( const char *model, int crash ) +{ + return gEngfuncs.Mod_ForName( model, crash, false ); +} + +/* +=============== +pfnGetPlayerState + +=============== +*/ +entity_state_t *R_StudioGetPlayerState( int index ) +{ + if( !RI.drawWorld ) + return &RI.currententity->curstate; + + return gEngfuncs.pfnGetPlayerState( index ); +} + +/* +=============== +pfnGetViewEntity + +=============== +*/ +static cl_entity_t *pfnGetViewEntity( void ) +{ + return gEngfuncs.GetViewModel(); +} + +/* +=============== +pfnGetEngineTimes + +=============== +*/ +static void pfnGetEngineTimes( int *framecount, double *current, double *old ) +{ + if( framecount ) *framecount = tr.realframecount; + if( current ) *current = gpGlobals->time; + if( old ) *old = gpGlobals->oldtime; +} + +/* +=============== +pfnGetViewInfo + +=============== +*/ +static void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv ) +{ + if( origin ) VectorCopy( RI.vieworg, origin ); + if( forwardv ) VectorCopy( RI.vforward, forwardv ); + if( rightv ) VectorCopy( RI.vright, rightv ); + if( upv ) VectorCopy( RI.vup, upv ); +} + +/* +=============== +R_GetChromeSprite + +=============== +*/ +static model_t *R_GetChromeSprite( void ) +{ + return gEngfuncs.GetDefaultSprite( REF_CHROME_SPRITE ); +} + +/* +=============== +pfnGetModelCounters + +=============== +*/ +static void pfnGetModelCounters( int **s, int **a ) +{ + *s = &g_studio.framecount; + *a = &r_stats.c_studio_models_drawn; +} + +/* +=============== +pfnGetAliasScale + +=============== +*/ +static void pfnGetAliasScale( float *x, float *y ) +{ + if( x ) *x = 1.0f; + if( y ) *y = 1.0f; +} + +/* +=============== +pfnStudioGetBoneTransform + +=============== +*/ +static float ****pfnStudioGetBoneTransform( void ) +{ + return (float ****)g_studio.bonestransform; +} + +/* +=============== +pfnStudioGetLightTransform + +=============== +*/ +static float ****pfnStudioGetLightTransform( void ) +{ + return (float ****)g_studio.lighttransform; +} + +/* +=============== +pfnStudioGetAliasTransform + +=============== +*/ +static float ***pfnStudioGetAliasTransform( void ) +{ + return NULL; +} + +/* +=============== +pfnStudioGetRotationMatrix + +=============== +*/ +static float ***pfnStudioGetRotationMatrix( void ) +{ + return (float ***)g_studio.rotationmatrix; +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +_inline void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3.0f); + + if( *pBlend < pseqdesc->blendstart[0] ) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0f; + *pBlend = 0; + } + else if( *pBlend > pseqdesc->blendend[0] ) + { + *pPitch -= pseqdesc->blendend[0] / 3.0f; + *pBlend = 255; + } + else + { + if( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error + *pBlend = 127; + else *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0.0f; + } +} + +/* +==================== +R_StudioLerpMovement + +==================== +*/ +void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ) +{ + float f = 1.0f; + + // don't do it if the goalstarttime hasn't updated in a while. + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + if( g_studio.interpolate && ( time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime )) + f = ( time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime ); + + // Con_Printf( "%4.2f %.2f %.2f\n", f, e->curstate.animtime, g_studio.time ); + VectorLerp( e->latched.prevorigin, f, e->curstate.origin, origin ); + + if( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON )) + { + vec4_t q, q1, q2; + + AngleQuaternion( e->curstate.angles, q1, false ); + AngleQuaternion( e->latched.prevangles, q2, false ); + QuaternionSlerp( q2, q1, f, q ); + QuaternionAngle( q, angles ); + } + else VectorCopy( e->curstate.angles, angles ); +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void R_StudioSetUpTransform( cl_entity_t *e ) +{ + vec3_t origin, angles; + + VectorCopy( e->origin, origin ); + VectorCopy( e->angles, angles ); + + // interpolate monsters position (moved into UpdateEntityFields by user request) + if( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPUTE_STUDIO_LERP )) + { + R_StudioLerpMovement( e, g_studio.time, origin, angles ); + } + + if( !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPENSATE_QUAKE_BUG )) + angles[PITCH] = -angles[PITCH]; // stupid quake bug + + // don't rotate clients, only aim + if( e->player ) angles[PITCH] = 0.0f; + + Matrix3x4_CreateFromEntity( g_studio.rotationmatrix, angles, origin, 1.0f ); + + if( tr.fFlipViewModel ) + { + g_studio.rotationmatrix[0][1] = -g_studio.rotationmatrix[0][1]; + g_studio.rotationmatrix[1][1] = -g_studio.rotationmatrix[1][1]; + g_studio.rotationmatrix[2][1] = -g_studio.rotationmatrix[2][1]; + } +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ) +{ + double dfdt, f; + + if( g_studio.interpolate ) + { + if( g_studio.time < e->curstate.animtime ) dfdt = 0.0; + else dfdt = (g_studio.time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; + } + else dfdt = 0; + + if( pseqdesc->numframes <= 1 ) f = 0.0; + else f = (e->curstate.frame * (pseqdesc->numframes - 1)) / 256.0f; + + f += dfdt; + + if( pseqdesc->flags & STUDIO_LOOPING ) + { + if( pseqdesc->numframes > 1 ) + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + if( f < 0 ) f += (pseqdesc->numframes - 1); + } + else + { + if( f >= pseqdesc->numframes - 1.001 ) + f = pseqdesc->numframes - 1.001; + if( f < 0.0 ) f = 0.0; + } + return f; +} + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float R_StudioEstimateInterpolant( cl_entity_t *e ) +{ + float dadt = 1.0f; + + if( g_studio.interpolate && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f )) + { + dadt = ( g_studio.time - e->curstate.animtime ) / 0.1f; + if( dadt > 2.0f ) dadt = 2.0f; + } + + return dadt; +} + +/* +==================== +CL_GetSequenceDuration + +==================== +*/ +float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ) +{ + studiohdr_t *pstudiohdr; + mstudioseqdesc_t *pseqdesc; + + if( ent->model != NULL && ent->model->type == mod_studio ) + { + pstudiohdr = (studiohdr_t *)STUDIOEXTRADATA( ent->model ); + + if( pstudiohdr ) + { + sequence = bound( 0, sequence, pstudiohdr->numseq - 1 ); + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; + + if( pseqdesc->numframes > 1 && pseqdesc->fps > 0 ) + return (float)pseqdesc->numframes / (float)pseqdesc->fps; + } + } + + return 0.1f; +} + + +/* +==================== +StudioFxTransform + +==================== +*/ +_inline void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if( !gEngfuncs.COM_RandomLong( 0, 49 )) + { + int axis = gEngfuncs.COM_RandomLong( 0, 1 ); + + if( axis == 1 ) axis = 2; // choose between x & z + VectorScale( transform[axis], gEngfuncs.COM_RandomFloat( 1.0f, 1.484f ), transform[axis] ); + } + else if( !gEngfuncs.COM_RandomLong( 0, 49 )) + { + float offset; + int axis = gEngfuncs.COM_RandomLong( 0, 1 ); + + if( axis == 1 ) axis = 2; // choose between x & z + offset = gEngfuncs.COM_RandomFloat( -10.0f, 10.0f ); + transform[gEngfuncs.COM_RandomLong( 0, 2 )][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0f + ( g_studio.time - ent->curstate.animtime ) * 10.0f; + if( scale > 2.0f ) scale = 2.0f; // don't blow up more than 200% + + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + } +} + +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +_inline void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +{ + mstudiobonecontroller_t *pbonecontroller; + float value = 0.0f; + int i, j; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + for( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ ) + { + i = pbonecontroller[j].index; + + if( i == STUDIO_MOUTH ) + { + // mouth hardcoded at controller 4 + value = (float)mouthopen / 64.0f; + value = bound( 0.0f, value, 1.0f ); + value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + else if( i < 4 ) + { + // check for 360% wrapping + if( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP )) + { + if( abs( pcontroller1[i] - pcontroller2[i] ) > 128 ) + { + int a = (pcontroller1[i] + 128) % 256; + int b = (pcontroller2[i] + 128) % 256; + value = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128) * (360.0f / 256.0f) + pbonecontroller[j].start; + } + else + { + value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0f - dadt))) * (360.0f / 256.0f) + pbonecontroller[j].start; + } + } + else + { + value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0f - dadt)) / 255.0f; + value = bound( 0.0f, value, 1.0f ); + value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + } + + switch( pbonecontroller[j].type & STUDIO_TYPES ) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = DEG2RAD( value ); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i, frame; + float adj[MAXSTUDIOCONTROLLERS]; + float s, dadt; + mstudiobone_t *pbone; + + // bah, fix this bug with changing sequences too fast + if( f > pseqdesc->numframes - 1 ) + { + f = 0.0f; + } + else if( f < -0.01f ) + { + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + f = -0.01f; + } + + frame = (int)f; + + dadt = R_StudioEstimateInterpolant( e ); + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + R_StudioCalcBoneAdj( dadt, adj, e->curstate.controller, e->latched.prevcontroller, e->mouth.mouthopen ); + + for( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ ) + { + gEngfuncs.R_StudioCalcBoneQuaternion( frame, s, pbone, panim, adj, q[i] ); + gEngfuncs.R_StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); + } + + if( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f; + if( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f; + if( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f; +} + +/* +==================== +StudioMergeBones + +==================== +*/ +_inline void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) +{ + int i, j; + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + matrix3x4 bonematrix; + static vec4_t q[MAXSTUDIOBONES]; + static float pos[MAXSTUDIOBONES][3]; + float f; + + if( e->curstate.sequence >= m_pStudioHeader->numseq ) + e->curstate.sequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + + f = R_StudioEstimateFrame( e, pseqdesc ); + + panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc ); + R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + for( j = 0; j < g_studio.cached_numbones; j++ ) + { + if( !Q_stricmp( pbones[i].name, g_studio.cached_bonenames[j] )) + { + Matrix3x4_Copy( g_studio.bonestransform[i], g_studio.cached_bonestransform[j] ); + Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.cached_lighttransform[j] ); + break; + } + } + + if( j >= g_studio.cached_numbones ) + { + Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] ); + if( pbones[i].parent == -1 ) + { + Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix ); + Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] ); + + // apply client-side effects to the transformation matrix + R_StudioFxTransform( e, g_studio.bonestransform[i] ); + } + else + { + Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix ); + Matrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix ); + } + } + } +} + +/* +==================== +StudioSetupBones + +==================== +*/ +_inline void R_StudioSetupBones( cl_entity_t *e ) +{ + float f; + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + matrix3x4 bonematrix; + static vec3_t pos[MAXSTUDIOBONES]; + static vec4_t q[MAXSTUDIOBONES]; + static vec3_t pos2[MAXSTUDIOBONES]; + static vec4_t q2[MAXSTUDIOBONES]; + static vec3_t pos3[MAXSTUDIOBONES]; + static vec4_t q3[MAXSTUDIOBONES]; + static vec3_t pos4[MAXSTUDIOBONES]; + static vec4_t q4[MAXSTUDIOBONES]; + int i; + + if( e->curstate.sequence >= m_pStudioHeader->numseq ) + e->curstate.sequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + + f = R_StudioEstimateFrame( e, pseqdesc ); + + panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); + R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); + + if( pseqdesc->numblends > 1 ) + { + float s; + float dadt; + + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, f ); + + dadt = R_StudioEstimateInterpolant( e ); + s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; + + gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s ); + + if( pseqdesc->numblends == 4 ) + { + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, f ); + + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, f ); + + s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; + gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); + + s = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f; + gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s ); + } + } + + if( g_studio.interpolate && e->latched.sequencetime && ( e->latched.sequencetime + 0.2f > g_studio.time ) && ( e->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static vec3_t pos1b[MAXSTUDIOBONES]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->latched.prevsequence; + panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); + + // clip prevframe + R_StudioCalcRotations( e, pos1b, q1b, pseqdesc, panim, e->latched.prevframe ); + + if( pseqdesc->numblends > 1 ) + { + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, e->latched.prevframe ); + + s = (e->latched.prevseqblending[0]) / 255.0f; + gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s ); + + if( pseqdesc->numblends == 4 ) + { + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, e->latched.prevframe ); + + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, e->latched.prevframe ); + + s = (e->latched.prevseqblending[0]) / 255.0f; + gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); + + s = (e->latched.prevseqblending[1]) / 255.0f; + gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s ); + } + } + + s = 1.0f - ( g_studio.time - e->latched.sequencetime ) / 0.2f; + gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s ); + } + else + { + // store prevframe otherwise + e->latched.prevframe = f; + } + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + // calc gait animation + if( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) + { + qboolean copy_bones = true; + + if( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq ) + m_pPlayerInfo->gaitsequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + + panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); + R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( !Q_strcmp( pbones[i].name, "Bip01 Spine" )) + copy_bones = false; + else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) + copy_bones = true; + + if( !copy_bones ) continue; + + VectorCopy( pos2[i], pos[i] ); + Vector4Copy( q2[i], q[i] ); + } + } + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] ); + + if( pbones[i].parent == -1 ) + { + Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix ); + Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] ); + + // apply client-side effects to the transformation matrix + R_StudioFxTransform( e, g_studio.bonestransform[i] ); + } + else + { + Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix ); + Matrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix ); + } + } +} + +/* +==================== +StudioSaveBones + +==================== +*/ +_inline void R_StudioSaveBones( void ) +{ + mstudiobone_t *pbones; + int i; + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + g_studio.cached_numbones = m_pStudioHeader->numbones; + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + Matrix3x4_Copy( g_studio.cached_bonestransform[i], g_studio.bonestransform[i] ); + Matrix3x4_Copy( g_studio.cached_lighttransform[i], g_studio.lighttransform[i] ); + Q_strncpy( g_studio.cached_bonenames[i], pbones[i].name, 32 ); + } +} + +/* +==================== +StudioBuildNormalTable + +NOTE: m_pSubModel must be set +==================== +*/ +_inline void R_StudioBuildNormalTable( void ) +{ + cl_entity_t *e = RI.currententity; + mstudiomesh_t *pmesh; + int i, j; + + Assert( m_pSubModel != NULL ); + + // reset chrome cache + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + g_studio.chromeage[i] = 0; + + for( i = 0; i < m_pSubModel->numverts; i++ ) + g_studio.normaltable[i] = -1; + + for( j = 0; j < m_pSubModel->nummesh; j++ ) + { + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + + while(( i = *( ptricmds++ ))) + { + if( i < 0 ) i = -i; + + for( ; i > 0; i--, ptricmds += 4 ) + { + if( g_studio.normaltable[ptricmds[0]] < 0 ) + g_studio.normaltable[ptricmds[0]] = ptricmds[1]; + } + } + } + + g_studio.chrome_origin[0] = cos( r_glowshellfreq->value * g_studio.time ) * 4000.0f; + g_studio.chrome_origin[1] = sin( r_glowshellfreq->value * g_studio.time ) * 4000.0f; + g_studio.chrome_origin[2] = cos( r_glowshellfreq->value * g_studio.time * 0.33f ) * 4000.0f; + + if( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b ) + TriColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, 255 ); + else TriColor4ub( 255, 255, 255, 255 ); +} + +/* +==================== +StudioGenerateNormals + +NOTE: m_pSubModel must be set +g_studio.verts must be computed +==================== +*/ +_inline void R_StudioGenerateNormals( void ) +{ + int v0, v1, v2; + vec3_t e0, e1, norm; + mstudiomesh_t *pmesh; + int i, j; + + Assert( m_pSubModel != NULL ); + + for( i = 0; i < m_pSubModel->numverts; i++ ) + VectorClear( g_studio.norms[i] ); + + for( j = 0; j < m_pSubModel->nummesh; j++ ) + { + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + + while(( i = *( ptricmds++ ))) + { + if( i < 0 ) + { + i = -i; + + if( i > 2 ) + { + v0 = ptricmds[0]; ptricmds += 4; + v1 = ptricmds[0]; ptricmds += 4; + + for( i -= 2; i > 0; i--, ptricmds += 4 ) + { + v2 = ptricmds[0]; + + VectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 ); + VectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 ); + CrossProduct( e1, e0, norm ); + + VectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] ); + VectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] ); + VectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] ); + + v1 = v2; + } + } + else + { + ptricmds += i; + } + } + else + { + if( i > 2 ) + { + qboolean odd = false; + + v0 = ptricmds[0]; ptricmds += 4; + v1 = ptricmds[0]; ptricmds += 4; + + for( i -= 2; i > 0; i--, ptricmds += 4 ) + { + v2 = ptricmds[0]; + + VectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 ); + VectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 ); + CrossProduct( e1, e0, norm ); + + VectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] ); + VectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] ); + VectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] ); + + if( odd ) v1 = v2; + else v0 = v2; + + odd = !odd; + } + } + else + { + ptricmds += i; + } + } + } + } + + for( i = 0; i < m_pSubModel->numverts; i++ ) + VectorNormalize( g_studio.norms[i] ); +} + +/* +==================== +StudioSetupChrome + +==================== +*/ +_inline void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) +{ + float n; + + if( g_studio.chromeage[bone] != g_studio.framecount ) + { + // calculate vectors from the viewer to the bone. This roughly adjusts for position + vec3_t chromeupvec; // g_studio.chrome t vector in world reference frame + vec3_t chromerightvec; // g_studio.chrome s vector in world reference frame + vec3_t tmp; // vector pointing at bone in world reference frame + + VectorNegate( g_studio.chrome_origin, tmp ); + tmp[0] += g_studio.bonestransform[bone][0][3]; + tmp[1] += g_studio.bonestransform[bone][1][3]; + tmp[2] += g_studio.bonestransform[bone][2][3]; + + VectorNormalize( tmp ); + CrossProduct( tmp, RI.vright, chromeupvec ); + VectorNormalize( chromeupvec ); + CrossProduct( tmp, chromeupvec, chromerightvec ); + VectorNormalize( chromerightvec ); + + Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromeupvec, g_studio.chromeup[bone] ); + Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromerightvec, g_studio.chromeright[bone] ); + + g_studio.chromeage[bone] = g_studio.framecount; + } + + // calc s coord + n = DotProduct( normal, g_studio.chromeright[bone] ); + pchrome[0] = (n + 1.0f) * 32.0f; + + // calc t coord + n = DotProduct( normal, g_studio.chromeup[bone] ); + pchrome[1] = (n + 1.0f) * 32.0f; +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +_inline void R_StudioCalcAttachments( void ) +{ + mstudioattachment_t *pAtt; + int i; + + // calculate attachment points + pAtt = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + + for( i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ ) + { + Matrix3x4_VectorTransform( g_studio.lighttransform[pAtt[i].bone], pAtt[i].org, RI.currententity->attachment[i] ); + } +} + +/* +=============== +pfnStudioSetupModel + +=============== +*/ +static void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmodel ) +{ + int index; + + if( bodypart > m_pStudioHeader->numbodyparts ) + bodypart = 0; + + m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart; + + index = RI.currententity->curstate.body / m_pBodyPart->base; + index = index % m_pBodyPart->nummodels; + + m_pSubModel = (mstudiomodel_t *)((byte *)m_pStudioHeader + m_pBodyPart->modelindex) + index; + + if( ppbodypart ) *ppbodypart = m_pBodyPart; + if( ppsubmodel ) *ppsubmodel = m_pSubModel; +} + +/* +=============== +R_StudioCheckBBox + +=============== +*/ +static int R_StudioCheckBBox( void ) +{ + if( !RI.currententity || !RI.currentmodel ) + return false; + + return R_StudioComputeBBox( NULL ); +} + +/* +=============== +R_StudioDynamicLight + +=============== +*/ +void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) +{ + movevars_t *mv = gEngfuncs.pfnGetMoveVars(); + vec3_t lightDir, vecSrc, vecEnd; + vec3_t origin, dist, finalLight; + float add, radius, total; + colorVec light; + uint lnum; + dlight_t *dl; + + if( !plight || !ent || !ent->model ) + return; + + if( !RI.drawWorld || r_fullbright->value || FBitSet( ent->curstate.effects, EF_FULLBRIGHT )) + { + plight->shadelight = 0; + plight->ambientlight = 192; + + VectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f ); + VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + return; + } + + // determine plane to get lightvalues from: ceil or floor + if( FBitSet( ent->curstate.effects, EF_INVLIGHT )) + VectorSet( lightDir, 0.0f, 0.0f, 1.0f ); + else VectorSet( lightDir, 0.0f, 0.0f, -1.0f ); + + VectorCopy( ent->origin, origin ); + + VectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f ); + light.r = light.g = light.b = light.a = 0; + + if(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 ) + { + msurface_t *psurf = NULL; + pmtrace_t trace; + + if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_WRITE_LARGE_COORD )) + { + vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f; + vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f; + vecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f; + } + else + { + vecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f; + vecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f; + vecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f; + } + + trace = gEngfuncs.CL_TraceLine( vecSrc, vecEnd, PM_WORLD_ONLY ); + if( trace.ent > 0 ) psurf = gEngfuncs.EV_TraceSurface( trace.ent, vecSrc, vecEnd ); + else psurf = gEngfuncs.EV_TraceSurface( 0, vecSrc, vecEnd ); + + if( FBitSet( ent->model->flags, STUDIO_FORCE_SKYLIGHT ) || ( psurf && FBitSet( psurf->flags, SURF_DRAWSKY ))) + { + VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z ); + + light.r = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_r, 255 )); + light.g = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_g, 255 )); + light.b = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_b, 255 )); + } + } + + if(( light.r + light.g + light.b ) < 16 ) // TESTTEST + { + colorVec gcolor; + float grad[4]; + + VectorScale( lightDir, 2048.0f, vecEnd ); + VectorAdd( vecEnd, vecSrc, vecEnd ); + + light = R_LightVec( vecSrc, vecEnd, g_studio.lightspot, g_studio.lightvec ); + + if( VectorIsNull( g_studio.lightvec )) + { + vecSrc[0] -= 16.0f; + vecSrc[1] -= 16.0f; + vecEnd[0] -= 16.0f; + vecEnd[1] -= 16.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); + grad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[0] += 32.0f; + vecEnd[0] += 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); + grad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[1] += 32.0f; + vecEnd[1] += 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); + grad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[0] -= 32.0f; + vecEnd[0] -= 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); + grad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + lightDir[0] = grad[0] - grad[1] - grad[2] + grad[3]; + lightDir[1] = grad[1] + grad[0] - grad[2] - grad[3]; + VectorNormalize( lightDir ); + } + else + { + VectorCopy( g_studio.lightvec, lightDir ); + } + } + + VectorSet( finalLight, light.r, light.g, light.b ); + ent->cvFloorColor = light; + + total = Q_max( Q_max( light.r, light.g ), light.b ); + if( total == 0.0f ) total = 1.0f; + + // scale lightdir by light intentsity + VectorScale( lightDir, total, lightDir ); + + for( lnum = 0, dl = gEngfuncs.GetDynamicLight( 0 ); lnum < MAX_DLIGHTS; lnum++, dl++ ) + { + if( dl->die < g_studio.time || !r_dynamic->value ) + continue; + + VectorSubtract( ent->origin, dl->origin, dist ); + + radius = VectorLength( dist ); + add = (dl->radius - radius); + + if( add > 0.0f ) + { + total += add; + + if( radius > 1.0f ) + VectorScale( dist, ( add / radius ), dist ); + else VectorScale( dist, add, dist ); + + VectorAdd( lightDir, dist, lightDir ); + + finalLight[0] += gEngfuncs.LightToTexGamma( dl->color.r ) * ( add / 256.0f ) * 2.0f; + finalLight[1] += gEngfuncs.LightToTexGamma( dl->color.g ) * ( add / 256.0f ) * 2.0f; + finalLight[2] += gEngfuncs.LightToTexGamma( dl->color.b ) * ( add / 256.0f ) * 2.0f; + } + } + + if( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT )) + add = 0.6f; + else add = 0.9f; + + VectorScale( lightDir, add, lightDir ); + + plight->shadelight = VectorLength( lightDir ); + plight->ambientlight = total - plight->shadelight; + + total = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] ); + + if( total > 0.0f ) + { + plight->color[0] = finalLight[0] * ( 1.0f / total ); + plight->color[1] = finalLight[1] * ( 1.0f / total ); + plight->color[2] = finalLight[2] * ( 1.0f / total ); + } + else VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + + if( plight->ambientlight > 128 ) + plight->ambientlight = 128; + + if( plight->ambientlight + plight->shadelight > 255 ) + plight->shadelight = 255 - plight->ambientlight; + + VectorNormalize2( lightDir, plight->plightvec ); +} + +/* +=============== +pfnStudioEntityLight + +=============== +*/ +void R_StudioEntityLight( alight_t *lightinfo ) +{ + int lnum, i, j, k; + float minstrength, dist2, f, r2; + float lstrength[MAX_LOCALLIGHTS]; + cl_entity_t *ent = RI.currententity; + vec3_t mid, origin, pos; + dlight_t *el; + + g_studio.numlocallights = 0; + + if( !ent || !r_dynamic->value ) + return; + + for( i = 0; i < MAX_LOCALLIGHTS; i++ ) + lstrength[i] = 0; + + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin ); + dist2 = 1000000.0f; + k = 0; + + for( lnum = 0, el = gEngfuncs.GetEntityLight( 0 ); lnum < MAX_ELIGHTS; lnum++, el++ ) + { + if( el->die < g_studio.time || el->radius <= 0.0f ) + continue; + + if(( el->key & 0xFFF ) == ent->index ) + { + int att = (el->key >> 12) & 0xF; + + if( att ) VectorCopy( ent->attachment[att], el->origin ); + else VectorCopy( ent->origin, el->origin ); + } + + VectorCopy( el->origin, pos ); + VectorSubtract( origin, el->origin, mid ); + + f = DotProduct( mid, mid ); + r2 = el->radius * el->radius; + + if( f > r2 ) minstrength = r2 / f; + else minstrength = 1.0f; + + if( minstrength > 0.05f ) + { + if( g_studio.numlocallights >= MAX_LOCALLIGHTS ) + { + for( j = 0, k = -1; j < g_studio.numlocallights; j++ ) + { + if( lstrength[j] < dist2 && lstrength[j] < minstrength ) + { + dist2 = lstrength[j]; + k = j; + } + } + } + else k = g_studio.numlocallights; + + if( k != -1 ) + { + g_studio.locallightcolor[k].r = gEngfuncs.LightToTexGamma( el->color.r ); + g_studio.locallightcolor[k].g = gEngfuncs.LightToTexGamma( el->color.g ); + g_studio.locallightcolor[k].b = gEngfuncs.LightToTexGamma( el->color.b ); + g_studio.locallightR2[k] = r2; + g_studio.locallight[k] = el; + lstrength[k] = minstrength; + + if( k >= g_studio.numlocallights ) + g_studio.numlocallights = k + 1; + } + } + } +} + +/* +=============== +R_StudioSetupLighting + +=============== +*/ +void R_StudioSetupLighting( alight_t *plight ) +{ + float scale = 1.0f; + int i; + + if( !m_pStudioHeader || !plight ) + return; + + if( RI.currententity != NULL ) + scale = RI.currententity->curstate.scale; + + g_studio.ambientlight = plight->ambientlight; + g_studio.shadelight = plight->shadelight; + VectorCopy( plight->plightvec, g_studio.lightvec ); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + Matrix3x4_VectorIRotate( g_studio.lighttransform[i], plight->plightvec, g_studio.blightvec[i] ); + if( scale > 1.0f ) VectorNormalize( g_studio.blightvec[i] ); // in case model may be scaled + } + + VectorCopy( plight->color, g_studio.lightcolor ); +} + +/* +=============== +R_StudioLighting + +=============== +*/ +_inline void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) +{ + float illum; + + if( FBitSet( flags, STUDIO_NF_FULLBRIGHT )) + { + *lv = 1.0f; + return; + } + + illum = g_studio.ambientlight; + + if( FBitSet( flags, STUDIO_NF_FLATSHADE )) + { + illum += g_studio.shadelight * 0.8f; + } + else + { + float r, lightcos; + + if( bone != -1 ) lightcos = DotProduct( normal, g_studio.blightvec[bone] ); + else lightcos = DotProduct( normal, g_studio.lightvec ); // -1 colinear, 1 opposite + if( lightcos > 1.0f ) lightcos = 1.0f; + + illum += g_studio.shadelight; + + r = SHADE_LAMBERT; + + // do modified hemispherical lighting + if( r <= 1.0f ) + { + r += 1.0f; + lightcos = (( r - 1.0f ) - lightcos) / r; + if( lightcos > 0.0f ) + illum += g_studio.shadelight * lightcos; + } + else + { + lightcos = (lightcos + ( r - 1.0f )) / r; + if( lightcos > 0.0f ) + illum -= g_studio.shadelight * lightcos; + } + + illum = Q_max( illum, 0.0f ); + } + + illum = Q_min( illum, 255.0f ); + *lv = illum * ( 1.0f / 255.0f ); +} + +/* +==================== +R_LightLambert + +==================== +*/ +_inline void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color, byte *out ) +{ + vec3_t finalLight; + vec3_t localLight; + int i; + + VectorCopy( color, finalLight ); + + for( i = 0; i < g_studio.numlocallights; i++ ) + { + float r, r2; + + if( tr.fFlipViewModel ) + r = DotProduct( normal, light[i] ); + else r = -DotProduct( normal, light[i] ); + + if( r > 0.0f ) + { + if( light[i][3] == 0.0f ) + { + r2 = DotProduct( light[i], light[i] ); + + if( r2 > 0.0f ) + light[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 )); + else light[i][3] = 0.0001f; + } + + localLight[0] = Q_min( g_studio.locallightcolor[i].r * r * light[i][3], 255.0f ); + localLight[1] = Q_min( g_studio.locallightcolor[i].g * r * light[i][3], 255.0f ); + localLight[2] = Q_min( g_studio.locallightcolor[i].b * r * light[i][3], 255.0f ); + VectorScale( localLight, ( 1.0f / 255.0f ), localLight ); + + finalLight[0] = Q_min( finalLight[0] + localLight[0], 1.0f ); + finalLight[1] = Q_min( finalLight[1] + localLight[1], 1.0f ); + finalLight[2] = Q_min( finalLight[2] + localLight[2], 1.0f ); + } + } + + out[0] = finalLight[0] * 255; + out[1] = finalLight[1] * 255; + out[2] = finalLight[2] * 255; +} + +#if 0 /* R_StudioSetColorArray used */ +static void R_StudioSetColorBegin(short *ptricmds, vec3_t *pstudionorms ) +{ + float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; + rgba_t color; + + if( g_studio.numlocallights ) + { + color[3] = tr.blend * 255; + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); + pglColor4ubv( color ); + } + else + { + if( RI.currententity->curstate.rendermode == kRenderTransColor ) + { + color[3] = tr.blend * 255; + VectorCopy( (byte*)&RI.currententity->curstate.rendercolor, color ); + pglColor4ubv( color ); + } + else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); + } +} +#endif + +_inline void R_StudioSetColorArray(short *ptricmds, vec3_t *pstudionorms, byte *color ) +{ + float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + color[3] = tr.blend * 255; + + if( g_studio.numlocallights ) + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); + else + { + if( RI.currententity->curstate.rendermode == kRenderTransColor ) + VectorCopy( (byte*)&RI.currententity->curstate.rendercolor, color ); + else + { + color[0] = lv[0] * 255; + color[1] = lv[1] * 255; + color[2] = lv[2] * 255; + } + } +} + +/* +==================== +R_LightStrength + +==================== +*/ +_inline void R_LightStrength( int bone, vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] ) +{ + int i; + + if( g_studio.lightage[bone] != g_studio.framecount ) + { + for( i = 0; i < g_studio.numlocallights; i++ ) + { + dlight_t *el = g_studio.locallight[i]; + Matrix3x4_VectorITransform( g_studio.lighttransform[bone], el->origin, g_studio.lightbonepos[bone][i] ); + } + + g_studio.lightage[bone] = g_studio.framecount; + } + + for( i = 0; i < g_studio.numlocallights; i++ ) + { + VectorSubtract( localpos, g_studio.lightbonepos[bone][i], light[i] ); + light[i][3] = 0.0f; + } +} + +/* +=============== +R_StudioSetupSkin + +=============== +*/ +static void R_StudioSetupSkin( studiohdr_t *ptexturehdr, int index ) +{ + mstudiotexture_t *ptexture = NULL; + + if( FBitSet( g_nForceFaceFlags, STUDIO_NF_CHROME )) + return; + + if( ptexturehdr == NULL ) + return; + + // NOTE: user may ignore to call StudioRemapColors and remap_info will be unavailable + if( m_fDoRemap ) ptexture = gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )->ptexture; + if( !ptexture ) ptexture = (mstudiotexture_t *)((byte *)ptexturehdr + ptexturehdr->textureindex); // fallback + + if( r_lightmap->value && !r_fullbright->value ) + GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); + else GL_Bind( XASH_TEXTURE0, ptexture[index].index ); +} + +/* +=============== +R_StudioGetTexture + +Doesn't changes studio global state at all +=============== +*/ +mstudiotexture_t *R_StudioGetTexture( cl_entity_t *e ) +{ + mstudiotexture_t *ptexture; + studiohdr_t *phdr, *thdr; + + if(( phdr = STUDIOEXTRADATA( e->model )) == NULL ) + return NULL; + + thdr = m_pStudioHeader; + if( !thdr ) return NULL; + + if( m_fDoRemap ) ptexture = gEngfuncs.CL_GetRemapInfoForEntity( e )->ptexture; + else ptexture = (mstudiotexture_t *)((byte *)thdr + thdr->textureindex); + + return ptexture; +} + +void R_StudioSetRenderamt( int iRenderamt ) +{ + if( !RI.currententity ) return; + + RI.currententity->curstate.renderamt = iRenderamt; + tr.blend = CL_FxBlend( RI.currententity ) / 255.0f; +} + +/* +=============== +R_StudioSetCullState + +sets true for enable backculling (for left-hand viewmodel) +=============== +*/ +void R_StudioSetCullState( int iCull ) +{ + g_iBackFaceCull = iCull; +} + +/* +=============== +R_StudioRenderShadow + +just a prefab for render shadow +=============== +*/ +void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 ) +{ + if( !p1 || !p2 || !p3 || !p4 ) + return; + + if( TriSpriteTexture( gEngfuncs.pfnGetModelByIndex( iSprite ), 0 )) + { + TriRenderMode( kRenderTransAlpha ); + TriColor4f( 0.0f, 0.0f, 0.0f, 1.0f ); + + gu_vert_t* const out = ( gu_vert_t* )sceGuGetMemory( sizeof( gu_vert_t ) * 4 ); + out[0].uv[0] = 0.0f; + out[0].uv[1] = 0.0f; + VectorCopy( p1, out[0].xyz ); + out[1].uv[0] = 0.0f; + out[1].uv[1] = 0.1f; + VectorCopy( p2, out[1].xyz ); + out[2].uv[0] = 1.0f; + out[2].uv[1] = 1.0f; + VectorCopy( p3, out[2].xyz ); + out[3].uv[0] = 1.0f; + out[3].uv[1] = 0.0f; + VectorCopy( p4, out[3].xyz ); + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, 4, 0, out ); + + TriRenderMode( kRenderNormal ); + } +} + +/* +=============== +R_StudioMeshCompare + +Sorting opaque entities by model type +=============== +*/ +static int R_StudioMeshCompare( const void *a, const void *b ) +{ + if( FBitSet( ((const sortedmesh_t*)a)->flags, STUDIO_NF_ADDITIVE )) + return 1; + + if( FBitSet( ((const sortedmesh_t*)a)->flags, STUDIO_NF_MASKED )) + return -1; + + return 0; +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +_inline void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +{ + float *lv; + int i, prim, index; + + while(( i = *( ptricmds++ ))) + { + if( i < 0 ) + { + prim = GU_TRIANGLE_FAN; + i = -i; + } + else + { + prim = GU_TRIANGLE_STRIP; + } + + gu_vert_ftcv_t* const out = ( gu_vert_ftcv_t* )sceGuGetMemory( sizeof( gu_vert_ftcv_t ) * i ); + + for( index = 0; index < i; index++, ptricmds += 4 ) + { + byte color[4]; + R_StudioSetColorArray( ptricmds, pstudionorms, color ); + out[index].u = ptricmds[2] * s; + out[index].v = ptricmds[3] * t; + out[index].c = GUCOLOR4UBV( color ); + out[index].x = g_studio.verts[ptricmds[0]][0]; + out[index].y = g_studio.verts[ptricmds[0]][1]; + out[index].z = g_studio.verts[ptricmds[0]][2]; + } + sceGuDrawArray( prim, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF, i, 0, out ); + } +} + +/* +=============== +R_StudioDrawFloatMesh + +generic path +=============== +*/ +_inline void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +{ + float *lv; + int i, prim, index; + + while(( i = *( ptricmds++ ))) + { + if( i < 0 ) + { + prim = GU_TRIANGLE_FAN; + i = -i; + } + else + { + prim = GU_TRIANGLE_STRIP; + } + + gu_vert_ftcv_t* const out = ( gu_vert_ftcv_t* )sceGuGetMemory( sizeof( gu_vert_ftcv_t ) * i ); + + for( index = 0; index < i; index++, ptricmds += 4 ) + { + byte color[4]; + R_StudioSetColorArray( ptricmds, pstudionorms, color ); + out[index].u = HalfToFloat( ptricmds[2] ); + out[index].u = HalfToFloat( ptricmds[3] ); + out[index].c = GUCOLOR4UBV( color ); + out[index].x = g_studio.verts[ptricmds[0]][0]; + out[index].y = g_studio.verts[ptricmds[0]][1]; + out[index].z = g_studio.verts[ptricmds[0]][2]; + } + sceGuDrawArray( prim, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF, i, 0, out ); + } +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +_inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +{ + float *lv, *av; + int i, idx, prim, index; + qboolean glowShell = (scale > 0.0f) ? true : false; + vec3_t vert; + byte color[4]; + + while(( i = *( ptricmds++ ))) + { + if( i < 0 ) + { + prim = GU_TRIANGLE_FAN; + i = -i; + } + else + { + prim = GU_TRIANGLE_STRIP; + } + + gu_vert_ftcv_t* const out = ( gu_vert_ftcv_t* )sceGuGetMemory( sizeof( gu_vert_ftcv_t ) * i ); + + for( index = 0; index < i; index++, ptricmds += 4 ) + { + if( glowShell ) + { + color24 *clr = &RI.currententity->curstate.rendercolor; + + idx = g_studio.normaltable[ptricmds[0]]; + av = g_studio.verts[ptricmds[0]]; + lv = g_studio.norms[ptricmds[0]]; + VectorMA( av, scale, lv, vert ); + out[index].u = g_studio.chrome[idx][0] * s; + out[index].v = g_studio.chrome[idx][1] * t; + out[index].c = GUCOLOR4UB( clr->r, clr->g, clr->b, 255 ); + out[index].x = vert[0]; + out[index].y = vert[1]; + out[index].z = vert[2]; + } + else + { + idx = ptricmds[1]; + //lv = (float *)g_studio.lightvalues[ptricmds[1]]; //unused + R_StudioSetColorArray( ptricmds, pstudionorms, color ); + out[index].u = g_studio.chrome[idx][0] * s; + out[index].v = g_studio.chrome[idx][1] * t; + out[index].c = GUCOLOR4UBV( color ); + out[index].x = g_studio.verts[ptricmds[0]][0]; + out[index].y = g_studio.verts[ptricmds[0]][1]; + out[index].z = g_studio.verts[ptricmds[0]][2]; + } + } + sceGuDrawArray( prim, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF, i, 0, out ); + } +} + +#ifdef STUDIO_DRAWELEMENTS +_inline int R_StudioBuildIndices( qboolean tri_strip, int vertexState ) +{ + // build in indices + if( vertexState++ < 3 ) + { + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts; + } + else if( tri_strip ) + { + // flip triangles between clockwise and counter clockwise + if( vertexState & 1 ) + { + // draw triangle [n-2 n-1 n] + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 2; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts; + } + else + { + // draw triangle [n-1 n-2 n] + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 2; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts; + } + } + else + { + // draw triangle fan [0 n-1 n] + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - ( vertexState - 1 ); + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts; + } + + return vertexState; +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +_inline void R_StudioBuildArrayNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +{ + float *lv; + int i; + float alpha = tr.blend; + + while(( i = *( ptricmds++ ))) + { + int vertexState = 0; + qboolean tri_strip = true; + + if( i < 0 ) + { + tri_strip = false; + i = -i; + } + + for( ; i > 0; i--, ptricmds += 4 ) + { +#if 1 + byte cl[4]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; /* unused */ + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); + + g_studio.arrayverts[g_studio.numverts].u = ptricmds[2] * s; + g_studio.arrayverts[g_studio.numverts].v = ptricmds[3] * t; + g_studio.arrayverts[g_studio.numverts].c = GUCOLOR4UBV( cl ); + g_studio.arrayverts[g_studio.numverts].x = g_studio.verts[ptricmds[0]][0]; + g_studio.arrayverts[g_studio.numverts].y = g_studio.verts[ptricmds[0]][1]; + g_studio.arrayverts[g_studio.numverts].z = g_studio.verts[ptricmds[0]][2]; + g_studio.numverts++; +#else + GLubyte *cl; + + cl = g_studio.arraycolor[g_studio.numverts]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); + + g_studio.arraycoord[g_studio.numverts][0] = ptricmds[2] * s; + g_studio.arraycoord[g_studio.numverts][1] = ptricmds[3] * t; + + VectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] ); + g_studio.numverts++; +#endif + } + } +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +_inline void R_StudioBuildArrayFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +{ + float *lv; + int i; + float alpha = tr.blend; + + while(( i = *( ptricmds++ ))) + { + int vertexState = 0; + qboolean tri_strip = true; + + if( i < 0 ) + { + tri_strip = false; + i = -i; + } + + for( ; i > 0; i--, ptricmds += 4 ) + { +#if 1 + byte cl[4]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; /* unused */ + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); +#if 0 + __asm__ volatile ( + ".set push\n" // save assembler option + ".set noreorder\n" // suppress reordering + "lv.s S000, 4(%0)\n" // S000 = ptricmds[2], S001 = ptricmds[3] + "vh2f.s C000, S000\n" // S000 = ptricmds[2], S001 = ptricmds[3] + "sv.s S000, 0(%1)\n" // out[index].u = S000 = ptricmds[2] + "sv.s S001, 4(%1)\n" // out[index].v = S001 = ptricmds[3] + ".set pop\n" // restore assembler option + :: "r"( ptricmds ), "r"( &g_studio.arrayverts[g_studio.numverts] ) + ); +#else + g_studio.arrayverts[g_studio.numverts].u = HalfToFloat( ptricmds[2] ); + g_studio.arrayverts[g_studio.numverts].v = HalfToFloat( ptricmds[3] ); /* Error fixed */ +#endif + g_studio.arrayverts[g_studio.numverts].u = HalfToFloat( ptricmds[2] ); + g_studio.arrayverts[g_studio.numverts].v = HalfToFloat( ptricmds[3] ); /* Error fixed */ + g_studio.arrayverts[g_studio.numverts].c = GUCOLOR4UBV( cl ); + g_studio.arrayverts[g_studio.numverts].x = g_studio.verts[ptricmds[0]][0]; + g_studio.arrayverts[g_studio.numverts].y = g_studio.verts[ptricmds[0]][1]; + g_studio.arrayverts[g_studio.numverts].z = g_studio.verts[ptricmds[0]][2]; + g_studio.numverts++; +#else + GLubyte *cl; + cl = g_studio.arraycolor[g_studio.numverts]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); + + g_studio.arraycoord[g_studio.numverts][0] = HalfToFloat( ptricmds[2] ); + g_studio.arraycoord[g_studio.numverts][1] = HalfToFloat( ptricmds[2] ); + + VectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] ); + g_studio.numverts++; +#endif + } + } +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +_inline void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +{ + float *lv, *av; + int i, idx; + qboolean glowShell = (scale > 0.0f) ? true : false; + vec3_t vert; + float alpha = tr.blend; + + while(( i = *( ptricmds++ ))) + { + int vertexState = 0; + qboolean tri_strip = true; + + if( i < 0 ) + { + tri_strip = false; + i = -i; + } + + for( ; i > 0; i--, ptricmds += 4 ) + { +#if 1 + byte cl[4]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; /* unused */ + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + if( glowShell ) + { + idx = g_studio.normaltable[ptricmds[0]]; + av = g_studio.verts[ptricmds[0]]; + lv = g_studio.norms[ptricmds[0]]; + + cl[0] = RI.currententity->curstate.rendercolor.r; + cl[1] = RI.currententity->curstate.rendercolor.g; + cl[2] = RI.currententity->curstate.rendercolor.b; + cl[3] = 255; + + VectorMA( av, scale, lv, vert ); + + g_studio.arrayverts[g_studio.numverts].x = vert[0]; + g_studio.arrayverts[g_studio.numverts].y = vert[1]; + g_studio.arrayverts[g_studio.numverts].z = vert[2]; + } + else + { + idx = ptricmds[1]; + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); + + g_studio.arrayverts[g_studio.numverts].x = g_studio.verts[ptricmds[0]][0]; + g_studio.arrayverts[g_studio.numverts].y = g_studio.verts[ptricmds[0]][1]; + g_studio.arrayverts[g_studio.numverts].z = g_studio.verts[ptricmds[0]][2]; + } + + g_studio.arrayverts[g_studio.numverts].u = g_studio.chrome[idx][0] * s; + g_studio.arrayverts[g_studio.numverts].v = g_studio.chrome[idx][1] * t; + g_studio.arrayverts[g_studio.numverts].c = GUCOLOR4UBV( cl ); + g_studio.numverts++; +#else + GLubyte *cl; + cl = g_studio.arraycolor[g_studio.numverts]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + if( glowShell ) + { + idx = g_studio.normaltable[ptricmds[0]]; + av = g_studio.verts[ptricmds[0]]; + lv = g_studio.norms[ptricmds[0]]; + + cl[0] = RI.currententity->curstate.rendercolor.r; + cl[1] = RI.currententity->curstate.rendercolor.g; + cl[2] = RI.currententity->curstate.rendercolor.b; + cl[3] = 255; + + VectorMA( av, scale, lv, vert ); + VectorCopy( vert, g_studio.arrayverts[g_studio.numverts] ); + } + else + { + idx = ptricmds[1]; + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); + + VectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] ); + } + + g_studio.arraycoord[g_studio.numverts][0] = g_studio.chrome[idx][0] * s; + g_studio.arraycoord[g_studio.numverts][1] = g_studio.chrome[idx][1] * t; + + g_studio.numverts++; +#endif + } + } +} + +_inline void R_StudioDrawArrays( uint startverts, uint startelems ) +{ + sceKernelDcacheWritebackAll(); // writeback cache + sceGuDrawArray( GU_TRIANGLES, GU_TEXTURE_32BITF | GU_COLOR_8888 | + GU_VERTEX_32BITF | GU_INDEX_16BIT, g_studio.numelems - startelems, + &g_studio.arrayelems[startelems], g_studio.arrayverts ); +} +#endif // STUDIO_DRAWELEMENTS + +/* +=============== +R_StudioDrawPoints + +=============== +*/ +static void R_StudioDrawPoints( void ) +{ + int i, j, k, m_skinnum; + float shellscale = 0.0f; + qboolean need_sort = false; + byte *pvertbone; + byte *pnormbone; + vec3_t *pstudioverts; + vec3_t *pstudionorms; + mstudiotexture_t *ptexture; + mstudiomesh_t *pmesh; + short *pskinref; + float lv_tmp; + + if( !m_pStudioHeader ) return; + +#ifdef STUDIO_DRAWELEMENTS + g_studio.numverts = g_studio.numelems = 0; +#endif // STUDIO_DRAWELEMENTS + + // safety bounding the skinnum + m_skinnum = bound( 0, RI.currententity->curstate.skin, ( m_pStudioHeader->numskinfamilies - 1 )); + ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); + pvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex); + pnormbone = ((byte *)m_pStudioHeader + m_pSubModel->norminfoindex); + + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex); + pstudioverts = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->vertindex); + pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); + + pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); + if( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref); + + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 ) + { + mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex); + mstudioboneweight_t *pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendnorminfoindex); + matrix3x4 skinMat; + + for( i = 0; i < m_pSubModel->numverts; i++ ) + { + R_StudioComputeSkinMatrix( &pvertweight[i], skinMat ); + Matrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.verts[i] ); + R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); + } + + for( i = 0; i < m_pSubModel->numnorms; i++ ) + { + R_StudioComputeSkinMatrix( &pnormweight[i], skinMat ); + Matrix3x4_VectorRotate( skinMat, pstudionorms[i], g_studio.norms[i] ); + } + } + else + { + for( i = 0; i < m_pSubModel->numverts; i++ ) + { + Matrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], g_studio.verts[i] ); + R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); + } + } + + // generate shared normals for properly scaling glowing shell + if( RI.currententity->curstate.renderfx == kRenderFxGlowShell ) + { + float factor = (1.0f / 128.0f); + shellscale = Q_max( factor, RI.currententity->curstate.renderamt * factor ); + R_StudioBuildNormalTable(); + R_StudioGenerateNormals(); + } + + for( j = k = 0; j < m_pSubModel->nummesh; j++ ) + { + g_nFaceFlags = ptexture[pskinref[pmesh[j].skinref]].flags | g_nForceFaceFlags; + + // fill in sortedmesh info + g_studio.meshes[j].flags = g_nFaceFlags; + g_studio.meshes[j].mesh = &pmesh[j]; + + if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE )) + need_sort = true; + + if( RI.currententity->curstate.rendermode == kRenderTransAdd ) + { + for( i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ ) + { + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms ); + VectorSet( g_studio.lightvalues[k], tr.blend, tr.blend, tr.blend ); + } + } + else + { + for( i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ ) + { + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS )) + R_StudioLighting( &lv_tmp, -1, g_nFaceFlags, g_studio.norms[k] ); + else R_StudioLighting( &lv_tmp, *pnormbone, g_nFaceFlags, (float *)pstudionorms ); + + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms ); + VectorScale( g_studio.lightcolor, lv_tmp, g_studio.lightvalues[k] ); + } + } + } + + if( r_studio_sort_textures->value && need_sort ) + { + // resort opaque and translucent meshes draw order + qsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), R_StudioMeshCompare ); + } + + // NOTE: rewind normals at start + pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); + + for( j = 0; j < m_pSubModel->nummesh; j++ ) + { + float oldblend = tr.blend; +#ifdef STUDIO_DRAWELEMENTS + uint startArrayVerts = g_studio.numverts; + uint startArrayElems = g_studio.numelems; +#endif // STUDIO_DRAWELEMENTS + short *ptricmds; + float s, t; + + pmesh = g_studio.meshes[j].mesh; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + + g_nFaceFlags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags; + + s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width; + t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height; + + if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) + { + sceGuEnable( GU_ALPHA_TEST ); + sceGuAlphaFunc( GU_GREATER, 0x7f, 0xff ); + sceGuDepthMask( GU_FALSE ); + + if( R_ModelOpaque( RI.currententity->curstate.rendermode )) + tr.blend = 1.0f; + } + else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE )) + { + if( R_ModelOpaque( RI.currententity->curstate.rendermode )) + { + sceGuBlendFunc( GU_ADD, GU_FIX, GU_FIX, GUBLEND1, GUBLEND1 ); + sceGuDepthMask( GU_TRUE ); + sceGuEnable( GU_BLEND ); + + R_AllowFog( false ); + } + else sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, GUBLEND1 ); + } + + R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] ); +#ifdef STUDIO_DRAWELEMENTS + if( CVAR_TO_BOOL(r_studio_drawelements) ) + { + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioBuildArrayChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); + else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS )) + R_StudioBuildArrayFloatMesh( ptricmds, pstudionorms ); + else R_StudioBuildArrayNormalMesh( ptricmds, pstudionorms, s, t ); + + R_StudioDrawArrays( startArrayVerts, startArrayElems ); + } + else +#endif // STUDIO_DRAWELEMENTS + { + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); + else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS )) + R_StudioDrawFloatMesh( ptricmds, pstudionorms ); + else R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t ); + } + + if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) + { + sceGuAlphaFunc( GU_GREATER, DEFAULT_ALPHATEST, 0xff ); + sceGuDisable( GU_ALPHA_TEST ); + } + else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode )) + { + sceGuDepthMask( GU_FALSE ); + sceGuDisable( GU_BLEND ); + + R_AllowFog( true ); + } + + r_stats.c_studio_polys += pmesh->numtris; + tr.blend = oldblend; + } +} + +/* +=============== +R_StudioDrawHulls + +=============== +*/ +static void R_StudioDrawHulls( void ) +{ +#if 0 + float alpha, lv; + int i, j; + + if( r_drawentities->value == 4 ) + alpha = 0.5f; + else alpha = 1.0f; + + GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); + sceGuTexFunc(GU_TFX_MODULATE , GU_TCC_RGBA); + + for( i = 0; i < m_pStudioHeader->numhitboxes; i++ ) + { + mstudiobbox_t *pbbox = (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex); + vec3_t tmp, p[8]; + + for( j = 0; j < 8; j++ ) + { + tmp[0] = (j & 1) ? pbbox[i].bbmin[0] : pbbox[i].bbmax[0]; + tmp[1] = (j & 2) ? pbbox[i].bbmin[1] : pbbox[i].bbmax[1]; + tmp[2] = (j & 4) ? pbbox[i].bbmin[2] : pbbox[i].bbmax[2]; + + Matrix3x4_VectorTransform( g_studio.bonestransform[pbbox[i].bone], tmp, p[j] ); + } + + j = (pbbox[i].group % 8); + + TriBegin( TRI_QUADS ); + TriColor4f( hullcolor[j][0], hullcolor[j][1], hullcolor[j][2], alpha ); + + for( j = 0; j < 6; j++ ) + { + VectorClear( tmp ); + tmp[j % 3] = (j < 3) ? 1.0f : -1.0f; + R_StudioLighting( &lv, pbbox[i].bone, 0, tmp ); + + TriBrightness( lv ); + TriVertex3fv( p[boxpnt[j][0]] ); + TriVertex3fv( p[boxpnt[j][1]] ); + TriVertex3fv( p[boxpnt[j][2]] ); + TriVertex3fv( p[boxpnt[j][3]] ); + } + TriEnd(); + } +#endif +} + +/* +=============== +R_StudioDrawAbsBBox + +=============== +*/ +static void R_StudioDrawAbsBBox( void ) +{ +#if 0 + vec3_t p[8], tmp; + float lv; + int i; + + // looks ugly, skip + if( RI.currententity == gEngfuncs.GetViewModel() ) + return; + + if( !R_StudioComputeBBox( p )) + return; + + GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); + TriColor4f( 0.5f, 0.5f, 1.0f, 0.5f ); + TriRenderMode( kRenderTransAdd ); + + TriBegin( TRI_QUADS ); + for( i = 0; i < 6; i++ ) + { + VectorClear( tmp ); + tmp[i % 3] = (i < 3) ? 1.0f : -1.0f; + R_StudioLighting( &lv, -1, 0, tmp ); + + TriBrightness( lv ); + TriVertex3fv( p[boxpnt[i][0]] ); + TriVertex3fv( p[boxpnt[i][1]] ); + TriVertex3fv( p[boxpnt[i][2]] ); + TriVertex3fv( p[boxpnt[i][3]] ); + } + TriEnd(); + TriRenderMode( kRenderNormal ); +#endif +} + +/* +=============== +R_StudioDrawBones + +=============== +*/ +static void R_StudioDrawBones( void ) +{ +#if 0 + mstudiobone_t *pbones = (mstudiobone_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + vec3_t point; + int i; + + pglDisable( GL_TEXTURE_2D ); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( pbones[i].parent >= 0 ) + { + pglPointSize( 3.0f ); + pglColor3f( 1, 0.7f, 0 ); + pglBegin( GL_LINES ); + + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[pbones[i].parent], point ); + pglVertex3fv( point ); + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); + pglVertex3fv( point ); + + pglEnd(); + + pglColor3f( 0, 0, 0.8f ); + pglBegin( GL_POINTS ); + if( pbones[pbones[i].parent].parent != -1 ) + { + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[pbones[i].parent], point ); + pglVertex3fv( point ); + } + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); + pglVertex3fv( point ); + pglEnd(); + } + else + { + // draw parent bone node + pglPointSize( 5.0f ); + pglColor3f( 0.8f, 0, 0 ); + pglBegin( GL_POINTS ); + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); + pglVertex3fv( point ); + pglEnd(); + } + } + + pglPointSize( 1.0f ); + pglEnable( GL_TEXTURE_2D ); +#endif +} + +static void R_StudioDrawAttachments( void ) +{ +#if 0 + int i; + + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + + for( i = 0; i < m_pStudioHeader->numattachments; i++ ) + { + mstudioattachment_t *pattachments; + vec3_t v[4]; + + pattachments = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].org, v[0] ); + Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[0], v[1] ); + Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[1], v[2] ); + Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[2], v[3] ); + + pglBegin( GL_LINES ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv( v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv (v[1] ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv (v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv (v[2] ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv (v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv( v[3] ); + pglEnd(); + + pglPointSize( 5.0f ); + pglColor3f( 0, 1, 0 ); + pglBegin( GL_POINTS ); + pglVertex3fv( v[0] ); + pglEnd(); + pglPointSize( 1.0f ); + } + + pglEnable( GL_TEXTURE_2D ); + pglEnable( GL_DEPTH_TEST ); +#endif +} + +/* +=============== +R_StudioSetRemapColors + +=============== +*/ +static void R_StudioSetRemapColors( int newTop, int newBottom ) +{ + gEngfuncs.CL_AllocRemapInfo( RI.currententity, newTop, newBottom ); + + if( gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )) + { + gEngfuncs.CL_UpdateRemapInfo( RI.currententity, newTop, newBottom ); + m_fDoRemap = true; + } +} + +void R_StudioResetPlayerModels( void ) +{ + memset( g_studio.player_models, 0, sizeof( g_studio.player_models )); +} + +/* +=============== +R_StudioSetupPlayerModel + +=============== +*/ +static model_t *R_StudioSetupPlayerModel( int index ) +{ + player_info_t *info = gEngfuncs.pfnPlayerInfo( index ); + player_model_t *state; + + state = &g_studio.player_models[index]; + + // g-cont: force for "dev-mode", non-local games and menu preview + if(( gpGlobals->developer || !ENGINE_GET_PARM( PARM_LOCAL_GAME ) || !RI.drawWorld ) && info->model[0] ) + { + if( Q_strcmp( state->name, info->model )) + { + Q_strncpy( state->name, info->model, sizeof( state->name )); + state->name[sizeof( state->name ) - 1] = 0; + + Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); + + if( gEngfuncs.FS_FileExists( state->modelname, false )) + state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); + else state->model = NULL; + + if( !state->model ) + state->model = RI.currententity->model; + } + } + else + { + if( state->model != RI.currententity->model ) + state->model = RI.currententity->model; + state->name[0] = 0; + } + + return state->model; +} + +/* +================ +R_GetEntityRenderMode + +check for texture flags +================ +*/ +int R_GetEntityRenderMode( cl_entity_t *ent ) +{ + int i, opaque, trans; + mstudiotexture_t *ptexture; + cl_entity_t *oldent; + model_t *model; + studiohdr_t *phdr; + + oldent = RI.currententity; + RI.currententity = ent; + + if( ent->player ) // check it for real playermodel + model = R_StudioSetupPlayerModel( ent->curstate.number - 1 ); + else model = ent->model; + + RI.currententity = oldent; + + if(( phdr = STUDIOEXTRADATA( model )) == NULL ) + { + if( R_ModelOpaque( ent->curstate.rendermode )) + { + // forcing to choose right sorting type + if(( model && model->type == mod_brush ) && FBitSet( model->flags, MODEL_TRANSPARENT )) + return kRenderTransAlpha; + } + return ent->curstate.rendermode; + } + ptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex); + + for( opaque = trans = i = 0; i < phdr->numtextures; i++, ptexture++ ) + { + // ignore chrome & additive it's just a specular-like effect + if( FBitSet( ptexture->flags, STUDIO_NF_ADDITIVE ) && !FBitSet( ptexture->flags, STUDIO_NF_CHROME )) + trans++; + else opaque++; + } + + // if model is more additive than opaque + if( trans > opaque ) + return kRenderTransAdd; + return ent->curstate.rendermode; +} + +/* +=============== +R_StudioClientEvents + +=============== +*/ +static void R_StudioClientEvents( void ) +{ + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + cl_entity_t *e = RI.currententity; + int i, sequence; + float end, start; + + if( g_studio.frametime == 0.0 ) + return; // gamepaused + + // fill attachments with interpolated origin + if( m_pStudioHeader->numattachments <= 0 ) + { + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[0] ); + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[1] ); + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[2] ); + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[3] ); + } + + if( FBitSet( e->curstate.effects, EF_MUZZLEFLASH )) + { + dlight_t *el = gEngfuncs.CL_AllocElight( 0 ); + + ClearBits( e->curstate.effects, EF_MUZZLEFLASH ); + VectorCopy( e->attachment[0], el->origin ); + el->die = gpGlobals->time + 0.05f; + el->color.r = 255; + el->color.g = 192; + el->color.b = 64; + el->decay = 320; + el->radius = 24; + } + + sequence = bound( 0, e->curstate.sequence, m_pStudioHeader->numseq - 1 ); + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + + // no events for this animation + if( pseqdesc->numevents == 0 ) + return; + + end = R_StudioEstimateFrame( e, pseqdesc ); + start = end - e->curstate.framerate * gpGlobals->frametime * pseqdesc->fps; + pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); + + if( e->latched.sequencetime == e->curstate.animtime ) + { + if( !FBitSet( pseqdesc->flags, STUDIO_LOOPING )) + start = -0.01f; + } + + for( i = 0; i < pseqdesc->numevents; i++ ) + { + // ignore all non-client-side events + if( pevent[i].event < EVENT_CLIENT ) + continue; + + if( (float)pevent[i].frame > start && pevent[i].frame <= end ) + gEngfuncs.pfnStudioEvent( &pevent[i], e ); + } +} + +/* +=============== +R_StudioGetForceFaceFlags + +=============== +*/ +int R_StudioGetForceFaceFlags( void ) +{ + return g_nForceFaceFlags; +} + +/* +=============== +R_StudioSetForceFaceFlags + +=============== +*/ +void R_StudioSetForceFaceFlags( int flags ) +{ + g_nForceFaceFlags = flags; +} + +/* +=============== +pfnStudioSetHeader + +=============== +*/ +void R_StudioSetHeader( studiohdr_t *pheader ) +{ + m_pStudioHeader = pheader; + m_fDoRemap = false; +} + +/* +=============== +R_StudioSetRenderModel + +=============== +*/ +void R_StudioSetRenderModel( model_t *model ) +{ + RI.currentmodel = model; +} + +/* +=============== +R_StudioSetupRenderer + +=============== +*/ +static void R_StudioSetupRenderer( int rendermode ) +{ + studiohdr_t *phdr = m_pStudioHeader; + int i; + + if( rendermode > kRenderTransAdd ) rendermode = 0; + g_studio.rendermode = bound( 0, rendermode, kRenderTransAdd ); + + sceGuTexFunc(GU_TFX_MODULATE , GU_TCC_RGBA); + sceGuDisable( GU_ALPHA_TEST ); + sceGuShadeModel( GU_SMOOTH ); + + // a point to setup local to world transform for boneweighted models + if( phdr && FBitSet( phdr->flags, STUDIO_HAS_BONEINFO )) + { + // NOTE: extended boneinfo goes immediately after bones + mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)phdr + phdr->boneindex + phdr->numbones * sizeof( mstudiobone_t )); + + for( i = 0; i < phdr->numbones; i++ ) + Matrix3x4_ConcatTransforms( g_studio.worldtransform[i], g_studio.bonestransform[i], boneinfo[i].poseToBone ); + } +} + +/* +=============== +R_StudioRestoreRenderer + +=============== +*/ +static void R_StudioRestoreRenderer( void ) +{ + if( g_studio.rendermode != kRenderNormal ) + sceGuDisable( GU_BLEND ); + + sceGuTexFunc( GU_TFX_REPLACE, GU_TCC_RGBA ); + sceGuShadeModel( GU_FLAT ); + + m_fDoRemap = false; +} + +/* +=============== +R_StudioSetChromeOrigin + +=============== +*/ +void R_StudioSetChromeOrigin( void ) +{ + VectorCopy( RI.vieworg, g_studio.chrome_origin ); +} + +/* +=============== +pfnIsHardware + +Xash3D is always works in hardware mode +=============== +*/ +static int pfnIsHardware( void ) +{ + return 1; // 0 is Software, 1 is OpenGL, 2 is Direct3D +} + +/* +=============== +R_StudioDrawPointsShadow + +=============== +*/ +static void R_StudioDrawPointsShadow( void ) +{ + float *av, height; + float vec_x, vec_y; + mstudiomesh_t *pmesh; + vec3_t point; + int i, k; + + if( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW )) + return; + + if( glState.stencilEnabled ) + sceGuEnable( GU_STENCIL_TEST ); + + height = g_studio.lightspot[2] + 1.0f; + vec_x = -g_studio.lightvec[0] * 8.0f; + vec_y = -g_studio.lightvec[1] * 8.0f; + + for( k = 0; k < m_pSubModel->nummesh; k++ ) + { + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + k; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + + r_stats.c_studio_polys += pmesh->numtris; + + while( ( i = *( ptricmds++ ) ) ) + { + int prim; + if( i < 0 ) + { + prim = GU_TRIANGLE_FAN; + i = -i; + } + else + { + prim = GU_TRIANGLE_STRIP; + } + + gu_vert_fv_t* const out = ( gu_vert_fv_t* )sceGuGetMemory( sizeof( gu_vert_fv_t ) * i ); + + for(int index = 0; index < i; index++, ptricmds += 4) + { + av = g_studio.verts[ptricmds[0]]; + point[0] = av[0] - (vec_x * ( av[2] - g_studio.lightspot[2] )); + point[1] = av[1] - (vec_y * ( av[2] - g_studio.lightspot[2] )); + point[2] = g_studio.lightspot[2] + 1.0f; + + out[index].x = point[0]; + out[index].y = point[1]; + out[index].z = point[2]; + } + sceGuDrawArray( prim, GU_VERTEX_32BITF, i, 0, out ); + } + } + if( glState.stencilEnabled ) + sceGuDisable( GU_STENCIL_TEST ); +} + +/* +=============== +GL_StudioSetRenderMode + +set rendermode for studiomodel +=============== +*/ +void GL_StudioSetRenderMode( int rendermode ) +{ + switch( rendermode ) + { + case kRenderNormal: + break; + case kRenderTransColor: + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuEnable( GU_BLEND ); + break; + case kRenderTransAdd: + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuColor( GUCOLOR4F( tr.blend, tr.blend, tr.blend, 1.0f ) ); + sceGuBlendFunc( GU_ADD, GU_FIX, GU_FIX, GUBLEND1, GUBLEND1 ); + sceGuDepthMask( GU_TRUE ); + sceGuEnable( GU_BLEND ); + break; + default: + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuColor( GUCOLOR4F( 1.0f, 1.0f, 1.0f, tr.blend ) ); + sceGuDepthMask( GU_FALSE ); + sceGuEnable( GU_BLEND ); + break; + } +} + +/* +=============== +GL_StudioDrawShadow + +g-cont: don't modify this code it's 100% matched with +original GoldSrc code and used in some mods to enable +studio shadows with some asm tricks +=============== +*/ +static void GL_StudioDrawShadow( void ) +{ + sceGuDepthMask( GU_FALSE ); + + if( r_shadows.value && g_studio.rendermode != kRenderTransAdd && !FBitSet( RI.currentmodel->flags, STUDIO_AMBIENT_LIGHT )) + { + float color = 1.0f - (tr.blend * 0.5f); + + sceGuDisable( GU_TEXTURE_2D ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuEnable( GU_BLEND ); + sceGuColor( GUCOLOR4F( 0.0f, 0.0f, 0.0f, 1.0f - color ) ); + + sceGuDepthFunc( GU_LESS ); + R_StudioDrawPointsShadow(); + sceGuDepthFunc( GU_LEQUAL ); + + sceGuEnable( GU_TEXTURE_2D ); + sceGuDisable( GU_BLEND ); + sceGuColor( GUCOLOR4F( 1.0f, 1.0f, 1.0f, 1.0f ) ); + sceGuShadeModel( GU_SMOOTH ); + } +} + +/* +==================== +StudioRenderFinal + +==================== +*/ +void R_StudioRenderFinal( void ) +{ + int i, rendermode; + + rendermode = R_StudioGetForceFaceFlags() ? kRenderTransAdd : RI.currententity->curstate.rendermode; + R_StudioSetupRenderer( rendermode ); + + if( r_drawentities->value == 2 ) + { + R_StudioDrawBones(); + } + else if( r_drawentities->value == 3 ) + { + R_StudioDrawHulls(); + } + else + { + for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) + { + R_StudioSetupModel( i, (void**)&m_pBodyPart, (void**)&m_pSubModel ); + + GL_StudioSetRenderMode( rendermode ); + R_StudioDrawPoints(); + GL_StudioDrawShadow(); + } + } + + if( r_drawentities->value == 4 ) + { + TriRenderMode( kRenderTransAdd ); + R_StudioDrawHulls( ); + TriRenderMode( kRenderNormal ); + } + + if( r_drawentities->value == 5 ) + { + R_StudioDrawAbsBBox( ); + } + + if( r_drawentities->value == 6 ) + { + R_StudioDrawAttachments(); + } + + if( r_drawentities->value == 7 ) + { + vec3_t origin; +#if 1 +#else + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin ); + + pglBegin( GL_LINES ); + pglColor3f( 1, 0.5, 0 ); + pglVertex3fv( origin ); + pglVertex3fv( g_studio.lightspot ); + pglEnd(); + + pglBegin( GL_LINES ); + pglColor3f( 0, 0.5, 1 ); + VectorMA( g_studio.lightspot, -64.0f, g_studio.lightvec, origin ); + pglVertex3fv( g_studio.lightspot ); + pglVertex3fv( origin ); + pglEnd(); + + pglPointSize( 5.0f ); + pglColor3f( 1, 0, 0 ); + pglBegin( GL_POINTS ); + pglVertex3fv( g_studio.lightspot ); + pglEnd(); + pglPointSize( 1.0f ); + + pglEnable( GL_DEPTH_TEST ); + pglEnable( GL_TEXTURE_2D ); +#endif + } + + R_StudioRestoreRenderer(); +} + +/* +==================== +StudioRenderModel + +==================== +*/ +void R_StudioRenderModel( void ) +{ + R_StudioSetChromeOrigin(); + R_StudioSetForceFaceFlags( 0 ); + + if( RI.currententity->curstate.renderfx == kRenderFxGlowShell ) + { + RI.currententity->curstate.renderfx = kRenderFxNone; + + R_StudioRenderFinal( ); + + R_StudioSetForceFaceFlags( STUDIO_NF_CHROME ); + TriSpriteTexture( R_GetChromeSprite(), 0 ); + RI.currententity->curstate.renderfx = kRenderFxGlowShell; + + R_StudioRenderFinal( ); + } + else + { + R_StudioRenderFinal( ); + } +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void R_StudioEstimateGait( entity_state_t *pplayer ) +{ + vec3_t est_velocity; + float dt; + + dt = bound( 0.0f, g_studio.frametime, 1.0f ); + + if( dt == 0.0f || m_pPlayerInfo->renderframe == tr.realframecount ) + { + m_flGaitMovement = 0; + return; + } + + VectorSubtract( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = VectorLength( est_velocity ); + + if( dt <= 0.0f || m_flGaitMovement / dt < 5.0f ) + { + m_flGaitMovement = 0.0f; + est_velocity[0] = 0.0f; + est_velocity[1] = 0.0f; + } + + if( est_velocity[1] == 0.0f && est_velocity[0] == 0.0f ) + { + float flYawDiff = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw; + + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if( flYawDiff > 180.0f ) flYawDiff -= 360.0f; + if( flYawDiff < -180.0f ) flYawDiff += 360.0f; + + if( dt < 0.25f ) + flYawDiff *= dt * 4.0f; + else flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0.0f; + } + else + { + m_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180 / M_PI_F ); + if( m_pPlayerInfo->gaityaw > 180.0f ) m_pPlayerInfo->gaityaw = 180.0f; + if( m_pPlayerInfo->gaityaw < -180.0f ) m_pPlayerInfo->gaityaw = -180.0f; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void R_StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + int iBlend; + float dt, flYaw; // view direction relative to movement + + if( RI.currententity->curstate.sequence >= m_pStudioHeader->numseq ) + RI.currententity->curstate.sequence = 0; + + dt = bound( 0.0f, g_studio.frametime, 1.0f ); + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI.currententity->curstate.sequence; + + R_StudioPlayerBlend( pseqdesc, &iBlend, &RI.currententity->angles[PITCH] ); + + RI.currententity->latched.prevangles[PITCH] = RI.currententity->angles[PITCH]; + RI.currententity->curstate.blending[0] = iBlend; + RI.currententity->latched.prevblending[0] = RI.currententity->curstate.blending[0]; + RI.currententity->latched.prevseqblending[0] = RI.currententity->curstate.blending[0]; + R_StudioEstimateGait( pplayer ); + + // calc side to side turning + flYaw = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + if( flYaw < -180.0f ) flYaw = flYaw + 360.0f; + if( flYaw > 180.0f ) flYaw = flYaw - 360.0f; + + if( flYaw > 120.0f ) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180.0f; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180.0f; + } + else if( flYaw < -120.0f ) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180.0f; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180.0f; + } + + // adjust torso + RI.currententity->curstate.controller[0] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->curstate.controller[1] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->curstate.controller[2] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->curstate.controller[3] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0]; + RI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1]; + RI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2]; + RI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3]; + + RI.currententity->angles[YAW] = m_pPlayerInfo->gaityaw; + if( RI.currententity->angles[YAW] < -0 ) RI.currententity->angles[YAW] += 360.0f; + RI.currententity->latched.prevangles[YAW] = RI.currententity->angles[YAW]; + + if( pplayer->gaitsequence >= m_pStudioHeader->numseq ) + pplayer->gaitsequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // calc gait frame + if( pseqdesc->linearmovement[0] > 0 ) + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + else m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + + // do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if( m_pPlayerInfo->gaitframe < 0 ) m_pPlayerInfo->gaitframe += pseqdesc->numframes; +} + +/* +=============== +R_StudioDrawPlayer + +=============== +*/ +static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + int m_nPlayerIndex; + alight_t lighting; + vec3_t dir; + + m_nPlayerIndex = pplayer->number - 1; + + if( m_nPlayerIndex < 0 || m_nPlayerIndex >= ENGINE_GET_PARM( PARM_MAX_CLIENTS ) ) + return 0; + + RI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex ); + if( RI.currentmodel == NULL ) + return 0; + + R_StudioSetHeader((studiohdr_t *)STUDIOEXTRADATA( RI.currentmodel )); + + if( pplayer->gaitsequence ) + { + vec3_t orig_angles; + + m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); + VectorCopy( RI.currententity->angles, orig_angles ); + + R_StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + R_StudioSetUpTransform( RI.currententity ); + VectorCopy( orig_angles, RI.currententity->angles ); + } + else + { + RI.currententity->curstate.controller[0] = 127; + RI.currententity->curstate.controller[1] = 127; + RI.currententity->curstate.controller[2] = 127; + RI.currententity->curstate.controller[3] = 127; + RI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0]; + RI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1]; + RI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2]; + RI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3]; + + m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + R_StudioSetUpTransform( RI.currententity ); + } + + if( flags & STUDIO_RENDER ) + { + // see if the bounding box lets us trivially reject, also sets + if( !R_StudioCheckBBox( )) + return 0; + + r_stats.c_studio_models_drawn++; + g_studio.framecount++; // render data cache cookie + + if( m_pStudioHeader->numbodyparts == 0 ) + return 1; + } + + m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); + R_StudioSetupBones( RI.currententity ); + R_StudioSaveBones( ); + + m_pPlayerInfo->renderframe = tr.realframecount; + m_pPlayerInfo = NULL; + + if( flags & STUDIO_EVENTS ) + { + R_StudioCalcAttachments( ); + R_StudioClientEvents( ); + + // copy attachments into global entity array + if( RI.currententity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( RI.currententity->index ); + memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if( flags & STUDIO_RENDER ) + { + if( cl_himodels->value && RI.currentmodel != RI.currententity->model ) + { + // show highest resolution multiplayer model + RI.currententity->curstate.body = 255; + } + + if( !( !gpGlobals->developer && ENGINE_GET_PARM( PARM_MAX_CLIENTS ) == 1 ) && ( RI.currentmodel == RI.currententity->model )) + RI.currententity->curstate.body = 1; // force helmet + + lighting.plightvec = dir; + R_StudioDynamicLight( RI.currententity, &lighting ); + + R_StudioEntityLight( &lighting ); + + // model and frame independant + R_StudioSetupLighting( &lighting ); + + m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); + + // get remap colors + g_nTopColor = m_pPlayerInfo->topcolor; + g_nBottomColor = m_pPlayerInfo->bottomcolor; + + if( g_nTopColor < 0 ) g_nTopColor = 0; + if( g_nTopColor > 360 ) g_nTopColor = 360; + if( g_nBottomColor < 0 ) g_nBottomColor = 0; + if( g_nBottomColor > 360 ) g_nBottomColor = 360; + + R_StudioSetRemapColors( g_nTopColor, g_nBottomColor ); + + R_StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if( pplayer->weaponmodel ) + { + cl_entity_t saveent = *RI.currententity; + model_t *pweaponmodel = gEngfuncs.pfnGetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)STUDIOEXTRADATA( pweaponmodel ); + + R_StudioMergeBones( RI.currententity, pweaponmodel ); + R_StudioSetupLighting( &lighting ); + R_StudioRenderModel( ); + R_StudioCalcAttachments( ); + + *RI.currententity = saveent; + } + } + + return 1; +} + +/* +=============== +R_StudioDrawModel + +=============== +*/ +static int R_StudioDrawModel( int flags ) +{ + alight_t lighting; + vec3_t dir; + + if( RI.currententity->curstate.renderfx == kRenderFxDeadPlayer ) + { + qboolean save_interpolate; + entity_state_t deadplayer; + int result; + + if( RI.currententity->curstate.renderamt <= 0 || + RI.currententity->curstate.renderamt > ENGINE_GET_PARM( PARM_MAX_CLIENTS ) ) + return 0; + + // get copy of player + deadplayer = *R_StudioGetPlayerState( RI.currententity->curstate.renderamt - 1 ); + + // clear weapon, movement state + deadplayer.number = RI.currententity->curstate.renderamt; + deadplayer.weaponmodel = 0; + deadplayer.gaitsequence = 0; + + deadplayer.movetype = MOVETYPE_NONE; + VectorCopy( RI.currententity->curstate.angles, deadplayer.angles ); + VectorCopy( RI.currententity->curstate.origin, deadplayer.origin ); + + save_interpolate = g_studio.interpolate; + g_studio.interpolate = false; + result = R_StudioDrawPlayer( flags, &deadplayer ); // draw as though it were a player + g_studio.interpolate = save_interpolate; + + return result; + } + + R_StudioSetHeader((studiohdr_t *)STUDIOEXTRADATA( RI.currentmodel )); + + R_StudioSetUpTransform( RI.currententity ); + + if( flags & STUDIO_RENDER ) + { + // see if the bounding box lets us trivially reject, also sets + if( !R_StudioCheckBBox( )) + return 0; + + r_stats.c_studio_models_drawn++; + g_studio.framecount++; // render data cache cookie + + if( m_pStudioHeader->numbodyparts == 0 ) + return 1; + } + + if( RI.currententity->curstate.movetype == MOVETYPE_FOLLOW ) + R_StudioMergeBones( RI.currententity, RI.currentmodel ); + else R_StudioSetupBones( RI.currententity ); + + R_StudioSaveBones(); + + if( flags & STUDIO_EVENTS ) + { + R_StudioCalcAttachments( ); + R_StudioClientEvents( ); + + // copy attachments into global entity array + if( RI.currententity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( RI.currententity->index ); + memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if( flags & STUDIO_RENDER ) + { + lighting.plightvec = dir; + R_StudioDynamicLight( RI.currententity, &lighting ); + + R_StudioEntityLight( &lighting ); + + // model and frame independant + R_StudioSetupLighting( &lighting ); + + // get remap colors + g_nTopColor = RI.currententity->curstate.colormap & 0xFF; + g_nBottomColor = (RI.currententity->curstate.colormap & 0xFF00) >> 8; + + R_StudioSetRemapColors( g_nTopColor, g_nBottomColor ); + + R_StudioRenderModel(); + } + + return 1; +} + +/* +================= +R_StudioDrawModelInternal +================= +*/ +void R_StudioDrawModelInternal( cl_entity_t *e, int flags ) +{ + if( !RI.drawWorld ) + { + if( e->player ) + R_StudioDrawPlayer( flags, &e->curstate ); + else R_StudioDrawModel( flags ); + } + else + { + // select the properly method + if( e->player ) + pStudioDraw->StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 )); + else pStudioDraw->StudioDrawModel( flags ); + } +} + +/* +================= +R_DrawStudioModel +================= +*/ +void R_DrawStudioModel( cl_entity_t *e ) +{ + if( FBitSet( RI.params, RP_ENVVIEW )) + return; + + R_StudioSetupTimings(); + + if( e->player ) + { + R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); + } + else + { + if( e->curstate.movetype == MOVETYPE_FOLLOW && e->curstate.aiment > 0 ) + { + cl_entity_t *parent = gEngfuncs.GetEntityByIndex( e->curstate.aiment ); + + if( parent && parent->model && parent->model->type == mod_studio ) + { + RI.currententity = parent; + R_StudioDrawModelInternal( RI.currententity, 0 ); + VectorCopy( parent->curstate.origin, e->curstate.origin ); + VectorCopy( parent->origin, e->origin ); + RI.currententity = e; + } + } + + R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); + } +} + +/* +================= +R_RunViewmodelEvents +================= +*/ +void R_RunViewmodelEvents( void ) +{ + int i; + vec3_t simorg; + + if( r_drawviewmodel->value == 0 ) + return; + + if( ENGINE_GET_PARM( PARM_THIRDPERSON )) + return; + + // ignore in thirdperson, camera view or client is died + if( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer()) + return; + + RI.currententity = gEngfuncs.GetViewModel(); + + if( !RI.currententity->model || RI.currententity->model->type != mod_studio ) + return; + + R_StudioSetupTimings(); + + gEngfuncs.GetPredictedOrigin( simorg ); + for( i = 0; i < 4; i++ ) + VectorCopy( simorg, RI.currententity->attachment[i] ); + RI.currentmodel = RI.currententity->model; + + R_StudioDrawModelInternal( RI.currententity, STUDIO_EVENTS ); +} + +/* +================= +R_GatherPlayerLight +================= +*/ +void R_GatherPlayerLight( void ) +{ + cl_entity_t *view = gEngfuncs.GetViewModel(); + colorVec c; + + tr.ignore_lightgamma = true; + c = R_LightPoint( view->origin ); + tr.ignore_lightgamma = false; + gEngfuncs.SetLocalLightLevel( ( c.r + c.g + c.b ) / 3 ); +} + +/* +================= +R_DrawViewModel +================= +*/ +void R_DrawViewModel( void ) +{ + qboolean m_flip; + + cl_entity_t *view = gEngfuncs.GetViewModel(); + + R_GatherPlayerLight(); + + if( r_drawviewmodel->value == 0 ) + return; + + if( ENGINE_GET_PARM( PARM_THIRDPERSON )) + return; + + // ignore in thirdperson, camera view or client is died + if( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer()) + return; + + tr.blend = CL_FxBlend( view ) / 255.0f; + if( !R_ModelOpaque( view->curstate.rendermode ) && tr.blend <= 0.0f ) + return; // invisible ? + + RI.currententity = view; + + if( !RI.currententity->model ) + return; + + // adjust the depth range to prevent view model from poking into walls + sceGuDepthRange( ( int )gldepthmin, ( int )( gldepthmin + 0.3f * ( gldepthmax - gldepthmin ) ) ); + + RI.currentmodel = RI.currententity->model; + + m_flip = R_AllowFlipViewModel( RI.currententity ); + + // backface culling for left-handed weapons + if( m_flip || g_iBackFaceCull ) + { + tr.fFlipViewModel = true; + GL_Cull( GL_FRONT ); + } + + switch( RI.currententity->model->type ) + { + case mod_alias: + R_DrawAliasModel( RI.currententity ); + break; + case mod_studio: + R_StudioSetupTimings(); + R_StudioDrawModelInternal( RI.currententity, STUDIO_RENDER ); + break; + } + + // restore depth range + sceGuDepthRange( ( int )gldepthmin, ( int )gldepthmax ); + + // backface culling for left-handed weapons + if( m_flip || g_iBackFaceCull ) + { + tr.fFlipViewModel = false; + GL_Cull( GL_BACK ); + } +} + +/* +==================== +R_StudioLoadTexture + +load model texture with unique name +==================== +*/ +static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture_t *ptexture ) +{ + size_t size; + int flags = 0; + char texname[128], name[128], mdlname[128]; + texture_t *tx = NULL; + + if( ptexture->flags & STUDIO_NF_NORMALMAP ) + flags |= (TF_NORMALMAP); + + // store some textures for remapping + if( !Q_strnicmp( ptexture->name, "DM_Base", 7 ) || !Q_strnicmp( ptexture->name, "remap", 5 )) + { + int i, size; + char val[6]; + byte *pixels; + + i = mod->numtextures; + mod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* )); + size = ptexture->width * ptexture->height + 768; + tx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size ); + mod->textures[i] = tx; + + // store ranges into anim_min, anim_max etc + if( !Q_strnicmp( ptexture->name, "DM_Base", 7 )) + { + Q_strncpy( tx->name, "DM_Base", sizeof( tx->name )); + tx->anim_min = PLATE_HUE_START; // topcolor start + tx->anim_max = PLATE_HUE_END; // topcolor end + // bottomcolor start always equal is (topcolor end + 1) + tx->anim_total = SUIT_HUE_END;// bottomcolor end + } + else + { + Q_strncpy( tx->name, "DM_User", sizeof( tx->name )); // custom remapped + Q_strncpy( val, ptexture->name + 7, 4 ); + tx->anim_min = bound( 0, Q_atoi( val ), 255 ); // topcolor start + Q_strncpy( val, ptexture->name + 11, 4 ); + tx->anim_max = bound( 0, Q_atoi( val ), 255 ); // topcolor end + // bottomcolor start always equal is (topcolor end + 1) + Q_strncpy( val, ptexture->name + 15, 4 ); + tx->anim_total = bound( 0, Q_atoi( val ), 255 ); // bottomcolor end + } + + tx->width = ptexture->width; + tx->height = ptexture->height; + + // the pixels immediately follow the structures + pixels = (byte *)phdr + ptexture->index; + memcpy( tx+1, pixels, size ); + + ptexture->flags |= STUDIO_NF_COLORMAP; // yes, this is colormap image + flags |= TF_FORCE_COLOR; + + mod->numtextures++; // done + } + + Q_strncpy( mdlname, mod->name, sizeof( mdlname )); + COM_FileBase( ptexture->name, name ); + COM_StripExtension( mdlname ); + + if( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS )) + SetBits( flags, TF_NOMIPMAP ); + + // NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it + //ptexture->index = (int)((byte *)phdr) + ptexture->index; + gEngfuncs.Image_SetMDLPointer((byte *)phdr + ptexture->index); + size = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768; + + if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_IMPROVED_LINETRACE ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED )) + flags |= TF_KEEP_SOURCE; // Paranoia2 texture alpha-tracing + + // build the texname + Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name ); + ptexture->index = GL_LoadTexture( texname, (byte *)ptexture, size, flags ); + + if( !ptexture->index ) + { + ptexture->index = tr.defaultTexture; + } + else if( tx ) + { + // duplicate texnum for easy acess + tx->gl_texturenum = ptexture->index; + } +} + +/* +================= +Mod_StudioLoadTextures +================= +*/ +void Mod_StudioLoadTextures( model_t *mod, void *data ) +{ + studiohdr_t *phdr = (studiohdr_t *)data; + mstudiotexture_t *ptexture; + int i; + + if( !phdr ) + return; + + ptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); + if( phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS ) + { + for( i = 0; i < phdr->numtextures; i++ ) + R_StudioLoadTexture( mod, phdr, &ptexture[i] ); + } +} + +/* +================= +Mod_StudioUnloadTextures +================= +*/ +void Mod_StudioUnloadTextures( void *data ) +{ + studiohdr_t *phdr = (studiohdr_t *)data; + mstudiotexture_t *ptexture; + int i; + + if( !phdr ) + return; + + ptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); + + // release all textures + for( i = 0; i < phdr->numtextures; i++ ) + { + if( ptexture[i].index == tr.defaultTexture ) + continue; + GL_FreeTexture( ptexture[i].index ); + } +} + +static model_t *pfnModelHandle( int modelindex ) +{ + return gEngfuncs.pfnGetModelByIndex( modelindex ); +} + +static void *pfnMod_CacheCheck( struct cache_user_s *c ) +{ + return gEngfuncs.Mod_CacheCheck( c ); +} + +static void *pfnMod_StudioExtradata( model_t *mod ) +{ + return STUDIOEXTRADATA( mod ); +} + +static void pfnMod_LoadCacheFile( const char *path, struct cache_user_s *cu ) +{ + gEngfuncs.Mod_LoadCacheFile( path, cu ); +} + +static cvar_t *pfnGetCvarPointer( const char *name ) +{ + return (cvar_t*)gEngfuncs.pfnGetCvarPointer( name, 0 ); +} + +static void *pfnMod_Calloc( int number, size_t size ) +{ + return gEngfuncs.Mod_Calloc( number, size ); +} + +static engine_studio_api_t gStudioAPI = +{ + pfnMod_Calloc, + pfnMod_CacheCheck, + pfnMod_LoadCacheFile, + pfnMod_ForName, + pfnMod_StudioExtradata, + pfnModelHandle, + pfnGetCurrentEntity, + pfnPlayerInfo, + R_StudioGetPlayerState, + pfnGetViewEntity, + pfnGetEngineTimes, + pfnGetCvarPointer, + pfnGetViewInfo, + R_GetChromeSprite, + pfnGetModelCounters, + pfnGetAliasScale, + pfnStudioGetBoneTransform, + pfnStudioGetLightTransform, + pfnStudioGetAliasTransform, + pfnStudioGetRotationMatrix, + R_StudioSetupModel, + R_StudioCheckBBox, + R_StudioDynamicLight, + R_StudioEntityLight, + R_StudioSetupLighting, + R_StudioDrawPoints, + R_StudioDrawHulls, + R_StudioDrawAbsBBox, + R_StudioDrawBones, + (void*)R_StudioSetupSkin, + R_StudioSetRemapColors, + R_StudioSetupPlayerModel, + R_StudioClientEvents, + R_StudioGetForceFaceFlags, + R_StudioSetForceFaceFlags, + (void*)R_StudioSetHeader, + R_StudioSetRenderModel, + R_StudioSetupRenderer, + R_StudioRestoreRenderer, + R_StudioSetChromeOrigin, + pfnIsHardware, + GL_StudioDrawShadow, + GL_StudioSetRenderMode, + R_StudioSetRenderamt, + R_StudioSetCullState, + R_StudioRenderShadow, +}; + +static r_studio_interface_t gStudioDraw = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +=============== +CL_InitStudioAPI + +Initialize client studio +=============== +*/ +void CL_InitStudioAPI( void ) +{ + pStudioDraw = &gStudioDraw; + + // trying to grab them from client.dll + cl_righthand = gEngfuncs.pfnGetCvarPointer( "cl_righthand", 0 ); + + if( cl_righthand == NULL ) + cl_righthand = gEngfuncs.Cvar_Get( "cl_righthand", "0", FCVAR_ARCHIVE, "flip viewmodel (left to right)" ); + + // Xash will be used internal StudioModelRenderer + if( gEngfuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI )) + return; + + // NOTE: we always return true even if game interface was not correct + // because we need Draw our StudioModels + // just restore pointer to builtin function + pStudioDraw = &gStudioDraw; +} diff --git a/ref_gu/gu_triapi.c b/ref_gu/gu_triapi.c new file mode 100644 index 000000000..64efe9353 --- /dev/null +++ b/ref_gu/gu_triapi.c @@ -0,0 +1,578 @@ +/* +gu_triapi.c - TriAPI draw methods +Copyright (C) 2011 Uncle Mike +Copyright (C) 2019 a1batross +Copyright (C) 2021 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" +#include "const.h" + +static struct +{ + int renderMode; // override kRenderMode from TriAPI + vec4_t triRGBA; +} ds; + +static struct +{ + byte *start; + byte *end; + int count; + int prim; + uint flags; + float lastTexCord[2]; + uint lastColor; +}triContext = { 0 }; + +/* +=============================================================== + + TRIAPI IMPLEMENTATION + +=============================================================== +*/ + +/* +============= +TriRenderMode + +set rendermode +============= +*/ +void TriRenderMode( int mode ) +{ + ds.renderMode = mode; + switch( mode ) + { + case kRenderNormal: + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuDisable( GU_BLEND ); + sceGuDepthMask( GU_FALSE ); + break; + case kRenderTransAlpha: + sceGuEnable( GU_BLEND ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuDepthMask( GU_TRUE ); + break; + case kRenderTransColor: + case kRenderTransTexture: + sceGuEnable( GU_BLEND ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + break; + case kRenderGlow: + case kRenderTransAdd: + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, GUBLEND1 ); + sceGuEnable( GU_BLEND ); + sceGuDepthMask( GU_TRUE ); + break; + } +} + +/* +============= +TriBegin + +begin triangle sequence +============= +*/ +void TriBegin( int mode ) +{ + switch( mode ) + { + case TRI_POINTS: + triContext.prim = GU_POINTS; + break; + case TRI_TRIANGLES: + triContext.prim = GU_TRIANGLES; + break; + case TRI_TRIANGLE_FAN: + triContext.prim = GU_TRIANGLE_FAN; + break; + case TRI_QUADS: + triContext.prim = GU_TRIANGLE_FAN; + break; + case TRI_LINES: + triContext.prim = GU_LINES; + break; + case TRI_TRIANGLE_STRIP: + triContext.prim = GU_TRIANGLE_STRIP; + break; + case TRI_QUAD_STRIP: + triContext.prim = GU_TRIANGLE_STRIP; + break; + case TRI_POLYGON: + default: + triContext.prim = GU_TRIANGLE_FAN; + break; + } + + triContext.start = extGuBeginPacket( NULL ); + triContext.end = triContext.start; + triContext.count = 0; + triContext.flags = 0; +} + +/* +============= +TriEnd + +draw triangle sequence +============= +*/ +void TriEnd( void ) +{ + if( !triContext.start || triContext.count == 0 ) + return; + + extGuEndPacket(( void* )triContext.end ); + sceGuDrawArray( triContext.prim, triContext.flags, triContext.count, 0, triContext.start ); + + triContext.start = NULL; + triContext.end = NULL; + triContext.count = 0; +} + +/* +============= +_TriColor4f + +============= +*/ +static void _TriColor4f( float r, float g, float b, float a ) +{ + if( triContext.start ) + { + if( triContext.count > 0 && !( triContext.flags & GU_COLOR_8888 )) + return; + + triContext.flags |= GU_COLOR_8888; + triContext.lastColor = GUCOLOR4F( r, g, b, a ); + } + else sceGuColor( GUCOLOR4F( r, g, b, a )); +} + +/* +============= +_TriColor4ub + +============= +*/ +static void _TriColor4ub( byte r, byte g, byte b, byte a ) +{ + if( triContext.start ) + { + if( triContext.count > 0 && !( triContext.flags & GU_COLOR_8888 )) + return; + + triContext.flags |= GU_COLOR_8888; + triContext.lastColor = GUCOLOR4UB( r, g, b, a ); + } + else sceGuColor( GUCOLOR4UB( r, g, b, a )); +} + + +/* +============= +TriColor4ub + +============= +*/ +void TriColor4ub( byte r, byte g, byte b, byte a ) +{ + ds.triRGBA[0] = r * (1.0f / 255.0f); + ds.triRGBA[1] = g * (1.0f / 255.0f); + ds.triRGBA[2] = b * (1.0f / 255.0f); + ds.triRGBA[3] = a * (1.0f / 255.0f); + + _TriColor4ub( r, g, b, 255 ); +} + +/* +================= +TriColor4f +================= +*/ +void TriColor4f( float r, float g, float b, float a ) +{ + if( ds.renderMode == kRenderTransAlpha ) + _TriColor4ub( r * 255.9f, g * 255.9f, b * 255.9f, a * 255.0f ); + else _TriColor4f( r * a, g * a, b * a, 1.0 ); + + ds.triRGBA[0] = r; + ds.triRGBA[1] = g; + ds.triRGBA[2] = b; + ds.triRGBA[3] = a; +} + +/* +============= +TriTexCoord2f + +============= +*/ +void TriTexCoord2f( float u, float v ) +{ + if( !triContext.start ) + return; + + if( triContext.count > 0 && !( triContext.flags & GU_TEXTURE_32BITF )) + return; + + triContext.flags |= GU_TEXTURE_32BITF; + triContext.lastTexCord[0] = u; + triContext.lastTexCord[1] = v; +} + +/* +============= +TriVertex3fv + +============= +*/ +void TriVertex3fv( const float *v ) +{ + if( !triContext.start ) + return; + + if( triContext.flags & GU_TEXTURE_32BITF ) + { + *( float* )triContext.end = triContext.lastTexCord[0]; + triContext.end += sizeof( float ); + *( float* )triContext.end = triContext.lastTexCord[1]; + triContext.end += sizeof( float ); + } + + if( triContext.flags & GU_COLOR_8888 ) + { + *( uint* )triContext.end = triContext.lastColor; + triContext.end += sizeof( uint ); + } + + triContext.flags |= GU_VERTEX_32BITF; + *( float* )triContext.end = v[0]; + triContext.end += sizeof( float ); + *( float* )triContext.end = v[1]; + triContext.end += sizeof( float ); + *( float* )triContext.end = v[2]; + triContext.end += sizeof( float ); + + triContext.count++; +} + +/* +============= +TriVertex3f + +============= +*/ +void TriVertex3f( float x, float y, float z ) +{ + if( !triContext.start ) + return; + + if( triContext.flags & GU_TEXTURE_32BITF ) + { + *( float* )triContext.end = triContext.lastTexCord[0]; + triContext.end += sizeof( float ); + *( float* )triContext.end = triContext.lastTexCord[1]; + triContext.end += sizeof( float ); + } + + if( triContext.flags & GU_COLOR_8888 ) + { + *( uint* )triContext.end = triContext.lastColor; + triContext.end += sizeof( uint ); + } + + triContext.flags |= GU_VERTEX_32BITF; + *( float* )triContext.end = x; + triContext.end += sizeof( float ); + *( float* )triContext.end = y; + triContext.end += sizeof( float ); + *( float* )triContext.end = z; + triContext.end += sizeof( float ); + + triContext.count++; +} + +/* +============= +TriWorldToScreen + +convert world coordinates (x,y,z) into screen (x, y) +============= +*/ +int TriWorldToScreen( const float *world, float *screen ) +{ + int retval; + + retval = R_WorldToScreen( world, screen ); + + screen[0] = 0.5f * screen[0] * (float)RI.viewport[2]; + screen[1] = -0.5f * screen[1] * (float)RI.viewport[3]; + screen[0] += 0.5f * (float)RI.viewport[2]; + screen[1] += 0.5f * (float)RI.viewport[3]; + + return retval; +} + +/* +============= +TriSpriteTexture + +bind current texture +============= +*/ +int TriSpriteTexture( model_t *pSpriteModel, int frame ) +{ + int gl_texturenum; + + if(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) == 0 ) + return 0; + + if( gl_texturenum <= 0 || gl_texturenum > MAX_TEXTURES ) + gl_texturenum = tr.defaultTexture; + + GL_Bind( XASH_TEXTURE0, gl_texturenum ); + + return 1; +} + +/* +============= +TriFog + +enables global fog on the level +============= +*/ +void TriFog( float flFogColor[3], float flStart, float flEnd, int bOn ) +{ + // overrided by internal fog + if( RI.fogEnabled ) return; + RI.fogCustom = bOn; + + // check for invalid parms + if( flEnd <= flStart ) + { + glState.isFogEnabled = RI.fogCustom = false; +#if 1 + sceGuDisable( GU_FOG ); +#else + pglDisable( GL_FOG ); +#endif + return; + } +#if 1 + if( RI.fogCustom ) + sceGuEnable( GU_FOG ); + else sceGuDisable( GU_FOG ); +#else + if( RI.fogCustom ) + pglEnable( GL_FOG ); + else pglDisable( GL_FOG ); +#endif + // copy fog params + RI.fogColor[0] = flFogColor[0] / 255.0f; + RI.fogColor[1] = flFogColor[1] / 255.0f; + RI.fogColor[2] = flFogColor[2] / 255.0f; + RI.fogStart = flStart; + RI.fogColor[3] = 1.0f; + RI.fogDensity = 0.0f; + RI.fogSkybox = true; + RI.fogEnd = flEnd; + +#if 1 + printf("TRI FOG: s %f e %f c %f %f %f %f \n", RI.fogStart, RI.fogEnd, + RI.fogColor[0], RI.fogColor[1], RI.fogColor[2], RI.fogColor[3] ); + + //glState.fogDensity = RI.fogDensity + glState.fogColor = GUCOLOR4F( RI.fogColor[0], RI.fogColor[1], RI.fogColor[2], glState.fogDensity ); + glState.fogStart = RI.fogStart; + glState.fogEnd = RI.fogEnd; + + sceGuFog( glState.fogStart, glState.fogEnd, glState.fogColor ); +#else + pglFogi( GL_FOG_MODE, GL_LINEAR ); + pglFogfv( GL_FOG_COLOR, RI.fogColor ); + pglFogf( GL_FOG_START, RI.fogStart ); + pglFogf( GL_FOG_END, RI.fogEnd ); + pglHint( GL_FOG_HINT, GL_NICEST ); +#endif +} + +/* +============= +TriGetMatrix + +very strange export +============= +*/ +void TriGetMatrix( const int pname, float *matrix ) +{ +#if 1 + printf("%s:%i:%s - Not implemented\n", __FILE__, __LINE__, __FUNCTION__ ); +#else + pglGetFloatv( pname, matrix ); +#endif +} + +/* +============= +TriForParams + +============= +*/ +void TriFogParams( float flDensity, int iFogSkybox ) +{ + RI.fogDensity = flDensity; + RI.fogSkybox = iFogSkybox; +} + +/* +============= +TriCullFace + +============= +*/ +void TriCullFace( TRICULLSTYLE mode ) +{ + int glMode; + + switch( mode ) + { + case TRI_FRONT: + glMode = GL_FRONT; + break; + default: + glMode = GL_NONE; + break; + } + + GL_Cull( glMode ); +} + +/* +============= +TriBrightness +============= +*/ +void TriBrightness( float brightness ) +{ + float r, g, b; + + r = ds.triRGBA[0] * ds.triRGBA[3] * brightness; + g = ds.triRGBA[1] * ds.triRGBA[3] * brightness; + b = ds.triRGBA[2] * ds.triRGBA[3] * brightness; + + _TriColor4f( r, g, b, 1.0f ); +} + +unsigned int getTriBrightness( float brightness ) +{ + vec3_t color; + + if( brightness < 0.0f ) return 0; + + color[0] = ds.triRGBA[0] * ds.triRGBA[3] * brightness; + color[1] = ds.triRGBA[1] * ds.triRGBA[3] * brightness; + color[2] = ds.triRGBA[2] * ds.triRGBA[3] * brightness; + + //VectorNormalizeFast( color ); + + return GUCOLOR3FV( color ); +} + +/* +============= +TriBoxInPVS + +check box in pvs (absmin, absmax) +============= +*/ +int TriBoxInPVS( float *mins, float *maxs ) +{ + return gEngfuncs.Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )); +} + +/* +============= +TriLightAtPoint +NOTE: dlights are ignored +============= +*/ +void TriLightAtPoint( float *pos, float *value ) +{ + colorVec vLightColor; + + if( !pos || !value ) return; + + vLightColor = R_LightPoint( pos ); + + value[0] = vLightColor.r; + value[1] = vLightColor.g; + value[2] = vLightColor.b; +} + +/* +============= +TriColor4fRendermode +Heavy legacy of Quake... +============= +*/ +void TriColor4fRendermode( float r, float g, float b, float a, int rendermode ) +{ + if( ds.renderMode == kRenderTransAlpha ) + { + ds.triRGBA[3] = a / 255.0f; + _TriColor4f( r, g, b, a ); + } + else _TriColor4f( r * a, g * a, b * a, 1.0f ); +} + +/* +============= +getTriAPI +export +============= +*/ +int getTriAPI( int version, triangleapi_t *api ) +{ + api->version = TRI_API_VERSION; + + if( version != TRI_API_VERSION ) + return 0; + + api->RenderMode = TriRenderMode; + api->Begin = TriBegin; + api->End = TriEnd; + api->Color4f = TriColor4f; + api->Color4ub = TriColor4ub; + api->TexCoord2f = TriTexCoord2f; + api->Vertex3fv = TriVertex3fv; + api->Vertex3f = TriVertex3f; + api->Brightness = TriBrightness; + api->CullFace = TriCullFace; + api->SpriteTexture = TriSpriteTexture; + api->WorldToScreen = R_WorldToScreen; + api->Fog = TriFog; + api->ScreenToWorld = R_ScreenToWorld; + api->GetMatrix = TriGetMatrix; + api->BoxInPVS = TriBoxInPVS; + api->LightAtPoint = TriLightAtPoint; + api->Color4fRendermode = TriColor4fRendermode; + api->FogParams = TriFogParams; + + return 1; +} diff --git a/ref_gu/gu_vgui.c b/ref_gu/gu_vgui.c new file mode 100644 index 000000000..e9f7a3542 --- /dev/null +++ b/ref_gu/gu_vgui.c @@ -0,0 +1,309 @@ +/* +gl_vgui.c - OpenGL vgui draw methods +Copyright (C) 2011 Uncle Mike +Copyright (C) 2019 a1batross +Copyright (C) 2020 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "gu_local.h" + +#define VGUI_MAX_TEXTURES ( MAX_TEXTURES / 2 ) // a half of total textures count +#if 1 +static short g_textures[VGUI_MAX_TEXTURES]; +#else +static int g_textures[VGUI_MAX_TEXTURES]; +#endif +static int g_textureId = 0; +static int g_iBoundTexture; + +/* +================ +VGUI_DrawInit + +Startup VGUI backend +================ +*/ +void GAME_EXPORT VGUI_DrawInit( void ) +{ + memset( g_textures, 0, sizeof( g_textures )); + g_textureId = g_iBoundTexture = 0; +} + +/* +================ +VGUI_DrawShutdown + +Release all textures +================ +*/ +void GAME_EXPORT VGUI_DrawShutdown( void ) +{ + int i; + + for( i = 1; i < g_textureId; i++ ) + { + GL_FreeTexture( g_textures[i] ); + } +} + +/* +================ +VGUI_GenerateTexture + +generate unique texture number +================ +*/ +int GAME_EXPORT VGUI_GenerateTexture( void ) +{ + if( ++g_textureId >= VGUI_MAX_TEXTURES ) + gEngfuncs.Host_Error( "VGUI_GenerateTexture: VGUI_MAX_TEXTURES limit exceeded\n" ); + return g_textureId; +} + +/* +================ +VGUI_UploadTexture + +Upload texture into video memory +================ +*/ +void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int width, int height ) +{ + rgbdata_t r_image; + char texName[32]; + + if( id <= 0 || id >= VGUI_MAX_TEXTURES ) + { + gEngfuncs.Con_DPrintf( S_ERROR "VGUI_UploadTexture: bad texture %i. Ignored\n", id ); + return; + } + + Q_snprintf( texName, sizeof( texName ), "*vgui%i", id ); + memset( &r_image, 0, sizeof( r_image )); + + r_image.width = width; + r_image.height = height; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; + r_image.buffer = (byte *)buffer; + + g_textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE ); +} + +/* +================ +VGUI_CreateTexture + +Create empty rgba texture and upload them into video memory +================ +*/ +void GAME_EXPORT VGUI_CreateTexture( int id, int width, int height ) +{ + rgbdata_t r_image; + char texName[32]; + + if( id <= 0 || id >= VGUI_MAX_TEXTURES ) + { + gEngfuncs.Con_Reportf( S_ERROR "VGUI_CreateTexture: bad texture %i. Ignored\n", id ); + return; + } + + Q_snprintf( texName, sizeof( texName ), "*vgui%i", id ); + memset( &r_image, 0, sizeof( r_image )); + + r_image.width = width; + r_image.height = height; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + r_image.flags = IMAGE_HAS_ALPHA; + r_image.buffer = NULL; + + g_textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE|TF_NEAREST ); + g_iBoundTexture = id; +} + +void GAME_EXPORT VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ) +{ + if( id <= 0 || id >= VGUI_MAX_TEXTURES || g_textures[id] == 0 || g_textures[id] == tr.whiteTexture ) + { + gEngfuncs.Con_Reportf( S_ERROR "VGUI_UploadTextureBlock: bad texture %i. Ignored\n", id ); + return; + } +#if 1 + +#else + pglTexSubImage2D( GL_TEXTURE_2D, 0, drawX, drawY, blockWidth, blockHeight, GL_RGBA, GL_UNSIGNED_BYTE, rgba ); +#endif + g_iBoundTexture = id; +} + +void GAME_EXPORT VGUI_SetupDrawingRect( int *pColor ) +{ +#if 1 + sceGuEnable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuColor( GUCOLOR4UB( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ) ); +#else + pglEnable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglColor4ub( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); +#endif +} + +void GAME_EXPORT VGUI_SetupDrawingText( int *pColor ) +{ +#if 1 + sceGuEnable( GU_BLEND ); + sceGuEnable( GU_ALPHA_TEST ); + sceGuAlphaFunc( GU_GREATER, 0x00, 0xff ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuColor( GUCOLOR4UB( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ) ); +#else + pglEnable( GL_BLEND ); + pglEnable( GL_ALPHA_TEST ); + pglAlphaFunc( GL_GREATER, 0.0f ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglColor4ub( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); +#endif +} + +void GAME_EXPORT VGUI_SetupDrawingImage( int *pColor ) +{ +#if 1 + sceGuEnable( GU_BLEND ); + sceGuEnable( GU_ALPHA_TEST ); + sceGuAlphaFunc( GU_GREATER, 0x00, 0xff ); + sceGuBlendFunc( GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0 ); + sceGuTexFunc( GU_TFX_MODULATE, GU_TCC_RGBA ); + sceGuColor( GUCOLOR4UB( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ) ); +#else + pglEnable( GL_BLEND ); + pglEnable( GL_ALPHA_TEST ); + pglAlphaFunc( GL_GREATER, 0.0f ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglColor4ub( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); +#endif +} + +void GAME_EXPORT VGUI_BindTexture( int id ) +{ + if( id > 0 && id < VGUI_MAX_TEXTURES && g_textures[id] ) + { + GL_Bind( XASH_TEXTURE0, g_textures[id] ); + g_iBoundTexture = id; + } + else + { + // NOTE: same as bogus index 2700 in GoldSrc + id = g_iBoundTexture = 1; + GL_Bind( XASH_TEXTURE0, g_textures[id] ); + } +} + +/* +================ +VGUI_GetTextureSizes + +returns wide and tall for currently binded texture +================ +*/ +void GAME_EXPORT VGUI_GetTextureSizes( int *width, int *height ) +{ + gl_texture_t *glt; + int texnum; + + if( g_iBoundTexture ) + texnum = g_textures[g_iBoundTexture]; + else texnum = tr.defaultTexture; + + glt = R_GetTexture( texnum ); + if( width ) *width = glt->srcWidth; + if( height ) *height = glt->srcHeight; +} + +/* +================ +VGUI_EnableTexture + +disable texturemode for fill rectangle +================ +*/ +void GAME_EXPORT VGUI_EnableTexture( qboolean enable ) +{ +#if 1 + if( enable ) sceGuEnable( GU_TEXTURE_2D ); + else sceGuDisable( GU_TEXTURE_2D ); +#else + if( enable ) pglEnable( GL_TEXTURE_2D ); + else pglDisable( GL_TEXTURE_2D ); +#endif +} + +/* +================ +VGUI_DrawQuad + +generic method to fill rectangle +================ +*/ +void GAME_EXPORT VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ) +{ + int width, height; + float xscale, yscale; +#if 1 + gl_texture_t *glt; + float twidth, theight; +#endif + gEngfuncs.CL_GetScreenInfo( &width, &height ); + + xscale = gpGlobals->width / (float)width; + yscale = gpGlobals->height / (float)height; + + ASSERT( ul != NULL && lr != NULL ); +#if 1 + glt = R_GetTexture( g_iBoundTexture ? g_textures[g_iBoundTexture] : tr.defaultTexture ); + + gu_vert_htv_t* const out = ( gu_vert_htv_t* )sceGuGetMemory( sizeof( gu_vert_htv_t ) * 2 ); + out[0].u = ul->coord[0] * glt->width; + out[0].v = ul->coord[1] * glt->height; + out[0].x = ul->point[0] * xscale; + out[0].y = ul->point[1] * yscale; + out[0].z = 0; + out[1].u = lr->coord[0] * glt->width; + out[1].v = lr->coord[1] * glt->height; + out[1].x = lr->point[0] * xscale; + out[1].y = lr->point[1] * yscale; + out[1].z = 0; + sceGuDrawArray( GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, out ); +#else + pglBegin( GL_QUADS ); + pglTexCoord2f( ul->coord[0], ul->coord[1] ); + pglVertex2f( ul->point[0] * xscale, ul->point[1] * yscale ); + + pglTexCoord2f( lr->coord[0], ul->coord[1] ); + pglVertex2f( lr->point[0] * xscale, ul->point[1] * yscale ); + + pglTexCoord2f( lr->coord[0], lr->coord[1] ); + pglVertex2f( lr->point[0] * xscale, lr->point[1] * yscale ); + + pglTexCoord2f( ul->coord[0], lr->coord[1] ); + pglVertex2f( ul->point[0] * xscale, lr->point[1] * yscale ); + pglEnd(); +#endif +} diff --git a/ref_gu/gu_vram.c b/ref_gu/gu_vram.c new file mode 100644 index 000000000..2f54de5d2 --- /dev/null +++ b/ref_gu/gu_vram.c @@ -0,0 +1,299 @@ +/* + * Helper for use with the PSP Software Development Kit - http://www.pspdev.org + * ----------------------------------------------------------------------- + * Licensed as 'free to use and modify as long as credited appropriately' + * + * vram.c - Standard C high performance VRAM allocation routines. + * + * Copyright (c) 2007 Alexander Berl 'Raphael' + * http://wordpress.fx-world.org + */ +#include "gu_vram.h" +#include +#include +#include + +#define _MBEINVRAM +//#define _DEBUG + +// Configure the memory to be managed +static unsigned int __mem_size = 0; +static unsigned int __mem_start = 0; +static int __mem_num_blocks = 0; + +static unsigned int *__mem_blocks; + +static int __largest_update = 0; +static int __largest_block = 0; +static int __mem_free = 0; + +// Configure the block size the memory gets subdivided into (page size) +// __mem_size/__BLOCK_SIZE may not exceed 2^16-1 = 65535 +// The block size also defines the alignment of allocations +// Larger block sizes perform better, because the blocktable is smaller and therefore fits better into cache +// however the overhead is also bigger and more memory is wasted +#define __BLOCK_SIZE 512 +#define __BLOCKS(x) (( x + __BLOCK_SIZE - 1 ) / __BLOCK_SIZE ) +#define __BLOCKSIZE(x) (( x + __BLOCK_SIZE - 1 ) & ~( __BLOCK_SIZE - 1 )) + + +// A MEMORY BLOCK ENTRY IS MADE UP LIKE THAT: +// bit: 31 30 29 - 15 14-0 +// free block prev size +// +// bit 31: free bit, indicating if block is allocated or not +// bit 30: blocked bit, indicating if block is part of a larger block (0) - used for error resilience +// bit 29-15: block index of previous block +// bit 14- 0: size of current block +// +// This management can handle a max amount of 2^16-1 = 65535 blocks, which resolves to 64MB at blocksize of 1024 bytes +// +#define __BLOCK_GET_SIZE( x ) (( x & 0x7FFF )) +#define __BLOCK_GET_PREV( x ) (( x >> 15 ) & 0x7FFF ) +#define __BLOCK_GET_FREE( x ) (( x >> 31 )) +#define __BLOCK_GET_BLOCK( x ) (( x >> 30) & 0x1 ) +#define __BLOCK_SET_SIZE( x, y ) x = (( x & ~0x7FFF ) | (( y ) & 0x7FFF )) +#define __BLOCK_ADD_SIZE( x, y ) x = (( x & ~0x7FFF ) | ((( x & 0x7FFF ) + (( y ) & 0x7FFF )) & 0x7FFF )) +#define __BLOCK_SET_PREV( x, y ) x = (( x & ~0x3FFF8000 ) | ((( y ) & 0x7FFF ) << 15 )) +#define __BLOCK_SET_FREE( x, y ) x = (( x & 0x7FFFFFFF ) | ((( y ) & 0x1 ) << 31 )) +#define __BLOCK_SET_BLOCK( x, y ) x = (( x & 0xBFFFFFFF ) | ((( y ) & 0x1 ) << 30 )) +#define __BLOCK_MAKE( s, p, f, n ) ((( f & 0x1 ) << 31 ) | (( n & 0x1 ) << 30 ) | ((( p ) & 0x7FFF ) << 15 ) | (( s ) & 0x7FFF )) +#define __BLOCK_GET_FREEBLOCK( x ) (( x >> 30 ) & 0x3 ) // returns 11b if block is a starting block and free, 10b if block is a starting block and allocated, 0xb if it is a non-starting block (don't change) +#define __BLOCK0 (( __mem_num_blocks ) | ( 1 << 31 ) | ( 1 << 30 )) + +int vinit( void ) +{ + if( __mem_start ) return 0; + + __mem_size = sceGeEdramGetSize(); + __mem_start = ( unsigned int )sceGeEdramGetAddr(); +#ifdef _MBEINVRAM + __mem_num_blocks = __mem_size / ( __BLOCK_SIZE + sizeof( unsigned int )); + __mem_size = __mem_num_blocks * __BLOCK_SIZE; + __mem_blocks = ( unsigned int* )( __mem_start + __mem_size ); // to end +#else + __mem_num_blocks = __mem_size / __BLOCK_SIZE; + __mem_blocks = ( unsigned int* )calloc( __mem_num_blocks, sizeof( unsigned int )); + if( !__mem_blocks ) + { + __mem_size = 0; + __mem_start = 0; + return -1; + } +#endif + __largest_block = __mem_num_blocks; + __mem_free = __mem_num_blocks; + + return 0; +} + + +inline void* vrelptr( void *ptr ) +{ + if( !__mem_start ) return NULL; + return ( void* )(( unsigned int )ptr & ~__mem_start ); +} + +inline void* vabsptr( void *ptr ) +{ + if( !__mem_start ) return NULL; + return ( void* )(( unsigned int )ptr | __mem_start ); +} + + +static void __find_largest_block( void ) +{ + int i = 0; + __largest_block = 0; + while( i < __mem_num_blocks ) + { + int csize = __BLOCK_GET_SIZE( __mem_blocks[i] ); + if( __BLOCK_GET_FREEBLOCK( __mem_blocks[i] ) == 3 && csize > __largest_block ) + __largest_block = csize; + i += csize; + } + __largest_update = 0; +} + +#ifdef _DEBUG +void __memwalk( void ) +{ + int i = 0; + if( !__mem_start ) return; + if( __mem_blocks[0] == 0 ) __mem_blocks[0] = __BLOCK0; + while( i < __mem_num_blocks ) + { + printf( "BLOCK %i:\n", i ); + printf( " free: %i\n", __BLOCK_GET_FREEBLOCK( __mem_blocks[i] )); + printf( " size: %i\n", __BLOCK_GET_SIZE( __mem_blocks[i] )); + printf( " prev: %i\n", __BLOCK_GET_PREV( __mem_blocks[i] )); + i += __BLOCK_GET_SIZE( __mem_blocks[i] ); + } +} +#endif + +void* valloc( size_t size ) +{ + if( !__mem_start ) return NULL; + + // Initialize memory block, if not yet done + if( __mem_blocks[0] == 0 ) __mem_blocks[0] = __BLOCK0; + + int i = 0; + int j = 0; + int bsize = __BLOCKS( size ); + + if( __largest_update == 0 && __largest_block < bsize ) + { +#ifdef _DEBUG + printf( "Not enough memory to allocate %i bytes (largest: %i)!\n", size, vlargestblock()); +#endif + return( 0 ); + } + +#ifdef _DEBUG + printf( "allocating %i bytes, in %i blocks\n", size, bsize ); +#endif + // Find smallest block that still fits the requested size + int bestblock = -1; + int bestblock_prev = 0; + int bestblock_size = __mem_num_blocks + 1; + while( i < __mem_num_blocks ) + { + int csize = __BLOCK_GET_SIZE( __mem_blocks[i] ); + if( __BLOCK_GET_FREEBLOCK( __mem_blocks[i] ) == 3 && csize >= bsize ) + { + if( csize < bestblock_size ) + { + bestblock = i; + bestblock_prev = j; + bestblock_size = csize; + } + + if( csize == bsize ) + break; + } + j = i; + i += csize; + } + + if( bestblock < 0 ) + { +#ifdef _DEBUG + printf( "Not enough memory to allocate %i bytes (largest: %i)!\n", size, vlargestblock()); +#endif + return( 0 ); + } + + i = bestblock; + j = bestblock_prev; + int csize = bestblock_size; + __mem_blocks[i] = __BLOCK_MAKE( bsize, j, 0, 1 ); + + int next = i + bsize; + if( csize > bsize && next < __mem_num_blocks ) + { + __mem_blocks[next] = __BLOCK_MAKE( csize - bsize, i, 1, 1 ); + int nextnext = i + csize; + if ( nextnext < __mem_num_blocks ) + { + __BLOCK_SET_PREV( __mem_blocks[nextnext], next ); + } + } + + __mem_free -= bsize; + if( __largest_block == csize ) // if we just allocated from one of the largest blocks + { + if(( csize - bsize ) > ( __mem_free / 2 )) + __largest_block = ( csize - bsize ); // there can't be another largest block + else + __largest_update = 1; + } + return (( void* )( __mem_start + ( i * __BLOCK_SIZE ))); +} + + +void vfree( void* ptr ) +{ + if( ptr == 0 ) return; + + int block = (( unsigned int )ptr - __mem_start ) / __BLOCK_SIZE; + if( block < 0 || block > __mem_num_blocks ) + { +#ifdef _DEBUG + printf( "Block is out of range: %i (0x%x)\n", block, (int)ptr ); +#endif + return; + } + int csize = __BLOCK_GET_SIZE( __mem_blocks[block] ); +#ifdef _DEBUG + printf("freeing block %i (0x%x), size: %i\n", block, (int)ptr, csize); +#endif + + if( __BLOCK_GET_FREEBLOCK( __mem_blocks[block] ) != 1 || csize == 0 ) + { +#ifdef _DEBUG + printf( "Block was not allocated!\n" ); +#endif + return; + } + + // Mark block as free + __BLOCK_SET_FREE( __mem_blocks[block], 1 ); + __mem_free += csize; + + int next = block + csize; + // Merge with previous block if possible + int prev = __BLOCK_GET_PREV( __mem_blocks[block] ); + if( prev < block ) + { + if ( __BLOCK_GET_FREEBLOCK( __mem_blocks[prev] ) == 3 ) + { + __BLOCK_ADD_SIZE( __mem_blocks[prev], csize ); + __BLOCK_SET_BLOCK( __mem_blocks[block], 0 ); // mark current block as inter block + if ( next < __mem_num_blocks ) + __BLOCK_SET_PREV( __mem_blocks[next], prev ); + block = prev; + } + } + + // Merge with next block if possible + if( next < __mem_num_blocks ) + { + if ( __BLOCK_GET_FREEBLOCK( __mem_blocks[next] ) == 3 ) + { + __BLOCK_ADD_SIZE( __mem_blocks[block], __BLOCK_GET_SIZE( __mem_blocks[next] )); + __BLOCK_SET_BLOCK( __mem_blocks[next], 0 ); // mark next block as inter block + int nextnext = next + __BLOCK_GET_SIZE( __mem_blocks[next] ); + if ( nextnext < __mem_num_blocks ) + __BLOCK_SET_PREV( __mem_blocks[nextnext], block ); + } + } + + // Update if a new largest block emerged + if( __largest_block < __BLOCK_GET_SIZE( __mem_blocks[block] )) + { + __largest_block = __BLOCK_GET_SIZE( __mem_blocks[block] ); + __largest_update = 0; // No update necessary any more, because update only necessary when largest has shrinked at most + } +} + + +size_t vmemavail( void ) +{ + if( !__mem_start ) return( 0 ); + return __mem_free * __BLOCK_SIZE; +} + + +size_t vlargestblock( void ) +{ + if( !__mem_start ) return( 0 ); + if( __largest_update ) __find_largest_block(); + return __largest_block * __BLOCK_SIZE; +} + +int vchkptr( void *ptr ) +{ + return ((( unsigned int )ptr >= __mem_start ) && (( unsigned int )ptr < __mem_start + __mem_size )); +} diff --git a/ref_gu/gu_vram.h b/ref_gu/gu_vram.h new file mode 100644 index 000000000..3c75f001d --- /dev/null +++ b/ref_gu/gu_vram.h @@ -0,0 +1,42 @@ +/* + * Helper for use with the PSP Software Development Kit - http://www.pspdev.org + * ----------------------------------------------------------------------- + * Licensed as 'free to use and modify as long as credited appropriately' + * + * vram.h - Standard C high performance VRAM allocation routines. + * + * Copyright (c) 2007 Alexander Berl 'Raphael' + * http://wordpress.fx-world.org + */ +#ifndef vram_h__ +#define vram_h__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int vinit( void ); + +void* vrelptr( void *ptr ); // make a pointer relative to memory base address (ATTENTION: A NULL rel ptr is not illegal/invalid!) +void* vabsptr( void *ptr ); // make a pointer absolute (default return type of valloc) + +void* valloc( size_t size ); +void vfree( void* ptr ); +size_t vmemavail(); +size_t vlargestblock(); +int vchkptr( void *ptr ); + + +#ifdef _DEBUG +// Debug printf (to stdout) a trace of the current Memblocks +void __memwalk(); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif // ifdef vram_h__ diff --git a/ref_gu/gu_warp.c b/ref_gu/gu_warp.c new file mode 100644 index 000000000..abbf86fa8 --- /dev/null +++ b/ref_gu/gu_warp.c @@ -0,0 +1,857 @@ +/* +gl_warp.c - sky and water polygons +Copyright (C) 2010 Uncle Mike +Copyright (C) 2020 Sergey Galushko + +This program 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 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + + +#include "gu_local.h" +#include "wadfile.h" +#define SKYCLOUDS_QUALITY 12 +#define MAX_CLIP_VERTS 128 // skybox clip vertices +#define TURBSCALE ( 256.0f / ( M_PI2 )) +const char* r_skyBoxSuffix[6] = { "rt", "bk", "lf", "ft", "up", "dn" }; +static const int r_skyTexOrder[6] = { 0, 2, 1, 3, 4, 5 }; + +static const vec3_t skyclip[6] = +{ +{ 1, 1, 0 }, +{ 1, -1, 0 }, +{ 0, -1, 1 }, +{ 0, 1, 1 }, +{ 1, 0, 1 }, +{ -1, 0, 1 } +}; + +// 1 = s, 2 = t, 3 = 2048 +static const int st_to_vec[6][3] = +{ +{ 3, -1, 2 }, +{ -3, 1, 2 }, +{ 1, 3, 2 }, +{ -1, -3, 2 }, +{ -2, -1, 3 }, // 0 degrees yaw, look straight up +{ 2, -1, -3 } // look straight down +}; + +// s = [0]/[2], t = [1]/[2] +static const int vec_to_st[6][3] = +{ +{ -2, 3, 1 }, +{ 2, 3, -1 }, +{ 1, 3, 2 }, +{ -1, 3, -2 }, +{ -2, -1, 3 }, +{ -2, 1, -3 } +}; + +// speed up sin calculations +float r_turbsin[] = +{ + #include "warpsin.h" +}; + +#define SKYBOX_MISSED 0 +#define SKYBOX_HLSTYLE 1 +#define SKYBOX_Q1STYLE 2 + +static int CheckSkybox( const char *name ) +{ + const char *skybox_ext[3] = { "bmp", "tga", "dds" }; + int i, j, num_checked_sides; + const char *sidename; + + // search for skybox images + for( i = 0; i < 3; i++ ) + { + num_checked_sides = 0; + for( j = 0; j < 6; j++ ) + { + // build side name + sidename = va( "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); + if( gEngfuncs.FS_FileExists( sidename, false )) + num_checked_sides++; + + } + + if( num_checked_sides == 6 ) + return SKYBOX_HLSTYLE; // image exists + + for( j = 0; j < 6; j++ ) + { + // build side name + sidename = va( "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); + if( gEngfuncs.FS_FileExists( sidename, false )) + num_checked_sides++; + } + + if( num_checked_sides == 6 ) + return SKYBOX_Q1STYLE; // images exists + } + + return SKYBOX_MISSED; +} + +void DrawSkyPolygon( int nump, vec3_t vecs ) +{ + int i, j, axis; + float s, t, dv, *vp; + vec3_t v, av; + + // decide which face it maps to + VectorClear( v ); + + for( i = 0, vp = vecs; i < nump; i++, vp += 3 ) + VectorAdd( vp, v, v ); + + av[0] = fabs( v[0] ); + av[1] = fabs( v[1] ); + av[2] = fabs( v[2] ); + + if( av[0] > av[1] && av[0] > av[2] ) + axis = (v[0] < 0) ? 1 : 0; + else if( av[1] > av[2] && av[1] > av[0] ) + axis = (v[1] < 0) ? 3 : 2; + else axis = (v[2] < 0) ? 5 : 4; + + // project new texture coords + for( i = 0; i < nump; i++, vecs += 3 ) + { + j = vec_to_st[axis][2]; + dv = (j > 0) ? vecs[j-1] : -vecs[-j-1]; + + if( dv == 0.0f ) continue; + + j = vec_to_st[axis][0]; + s = (j < 0) ? -vecs[-j-1] / dv : vecs[j-1] / dv; + + j = vec_to_st[axis][1]; + t = (j < 0) ? -vecs[-j-1] / dv : vecs[j-1] / dv; + + if( s < RI.skyMins[0][axis] ) RI.skyMins[0][axis] = s; + if( t < RI.skyMins[1][axis] ) RI.skyMins[1][axis] = t; + if( s > RI.skyMaxs[0][axis] ) RI.skyMaxs[0][axis] = s; + if( t > RI.skyMaxs[1][axis] ) RI.skyMaxs[1][axis] = t; + } +} + +/* +============== +ClipSkyPolygon +============== +*/ +void ClipSkyPolygon( int nump, vec3_t vecs, int stage ) +{ + const float *norm; + float *v, d, e; + qboolean front, back; + float dists[MAX_CLIP_VERTS + 1]; + int sides[MAX_CLIP_VERTS + 1]; + vec3_t newv[2][MAX_CLIP_VERTS + 1]; + int newc[2]; + int i, j; + + if( nump > MAX_CLIP_VERTS ) + gEngfuncs.Host_Error( "ClipSkyPolygon: MAX_CLIP_VERTS\n" ); +loc1: + if( stage == 6 ) + { + // fully clipped, so draw it + DrawSkyPolygon( nump, vecs ); + return; + } + + front = back = false; + norm = skyclip[stage]; + for( i = 0, v = vecs; i < nump; i++, v += 3 ) + { + d = DotProduct( v, norm ); + if( d > ON_EPSILON ) + { + front = true; + sides[i] = SIDE_FRONT; + } + else if( d < -ON_EPSILON ) + { + back = true; + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + dists[i] = d; + } + + if( !front || !back ) + { + // not clipped + stage++; + goto loc1; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy( vecs, ( vecs + ( i * 3 ))); + newc[0] = newc[1] = 0; + + for( i = 0, v = vecs; i < nump; i++, v += 3 ) + { + switch( sides[i] ) + { + case SIDE_FRONT: + VectorCopy( v, newv[0][newc[0]] ); + newc[0]++; + break; + case SIDE_BACK: + VectorCopy( v, newv[1][newc[1]] ); + newc[1]++; + break; + case SIDE_ON: + VectorCopy( v, newv[0][newc[0]] ); + newc[0]++; + VectorCopy( v, newv[1][newc[1]] ); + newc[1]++; + break; + } + + if( sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) + continue; + + d = dists[i] / ( dists[i] - dists[i+1] ); + for( j = 0; j < 3; j++ ) + { + e = v[j] + d * ( v[j+3] - v[j] ); + newv[0][newc[0]][j] = e; + newv[1][newc[1]][j] = e; + } + newc[0]++; + newc[1]++; + } + + // continue + ClipSkyPolygon( newc[0], newv[0][0], stage + 1 ); + ClipSkyPolygon( newc[1], newv[1][0], stage + 1 ); +} +#if 1 +_inline void MakeSkyVec( float s, float t, int axis, gu_vert_t *out ) +{ + int j, k, farclip; + vec3_t b; + + farclip = RI.farClip; + + b[0] = s * (farclip >> 1); + b[1] = t * (farclip >> 1); + b[2] = (farclip >> 1); + + for( j = 0; j < 3; j++ ) + { + k = st_to_vec[axis][j]; + out->xyz[j] = (k < 0) ? -b[-k-1] : b[k-1]; + out->xyz[j] += RI.cullorigin[j]; + } + + // avoid bilerp seam + s = (s + 1.0f) * 0.5f; + t = (t + 1.0f) * 0.5f; + + if( s < 1.0f / 512.0f ) + s = 1.0f / 512.0f; + else if( s > 511.0f / 512.0f ) + s = 511.0f / 512.0f; + if( t < 1.0f / 512.0f ) + t = 1.0f / 512.0f; + else if( t > 511.0f / 512.0f ) + t = 511.0f / 512.0f; + + t = 1.0f - t; + + out->uv[0] = s; + out->uv[1] = t; +} +#else +void MakeSkyVec( float s, float t, int axis ) +{ + int j, k, farclip; + vec3_t v, b; + + farclip = RI.farClip; + + b[0] = s * (farclip >> 1); + b[1] = t * (farclip >> 1); + b[2] = (farclip >> 1); + + for( j = 0; j < 3; j++ ) + { + k = st_to_vec[axis][j]; + v[j] = (k < 0) ? -b[-k-1] : b[k-1]; + v[j] += RI.cullorigin[j]; + } + + // avoid bilerp seam + s = (s + 1.0f) * 0.5f; + t = (t + 1.0f) * 0.5f; + + if( s < 1.0f / 512.0f ) + s = 1.0f / 512.0f; + else if( s > 511.0f / 512.0f ) + s = 511.0f / 512.0f; + if( t < 1.0f / 512.0f ) + t = 1.0f / 512.0f; + else if( t > 511.0f / 512.0f ) + t = 511.0f / 512.0f; + + t = 1.0f - t; + + pglTexCoord2f( s, t ); + pglVertex3fv( v ); +} +#endif +/* +============== +R_ClearSkyBox +============== +*/ +void R_ClearSkyBox( void ) +{ + int i; + + for( i = 0; i < 6; i++ ) + { + RI.skyMins[0][i] = RI.skyMins[1][i] = 9999999.0f; + RI.skyMaxs[0][i] = RI.skyMaxs[1][i] = -9999999.0f; + } +} + +/* +================= +R_AddSkyBoxSurface +================= +*/ +void R_AddSkyBoxSurface( msurface_t *fa ) +{ + vec3_t verts[MAX_CLIP_VERTS]; + glpoly_t *p; + float *v; + int i; + + if( ENGINE_GET_PARM( PARM_SKY_SPHERE ) && fa->polys && !tr.fCustomSkybox ) + { + glpoly_t *p = fa->polys; + + // draw the sky poly + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, p->numverts, 0, p->verts ); + } + + // calculate vertex values for sky box + for( p = fa->polys; p; p = p->next ) + { + for( i = 0; i < p->numverts; i++ ) + VectorSubtract( p->verts[i].xyz, RI.cullorigin, verts[i] ); + ClipSkyPolygon( p->numverts, verts[0], 0 ); + } +} + +/* +============== +R_UnloadSkybox + +Unload previous skybox +============== +*/ +void R_UnloadSkybox( void ) +{ + int i; + + // release old skybox + for( i = 0; i < 6; i++ ) + { + if( !tr.skyboxTextures[i] ) continue; + GL_FreeTexture( tr.skyboxTextures[i] ); + } +#if 0 + tr.skyboxbasenum = 5800; // set skybox base (to let some mods load hi-res skyboxes) +#endif + memset( tr.skyboxTextures, 0, sizeof( tr.skyboxTextures )); + tr.fCustomSkybox = false; +} + +/* +============== +R_DrawSkybox +============== +*/ +void R_DrawSkyBox( void ) +{ + int i; + RI.isSkyVisible = true; + + // don't fogging skybox (this fix old Half-Life bug) + if( !RI.fogSkybox ) R_AllowFog( false ); + +/* + if( RI.fogEnabled ) + pglFogf( GL_FOG_DENSITY, RI.fogDensity * 0.5f ); +*/ + sceGuDisable( GU_BLEND ); + sceGuDisable( GU_ALPHA_TEST ); + sceGuTexFunc( GU_TFX_REPLACE, GU_TCC_RGBA ); + + for( i = 0; i < 6; i++ ) + { + if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) + continue; + + if( tr.skyboxTextures[r_skyTexOrder[i]] ) + GL_Bind( XASH_TEXTURE0, tr.skyboxTextures[r_skyTexOrder[i]] ); + else GL_Bind( XASH_TEXTURE0, tr.grayTexture ); // stub + + gu_vert_t* const uv = ( gu_vert_t* )sceGuGetMemory( sizeof( gu_vert_t ) * 4 ); + MakeSkyVec( RI.skyMins[0][i], RI.skyMins[1][i], i, &uv[0] ); + MakeSkyVec( RI.skyMins[0][i], RI.skyMaxs[1][i], i, &uv[1] ); + MakeSkyVec( RI.skyMaxs[0][i], RI.skyMaxs[1][i], i, &uv[2] ); + MakeSkyVec( RI.skyMaxs[0][i], RI.skyMins[1][i], i, &uv[3] ); + + if ( GU_ClipIsRequired( uv, 4 ) ) + { + gu_vert_t* cv; + int cvc; + + GU_Clip( uv, 4, &cv, &cvc ); + if( cvc ) sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, cvc, 0, cv ); + } + else sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, 4, 0, uv ); + } + + if( !RI.fogSkybox ) + R_AllowFog( true ); +/* + if( RI.fogEnabled ) + pglFogf( GL_FOG_DENSITY, RI.fogDensity ); +*/ + R_LoadIdentity(); +} + +/* +=============== +R_SetupSky +=============== +*/ +void R_SetupSky( const char *skyboxname ) +{ + char loadname[MAX_STRING]; + char sidename[MAX_STRING]; + int i, result; + + if( !COM_CheckString( skyboxname )) + { + R_UnloadSkybox(); + return; // clear old skybox + } + + Q_snprintf( loadname, sizeof( loadname ), "gfx/env/%s", skyboxname ); + COM_StripExtension( loadname ); + + // kill the underline suffix to find them manually later + if( loadname[Q_strlen( loadname ) - 1] == '_' ) + loadname[Q_strlen( loadname ) - 1] = '\0'; + result = CheckSkybox( loadname ); + + // to prevent infinite recursion if default skybox was missed + if( result == SKYBOX_MISSED && Q_stricmp( loadname, DEFAULT_SKYBOX_PATH )) + { + gEngfuncs.Con_Reportf( S_WARN "missed or incomplete skybox '%s'\n", skyboxname ); + R_SetupSky( "desert" ); // force to default + return; + } + + // release old skybox + R_UnloadSkybox(); + gEngfuncs.Con_DPrintf( "SKY: " ); + + for( i = 0; i < 6; i++ ) + { + if( result == SKYBOX_HLSTYLE ) + Q_snprintf( sidename, sizeof( sidename ), "%s%s", loadname, r_skyBoxSuffix[i] ); + else Q_snprintf( sidename, sizeof( sidename ), "%s_%s", loadname, r_skyBoxSuffix[i] ); + + tr.skyboxTextures[i] = GL_LoadTexture( sidename, NULL, 0, TF_CLAMP|TF_SKY ); + if( !tr.skyboxTextures[i] ) break; + gEngfuncs.Con_DPrintf( "%s%s%s", skyboxname, r_skyBoxSuffix[i], i != 5 ? ", " : ". " ); + } + + if( i == 6 ) + { + tr.fCustomSkybox = true; + gEngfuncs.Con_DPrintf( "done\n" ); + return; // loaded + } + + gEngfuncs.Con_DPrintf( "^2failed\n" ); + R_UnloadSkybox(); +} + +//============================================================================== +// +// RENDER CLOUDS +// +//============================================================================== +/* +============== +R_CloudVertex +============== +*/ +void R_CloudVertex( float s, float t, int axis, vec3_t v ) +{ + int j, k, farclip; + vec3_t b; + + farclip = RI.farClip; + + b[0] = s * (farclip >> 1); + b[1] = t * (farclip >> 1); + b[2] = (farclip >> 1); + + for( j = 0; j < 3; j++ ) + { + k = st_to_vec[axis][j]; + v[j] = (k < 0) ? -b[-k-1] : b[k-1]; + v[j] += RI.cullorigin[j]; + } +} + +/* +============= +R_CloudTexCoord +============= +*/ +#if 1 +_inline void R_CloudTexCoord( vec3_t v, float speed, float *s, float *t ) +#else +void R_CloudTexCoord( vec3_t v, float speed, float *s, float *t ) +#endif +{ + float length, speedscale; + vec3_t dir; + + speedscale = gpGlobals->time * speed; + speedscale -= (int)speedscale & ~127; + + VectorSubtract( v, RI.vieworg, dir ); + dir[2] *= 3.0f; // flatten the sphere + + length = VectorLength( dir ); + length = 6.0f * 63.0f / length; + + *s = ( speedscale + dir[0] * length ) * (1.0f / 128.0f); + *t = ( speedscale + dir[1] * length ) * (1.0f / 128.0f); +} + +/* +=============== +R_CloudDrawPoly +=============== +*/ +void R_CloudDrawPoly( glpoly_t *p ) +{ + float *v; + int i; + + GL_SetRenderMode( kRenderNormal ); + GL_Bind( XASH_TEXTURE0, tr.solidskyTexture ); + + gu_vert_t* out = ( gu_vert_t* )sceGuGetMemory( sizeof( gu_vert_t ) * 4 ); + for( i = 0; i < 4; i++ ) + { + R_CloudTexCoord( p->verts[i].xyz, 8.0f, &out[i].uv[0], &out[i].uv[1] ); + VectorCopy( p->verts[i].xyz, out[i].xyz ); + } + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, 4, 0, out ); + + GL_SetRenderMode( kRenderTransTexture ); + GL_Bind( XASH_TEXTURE0, tr.alphaskyTexture ); + + out = ( gu_vert_t* )sceGuGetMemory( sizeof( gu_vert_t ) * 4 ); + for( i = 0; i < 4; i++ ) + { + R_CloudTexCoord( p->verts[i].xyz, 16.0f, &out[i].uv[0], &out[i].uv[1] ); + VectorCopy( p->verts[i].xyz, out[i].xyz ); + } + sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, 4, 0, out ); + + sceGuDisable( GU_BLEND ); +} + +/* +============== +R_CloudRenderSide +============== +*/ +void R_CloudRenderSide( int axis ) +{ + vec3_t verts[4]; + float di, qi, dj, qj; + vec3_t vup, vright; + vec3_t temp, temp2; + glpoly_t p[1]; + int i, j; + + R_CloudVertex( -1.0f, -1.0f, axis, verts[0] ); + R_CloudVertex( -1.0f, 1.0f, axis, verts[1] ); + R_CloudVertex( 1.0f, 1.0f, axis, verts[2] ); + R_CloudVertex( 1.0f, -1.0f, axis, verts[3] ); + + VectorSubtract( verts[2], verts[3], vup ); + VectorSubtract( verts[2], verts[1], vright ); + + p->numverts = 4; + di = SKYCLOUDS_QUALITY; + qi = 1.0f / di; + dj = (axis < 4) ? di * 2 : di; //subdivide vertically more than horizontally on skybox sides + qj = 1.0f / dj; + + for( i = 0; i < di; i++ ) + { + for( j = 0; j < dj; j++ ) + { + if( i * qi < RI.skyMins[0][axis] / 2 + 0.5f - qi + || i * qi > RI.skyMaxs[0][axis] / 2 + 0.5f + || j * qj < RI.skyMins[1][axis] / 2 + 0.5f - qj + || j * qj > RI.skyMaxs[1][axis] / 2 + 0.5f ) + continue; + + VectorScale( vright, qi * i, temp ); + VectorScale( vup, qj * j, temp2 ); + VectorAdd( temp, temp2, temp ); + VectorAdd( verts[0], temp, p->verts[0].xyz ); + + VectorScale( vup, qj, temp ); + VectorAdd( p->verts[0].xyz, temp, p->verts[1].xyz ); + + VectorScale( vright, qi, temp ); + VectorAdd( p->verts[1].xyz, temp, p->verts[2].xyz ); + + VectorAdd( p->verts[0].xyz, temp, p->verts[3].xyz ); + + R_CloudDrawPoly( p ); + } + } +} + +/* +============== +R_DrawClouds + +Quake-style clouds +============== +*/ +void R_DrawClouds( void ) +{ + int i; + + RI.isSkyVisible = true; + +/* + if( RI.fogEnabled ) + pglFogf( GL_FOG_DENSITY, RI.fogDensity * 0.25f ); +*/ + sceGuDepthFunc( GU_GEQUAL ); + sceGuDepthMask( GU_TRUE ); + + for( i = 0; i < 6; i++ ) + { + if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) + continue; + R_CloudRenderSide( i ); + } + + sceGuDepthFunc( GU_LEQUAL ); + sceGuDepthMask( GU_FALSE ); +/* + if( RI.fogEnabled ) + pglFogf( GL_FOG_DENSITY, RI.fogDensity ); +*/ +} + +/* +============= +R_InitSkyClouds + +A sky texture is 256*128, with the right side being a masked overlay +============== +*/ +void R_InitSkyClouds( mip_t *mt, texture_t *tx, qboolean custom_palette ) +{ + rgbdata_t r_temp, *r_sky; + uint *trans, *rgba; + uint transpix; + int r, g, b; + int i, j, p; + char texname[32]; + + if( !glw_state.initialized ) + return; + + Q_snprintf( texname, sizeof( texname ), "%s%s.mip", ( mt->offsets[0] > 0 ) ? "#" : "", tx->name ); + + if( mt->offsets[0] > 0 ) + { + int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); + + if( custom_palette ) size += sizeof( short ) + 768; + r_sky = gEngfuncs.FS_LoadImage( texname, (byte *)mt, size ); + } + else + { + // okay, loading it from wad + r_sky = gEngfuncs.FS_LoadImage( texname, NULL, 0 ); + } + + // make sure what sky image is valid + if( !r_sky || !r_sky->palette || r_sky->type != PF_INDEXED_32 || r_sky->height == 0 ) + { + gEngfuncs.Con_Reportf( S_ERROR "R_InitSky: unable to load sky texture %s\n", tx->name ); + if( r_sky ) gEngfuncs.FS_FreeImage( r_sky ); + return; + } + + // make an average value for the back to avoid + // a fringe on the top level + trans = Mem_Malloc( r_temppool, r_sky->height * r_sky->height * sizeof( *trans )); + r = g = b = 0; + + for( i = 0; i < r_sky->width >> 1; i++ ) + { + for( j = 0; j < r_sky->height; j++ ) + { + p = r_sky->buffer[i * r_sky->width + j + r_sky->height]; + rgba = (uint *)r_sky->palette + p; + trans[(i * r_sky->height) + j] = *rgba; + r += ((byte *)rgba)[0]; + g += ((byte *)rgba)[1]; + b += ((byte *)rgba)[2]; + } + } + + ((byte *)&transpix)[0] = r / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[1] = g / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[2] = b / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[3] = 0; + + // build a temporary image + r_temp = *r_sky; + r_temp.width = r_sky->width >> 1; + r_temp.height = r_sky->height; + r_temp.type = PF_RGBA_32; + r_temp.flags = IMAGE_HAS_COLOR; + r_temp.size = r_temp.width * r_temp.height * 4; + r_temp.buffer = (byte *)trans; + r_temp.palette = NULL; + + // load it in + tr.solidskyTexture = GL_LoadTextureInternal( REF_SOLIDSKY_TEXTURE, &r_temp, TF_NOMIPMAP ); + + for( i = 0; i < r_sky->width >> 1; i++ ) + { + for( j = 0; j < r_sky->height; j++ ) + { + p = r_sky->buffer[i * r_sky->width + j]; + + if( p == 0 ) + { + trans[(i * r_sky->height) + j] = transpix; + } + else + { + rgba = (uint *)r_sky->palette + p; + trans[(i * r_sky->height) + j] = *rgba; + } + } + } + + r_temp.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; + + // load it in + tr.alphaskyTexture = GL_LoadTextureInternal( REF_ALPHASKY_TEXTURE, &r_temp, TF_NOMIPMAP ); + + // clean up + gEngfuncs.FS_FreeImage( r_sky ); + Mem_Free( trans ); +} + +/* +============= +EmitWaterPolys + +Does a water warp on the pre-fragmented glpoly_t chain +============= +*/ +void EmitWaterPolys( msurface_t *warp, qboolean reverse ) +{ + gu_vert_t *verts; + float waveHeight; + glpoly_t *p; + int i; + + if( !warp->polys ) return; + + // set the current waveheight + if( warp->polys->verts[0].xyz[2] >= RI.vieworg[2] ) + waveHeight = -RI.currententity->curstate.scale; + else waveHeight = RI.currententity->curstate.scale; + + // reset fog color for nonlightmapped water + GL_ResetFogColor(); + + for( p = warp->polys; p; p = p->next ) + { + if( reverse ) verts = &p->verts[p->numverts - 1]; + else verts = &p->verts[0]; + + gu_vert_t* const out = ( gu_vert_t* )sceGuGetMemory( sizeof( gu_vert_t ) * p->numverts ); + for( i = 0; i < p->numverts; i++ ) + { + out[i].uv[0] = verts->uv[0] + r_turbsin[( int )(( verts->uv[1] * 0.125f + gpGlobals->time ) * TURBSCALE ) & 255]; + out[i].uv[0] *= ( 1.0f / SUBDIVIDE_SIZE ); + out[i].uv[1] = verts->uv[1] + r_turbsin[( int )(( verts->uv[0] * 0.125f + gpGlobals->time ) * TURBSCALE ) & 255]; + out[i].uv[1] *= ( 1.0f / SUBDIVIDE_SIZE ); + + out[i].xyz[0] = verts->xyz[0]; + out[i].xyz[1] = verts->xyz[1]; + + if( waveHeight ) + { + out[i].xyz[2] = r_turbsin[( int )( gpGlobals->time * 160.0f + verts->xyz[1] + verts->xyz[0] ) & 255] + 8.0f; + out[i].xyz[2] = ( r_turbsin[( int )( verts->xyz[0] * 5.0f + gpGlobals->time * 171.0f - verts->xyz[1] ) & 255] + 8.0f ) * 0.8f + out[i].xyz[2]; + out[i].xyz[2] = out[i].xyz[2] * waveHeight + verts->xyz[2]; + } + else out[i].xyz[2] = verts->xyz[2]; + + if( reverse ) verts -= 1; + else verts += 1; + } + if ( GU_ClipIsRequired( out, p->numverts )) + { + // Clip the polygon. + gu_vert_t* cv; + int cvc; + + GU_Clip( out, p->numverts, &cv, &cvc ); + if( cvc ) sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, cvc, 0, cv ); + } + else sceGuDrawArray( GU_TRIANGLE_FAN, GU_TEXTURE_32BITF | GU_VERTEX_32BITF, p->numverts, 0, out ); + } + GL_SetupFogColorForSurfaces(); +} diff --git a/ref_gu/wscript b/ref_gu/wscript new file mode 100644 index 000000000..7c05884b8 --- /dev/null +++ b/ref_gu/wscript @@ -0,0 +1,58 @@ +#! /usr/bin/env python +# encoding: utf-8 +# mittorn, 2018 + +from waflib import Logs +import os + +top = '.' + +def options(opt): + grp = opt.add_option_group('ref_gu options') + + grp.add_option('--enable-static-gl', action='store_true', dest='GL_STATIC', default=False, + help = 'enable direct linking to opengl [default: %default]') + + grp.add_option('--disable-gl', action='store_false', dest='GL', default=True, + help = 'disable opengl [default: %default]') + + # stub + return + +def configure(conf): + # check for dedicated server build + if conf.options.DEDICATED: + return + + conf.define_cond('SUPPORT_BSP2_FORMAT', conf.options.SUPPORT_BSP2_FORMAT) + + conf.env.GL = conf.options.GL + conf.env.GL_STATIC = conf.options.GL_STATIC + + conf.define('REF_DLL', 1) + +def build(bld): + libs = [ 'public', 'M' ] + + source = bld.path.ant_glob(['*.c']) + + includes = ['.', + '../engine', + '../engine/common', + '../engine/server', + '../engine/client', + '../public', + '../common', + '../pm_shared' ] + + if bld.env.GL: + bld.shlib( + source = source, + target = 'ref_gu', + features = 'c', + includes = includes, + use = libs + (['GL'] if bld.env.GL_STATIC else []), + defines = ['XASH_GL_STATIC'] if bld.env.GL_STATIC else [], + install_path = bld.env.LIBDIR, + subsystem = bld.env.MSVC_SUBSYSTEM + ) diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 18f190a00..313b52d32 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -13,7 +13,7 @@ try: from fwgslib import get_flags_by_compiler except: from waflib.extras.fwgslib import get_flags_by_compiler -from waflib import Logs, TaskGen +from waflib import Logs, TaskGen, Task from waflib.Tools import c_config from collections import OrderedDict import os @@ -31,8 +31,8 @@ ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets NSWITCH_ENVVARS = ['DEVKITPRO'] - PSVITA_ENVVARS = ['VITASDK'] +PSP_SDK_ENVVARS = ['PSPDEV', 'PSPSDK', 'PSPTOOLCHAIN'] # This class does support ONLY r10e and r19c/r20 NDK class Android: @@ -498,6 +498,63 @@ def ldflags(self): ldflags = [] return ldflags +class PSP: + ctx = None # waf context + sdk_home = None + psptoolchain_path = None + pspsdk_path = None + binutils_path = None + build_prx = None + fw_version = None + render_type = None + + def __init__(self, ctx, moduletype, fwversion, rendertype): + self.ctx = ctx + self.build_prx = True if moduletype == 'prx' else False + self.fw_version = fwversion + self.render_type = rendertype + + for i in PSP_SDK_ENVVARS: + self.sdk_home = os.getenv(i) + if self.sdk_home != None: + break + else: + ctx.fatal('Set %s environment variable pointing to the root of PSP SDK!' % + ' or '.join(PSP_SDK_ENVVARS)) + + self.psptoolchain_path = os.path.join(self.sdk_home, 'psp') + self.pspsdk_path = os.path.join(self.psptoolchain_path, 'sdk') + self.binutils_path = os.path.join(self.sdk_home, 'bin') + + def cflags(self, cxx = False): + cflags = [] + cflags += ['-I%s' % (os.path.join(self.pspsdk_path, 'include'))] + cflags += ['-I.'] + cflags += ['-DNDEBUG', '-D_PSP_FW_VERSION=%s' % self.fw_version, '-G0'] + return cflags + # they go before object list + def linkflags(self): + linkflags = [] + return linkflags + + def ldflags(self): + ldflags = [] + if self.build_prx: + ldflags += ['-specs=%s' % os.path.join(self.pspsdk_path, 'lib/prxspecs')] + ldflags += ['-Wl,-q,-T%s' % os.path.join(self.pspsdk_path, 'lib/linkfile.prx')] + ldflags += ['-L%s' % os.path.join(self.pspsdk_path, 'lib')] + ldflags += ['-L.'] + return ldflags + + def stdlibs(self): + stdlibs = [] + stdlibs += ['-lpspdisplay', '-lpspgum_vfpu', '-lpspgu','-lpspge', '-lpspvfpu'] + stdlibs += ['-lpspaudio', '-lpspdmac'] + stdlibs += ['-lstdc++', '-lc', '-lm'] + stdlibs += ['-lpspctrl', '-lpspdebug', '-lpsppower', '-lpsputility', '-lpspsdk', '-lpsprtc'] + stdlibs += ['-lpspuser', '-lpspkernel'] + return stdlibs + def options(opt): xc = opt.add_option_group('Cross compile options') xc.add_option('--android', action='store', dest='ANDROID_OPTS', default=None, @@ -512,6 +569,8 @@ def options(opt): help='enable building for PlayStation Vita [default: %default]') xc.add_option('--sailfish', action='store', dest='SAILFISH', default = None, help='enable building for Sailfish/Aurora') + xc.add_option('--psp', action='store', dest='PSP_OPTS', default=None, + help='enable building for Sony PSP, format: --psp=,, example: --psp=prx,660,HW') def configure(conf): if conf.options.ANDROID_OPTS: @@ -592,15 +651,69 @@ def configure(conf): conf.env.LIB_M = ['m'] conf.env.VRTLD = ['vrtld'] conf.env.DEST_OS = 'psvita' + elif conf.options.PSP_OPTS: + values = conf.options.PSP_OPTS.split(',') + if len(values) != 3: + conf.fatal('Invalid --psp paramater value!') + + valid_module_type = ['elf', 'prx'] + valid_render_type = ['SW', 'HW', 'ALL'] + + if values[0] not in valid_module_type: + conf.fatal('Unknown module type: %s. Supported: %r' % (values[0], ', '.join(valid_module_type))) + if values[2] not in valid_render_type: + conf.fatal('Unknown render type: %s. Supported: %r' % (values[2], ', '.join(valid_render_type))) + + conf.psp = psp = PSP(conf, values[0], values[1], values[2]) + conf.environ['CC'] = os.path.join(psp.binutils_path, 'psp-gcc') + conf.environ['CXX'] = os.path.join(psp.binutils_path, 'psp-gcc') + conf.environ['AS'] = os.path.join(psp.binutils_path, 'psp-gcc') + conf.environ['STRIP'] = os.path.join(psp.binutils_path, 'psp-strip') + conf.environ['LD'] = os.path.join(psp.binutils_path, 'psp-gcc') + conf.environ['AR'] = os.path.join(psp.binutils_path, 'psp-ar') + conf.environ['RANLIB'] = os.path.join(psp.binutils_path, 'psp-ranlib') + conf.environ['OBJCOPY'] = os.path.join(psp.binutils_path, 'psp-objcopy') + + conf.env.PRXGEN = conf.find_program('psp-prxgen', path_list = psp.binutils_path) + conf.env.MKSFO = conf.find_program('mksfoex', path_list = psp.binutils_path) + conf.env.PACK_PBP = conf.find_program('pack-pbp', path_list = psp.binutils_path) + conf.env.FIXUP = conf.find_program('psp-fixup-imports', path_list = psp.binutils_path) + + conf.env.ASFLAGS += psp.cflags() + conf.env.CFLAGS += psp.cflags() + conf.env.CXXFLAGS += psp.cflags() + conf.env.LINKFLAGS += psp.linkflags() + conf.env.LDFLAGS += psp.ldflags() + + conf.msg('Selected PSPSDK', '%s' % (psp.sdk_home)) + # no need to print C/C++ compiler, as it would be printed by compiler_c/cxx + conf.msg('... C/C++ flags', ' '.join(psp.cflags()).replace(psp.sdk_home, '$PSPSDK')) + conf.msg('... link flags', ' '.join(psp.linkflags()).replace(psp.sdk_home, '$PSPSDK')) + conf.msg('... ld flags', ' '.join(psp.ldflags()).replace(psp.sdk_home, '$PSPSDK')) + conf.msg('Bulid prx', '%s' % (psp.build_prx)) + + if conf.options.PROFILING: + conf.env.LDFLAGS += ['-lpspprof'] + conf.env.CFLAGS += ['-pg'] + conf.env.CXXFLAGS += ['-pg'] + conf.env.LDFLAGS += psp.stdlibs() + + conf.env.PSP_RENDER_TYPE = psp.render_type + conf.env.PSP_BUILD_PRX = psp.build_prx conf.env.MAGX = conf.options.MAGX conf.env.MSVC_WINE = conf.options.MSVC_WINE conf.env.SAILFISH = conf.options.SAILFISH - MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android', '__SWITCH__' : 'nswitch', '__vita__' : 'psvita' }) + MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android', '__SWITCH__' : 'nswitch', '__vita__' : 'psvita', '__psp__': 'psp' }) + for k in c_config.MACRO_TO_DESTOS: MACRO_TO_DESTOS[k] = c_config.MACRO_TO_DESTOS[k] # ordering is important c_config.MACRO_TO_DESTOS = MACRO_TO_DESTOS +def build(bld): + if bld.env.DEST_OS == 'psp': + apply_psptools() + def post_compiler_cxx_configure(conf): conf.msg('Target OS', conf.env.DEST_OS) conf.msg('Target CPU', conf.env.DEST_CPU) diff --git a/scripts/waifulib/xshlib.py b/scripts/waifulib/xshlib.py index 60c10120d..47c7547d6 100644 --- a/scripts/waifulib/xshlib.py +++ b/scripts/waifulib/xshlib.py @@ -96,6 +96,8 @@ def apply_xshlib(self): for k in ('cshlib', 'cxxshlib'): if k in self.features: self.features.remove(k) + if getattr(self, 'install_path', None): + delattr(self, 'install_path') self.features.append('xshlib') in_node = self.path.get_src().make_node('exports.txt') bldnode = self.path.get_bld() diff --git a/wscript b/wscript index fbbae3d65..3717951fc 100644 --- a/wscript +++ b/wscript @@ -75,6 +75,7 @@ SUBDIRS = [ Subproject('3rdparty/gl4es', lambda x: not x.env.DEDICATED and x.env.GL4ES), Subproject('ref/gl', lambda x: not x.env.DEDICATED and (x.env.GL or x.env.NANOGL or x.env.GLWES or x.env.GL4ES)), Subproject('ref/soft', lambda x: not x.env.DEDICATED and not x.env.SUPPORT_BSP2_FORMAT and x.env.SOFT), + Subproject('ref/gu', lambda x: not x.env.DEDICATED and x.env.DEST_OS == 'psp' and x.env.GU), Subproject('3rdparty/mainui', lambda x: not x.env.DEDICATED), Subproject('3rdparty/vgui_support', lambda x: not x.env.DEDICATED), Subproject('stub/client', lambda x: not x.env.DEDICATED), @@ -100,6 +101,7 @@ REFDLLS = [ RefDll('gles2', False, 'GLWES'), RefDll('gl4es', False), RefDll('gles3compat', False), + RefDll('gu', False), ] def options(opt): @@ -131,6 +133,9 @@ def options(opt): grp.add_option('--disable-werror', action = 'store_true', dest = 'DISABLE_WERROR', default = False, help = 'disable compilation abort on warning') + grp.add_option('--enable-profiling', action = 'store_true', dest = 'PROFILING', default = False, + help = 'enable building with GNU profiling tools') + grp.add_option('--enable-tests', action = 'store_true', dest = 'TESTS', default = False, help = 'enable building standalone tests (does not enable engine tests!) [default: %default]') @@ -166,11 +171,8 @@ def configure(conf): # Load compilers early conf.load('xshlib xcompile compiler_c compiler_cxx cmake gccdeps') - if conf.options.NSWITCH: - conf.load('nswitch') - - if conf.options.PSVITA: - conf.load('psvita') + if conf.env.DEST_OS in ['nswitch', 'psvita', 'psp']: + conf.load(conf.env.DEST_OS) # load additional tools # HACKHACK: override msvc DEST_CPU value by something that we understand if conf.env.DEST_CPU == 'amd64': @@ -220,6 +222,26 @@ def configure(conf): conf.options.USE_STBTT = True # we'll specify -fPIC by hand for shared libraries only enforce_pic = False + elif conf.env.DEST_OS == 'psp': + conf.options.NO_VGUI = True + conf.options.NO_ASYNC_RESOLVE = True + conf.options.LOW_MEMORY = 2 + conf.options.GL = False + conf.options.STATIC = True + enforce_pic = False + + # set EBOOT.PBP generation parameters + conf.env.PSP_EBOOT_TITLE = 'Xash3D' + conf.env.PSP_EBOOT_SFO = 'PARAM.SFO' + conf.env.PSP_EBOOT_ICON = 'NULL' + conf.env.PSP_EBOOT_ICON1 = 'NULL' + conf.env.PSP_EBOOT_UNKPNG = 'NULL' + conf.env.PSP_EBOOT_PIC1 = 'NULL' + conf.env.PSP_EBOOT_SND0 = 'NULL' + conf.env.PSP_EBOOT_PSAR = 'NULL' + + conf.env.SOFT = conf.env.PSP_RENDER_TYPE in ['SW', 'ALL'] + conf.env.GU = conf.env.PSP_RENDER_TYPE in ['HW', 'ALL'] if conf.env.STATIC_LINKING: enforce_pic = False # PIC may break full static builds @@ -351,7 +373,7 @@ def configure(conf): conf.env.SUPPORT_BSP2_FORMAT = conf.options.SUPPORT_BSP2_FORMAT # disable game_launch compiling on platform where it's not needed - conf.env.DISABLE_LAUNCHER = conf.env.DEST_OS in ['android', 'nswitch', 'psvita', 'dos'] or conf.env.MAGX or conf.env.DEDICATED + conf.env.DISABLE_LAUNCHER = conf.env.DEST_OS in ['android', 'nswitch', 'psvita', 'dos', 'psp'] or conf.env.MAGX or conf.env.DEDICATED if conf.env.SAILFISH == 'aurora': conf.env.DEFAULT_RPATH = '/usr/share/su.xash.Engine/lib' @@ -482,6 +504,7 @@ int main(void) { return !opus_custom_encoder_init((OpusCustomEncoder *)1, (const conf.env.HAVE_SYSTEM_OPUS = True conf.define('XASH_LOW_MEMORY', conf.options.LOW_MEMORY) + conf.define_cond('XASH_PROFILING', conf.options.PROFILING) for i in SUBDIRS: if not i.is_enabled(conf):