mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-11 13:11:47 +00:00
1284 lines
30 KiB
C++
1284 lines
30 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code 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.
|
|
|
|
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#pragma hdrstop
|
|
#include "../idlib/precompiled.h"
|
|
|
|
#include "DeviceContext.h"
|
|
#include "../renderer/GuiModel.h"
|
|
|
|
extern idCVar in_useJoystick;
|
|
|
|
// bypass rendersystem to directly work on guiModel
|
|
extern idGuiModel* tr_guiModel;
|
|
|
|
idVec4 idDeviceContext::colorPurple;
|
|
idVec4 idDeviceContext::colorOrange;
|
|
idVec4 idDeviceContext::colorYellow;
|
|
idVec4 idDeviceContext::colorGreen;
|
|
idVec4 idDeviceContext::colorBlue;
|
|
idVec4 idDeviceContext::colorRed;
|
|
idVec4 idDeviceContext::colorBlack;
|
|
idVec4 idDeviceContext::colorWhite;
|
|
idVec4 idDeviceContext::colorNone;
|
|
|
|
void idDeviceContext::Init()
|
|
{
|
|
xScale = 1.0f;
|
|
yScale = 1.0f;
|
|
xOffset = 0.0f;
|
|
yOffset = 0.0f;
|
|
whiteImage = declManager->FindMaterial( "guis/assets/white.tga" );
|
|
whiteImage->SetSort( SS_GUI );
|
|
activeFont = renderSystem->RegisterFont( "" );
|
|
colorPurple = idVec4( 1, 0, 1, 1 );
|
|
colorOrange = idVec4( 1, 1, 0, 1 );
|
|
colorYellow = idVec4( 0, 1, 1, 1 );
|
|
colorGreen = idVec4( 0, 1, 0, 1 );
|
|
colorBlue = idVec4( 0, 0, 1, 1 );
|
|
colorRed = idVec4( 1, 0, 0, 1 );
|
|
colorWhite = idVec4( 1, 1, 1, 1 );
|
|
colorBlack = idVec4( 0, 0, 0, 1 );
|
|
colorNone = idVec4( 0, 0, 0, 0 );
|
|
cursorImages[CURSOR_ARROW] = declManager->FindMaterial( "ui/assets/guicursor_arrow.tga" );
|
|
cursorImages[CURSOR_HAND] = declManager->FindMaterial( "ui/assets/guicursor_hand.tga" );
|
|
cursorImages[CURSOR_HAND_JOY1] = declManager->FindMaterial( "ui/assets/guicursor_hand_cross.tga" );
|
|
cursorImages[CURSOR_HAND_JOY2] = declManager->FindMaterial( "ui/assets/guicursor_hand_circle.tga" );
|
|
cursorImages[CURSOR_HAND_JOY3] = declManager->FindMaterial( "ui/assets/guicursor_hand_square.tga" );
|
|
cursorImages[CURSOR_HAND_JOY4] = declManager->FindMaterial( "ui/assets/guicursor_hand_triangle.tga" );
|
|
cursorImages[CURSOR_HAND_JOY1] = declManager->FindMaterial( "ui/assets/guicursor_hand_a.tga" );
|
|
cursorImages[CURSOR_HAND_JOY2] = declManager->FindMaterial( "ui/assets/guicursor_hand_b.tga" );
|
|
cursorImages[CURSOR_HAND_JOY3] = declManager->FindMaterial( "ui/assets/guicursor_hand_x.tga" );
|
|
cursorImages[CURSOR_HAND_JOY4] = declManager->FindMaterial( "ui/assets/guicursor_hand_y.tga" );
|
|
scrollBarImages[SCROLLBAR_HBACK] = declManager->FindMaterial( "ui/assets/scrollbarh.tga" );
|
|
scrollBarImages[SCROLLBAR_VBACK] = declManager->FindMaterial( "ui/assets/scrollbarv.tga" );
|
|
scrollBarImages[SCROLLBAR_THUMB] = declManager->FindMaterial( "ui/assets/scrollbar_thumb.tga" );
|
|
scrollBarImages[SCROLLBAR_RIGHT] = declManager->FindMaterial( "ui/assets/scrollbar_right.tga" );
|
|
scrollBarImages[SCROLLBAR_LEFT] = declManager->FindMaterial( "ui/assets/scrollbar_left.tga" );
|
|
scrollBarImages[SCROLLBAR_UP] = declManager->FindMaterial( "ui/assets/scrollbar_up.tga" );
|
|
scrollBarImages[SCROLLBAR_DOWN] = declManager->FindMaterial( "ui/assets/scrollbar_down.tga" );
|
|
cursorImages[CURSOR_ARROW]->SetSort( SS_GUI );
|
|
cursorImages[CURSOR_HAND]->SetSort( SS_GUI );
|
|
scrollBarImages[SCROLLBAR_HBACK]->SetSort( SS_GUI );
|
|
scrollBarImages[SCROLLBAR_VBACK]->SetSort( SS_GUI );
|
|
scrollBarImages[SCROLLBAR_THUMB]->SetSort( SS_GUI );
|
|
scrollBarImages[SCROLLBAR_RIGHT]->SetSort( SS_GUI );
|
|
scrollBarImages[SCROLLBAR_LEFT]->SetSort( SS_GUI );
|
|
scrollBarImages[SCROLLBAR_UP]->SetSort( SS_GUI );
|
|
scrollBarImages[SCROLLBAR_DOWN]->SetSort( SS_GUI );
|
|
cursor = CURSOR_ARROW;
|
|
enableClipping = true;
|
|
overStrikeMode = true;
|
|
mat.Identity();
|
|
matIsIdentity = true;
|
|
origin.Zero();
|
|
initialized = true;
|
|
}
|
|
|
|
void idDeviceContext::Shutdown()
|
|
{
|
|
clipRects.Clear();
|
|
Clear();
|
|
}
|
|
|
|
void idDeviceContext::Clear()
|
|
{
|
|
initialized = false;
|
|
}
|
|
|
|
idDeviceContext::idDeviceContext()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void idDeviceContext::SetTransformInfo( const idVec3& org, const idMat3& m )
|
|
{
|
|
origin = org;
|
|
mat = m;
|
|
matIsIdentity = mat.IsIdentity();
|
|
}
|
|
|
|
//
|
|
// added method
|
|
void idDeviceContext::GetTransformInfo( idVec3& org, idMat3& m )
|
|
{
|
|
m = mat;
|
|
org = origin;
|
|
}
|
|
//
|
|
|
|
void idDeviceContext::EnableClipping( bool b )
|
|
{
|
|
enableClipping = b;
|
|
};
|
|
|
|
void idDeviceContext::PopClipRect()
|
|
{
|
|
if( clipRects.Num() )
|
|
{
|
|
clipRects.RemoveIndex( clipRects.Num() - 1 );
|
|
}
|
|
}
|
|
|
|
void idDeviceContext::PushClipRect( idRectangle r )
|
|
{
|
|
clipRects.Append( r );
|
|
}
|
|
|
|
bool idDeviceContext::ClippedCoords( float* x, float* y, float* w, float* h, float* s1, float* t1, float* s2, float* t2 )
|
|
{
|
|
|
|
if( enableClipping == false || clipRects.Num() == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int c = clipRects.Num();
|
|
while( --c > 0 )
|
|
{
|
|
idRectangle* clipRect = &clipRects[c];
|
|
|
|
float ox = *x;
|
|
float oy = *y;
|
|
float ow = *w;
|
|
float oh = *h;
|
|
|
|
if( ow <= 0.0f || oh <= 0.0f )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if( *x < clipRect->x )
|
|
{
|
|
*w -= clipRect->x - *x;
|
|
*x = clipRect->x;
|
|
}
|
|
else if( *x > clipRect->x + clipRect->w )
|
|
{
|
|
*x = *w = *y = *h = 0;
|
|
}
|
|
if( *y < clipRect->y )
|
|
{
|
|
*h -= clipRect->y - *y;
|
|
*y = clipRect->y;
|
|
}
|
|
else if( *y > clipRect->y + clipRect->h )
|
|
{
|
|
*x = *w = *y = *h = 0;
|
|
}
|
|
if( *w > clipRect->w )
|
|
{
|
|
*w = clipRect->w - *x + clipRect->x;
|
|
}
|
|
else if( *x + *w > clipRect->x + clipRect->w )
|
|
{
|
|
*w = clipRect->Right() - *x;
|
|
}
|
|
if( *h > clipRect->h )
|
|
{
|
|
*h = clipRect->h - *y + clipRect->y;
|
|
}
|
|
else if( *y + *h > clipRect->y + clipRect->h )
|
|
{
|
|
*h = clipRect->Bottom() - *y;
|
|
}
|
|
|
|
if( s1 && s2 && t1 && t2 && ow > 0.0f )
|
|
{
|
|
float ns1, ns2, nt1, nt2;
|
|
// upper left
|
|
float u = ( *x - ox ) / ow;
|
|
ns1 = *s1 * ( 1.0f - u ) + *s2 * ( u );
|
|
|
|
// upper right
|
|
u = ( *x + *w - ox ) / ow;
|
|
ns2 = *s1 * ( 1.0f - u ) + *s2 * ( u );
|
|
|
|
// lower left
|
|
u = ( *y - oy ) / oh;
|
|
nt1 = *t1 * ( 1.0f - u ) + *t2 * ( u );
|
|
|
|
// lower right
|
|
u = ( *y + *h - oy ) / oh;
|
|
nt2 = *t1 * ( 1.0f - u ) + *t2 * ( u );
|
|
|
|
// set values
|
|
*s1 = ns1;
|
|
*s2 = ns2;
|
|
*t1 = nt1;
|
|
*t2 = nt2;
|
|
}
|
|
}
|
|
|
|
return ( *w == 0 || *h == 0 ) ? true : false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
DrawStretchPic
|
|
=============
|
|
*/
|
|
void idDeviceContext::DrawWinding( idWinding& w, const idMaterial* mat )
|
|
{
|
|
|
|
idPlane p;
|
|
|
|
p.Normal().Set( 1.0f, 0.0f, 0.0f );
|
|
p.SetDist( 0.0f );
|
|
w.ClipInPlace( p );
|
|
|
|
p.Normal().Set( -1.0f, 0.0f, 0.0f );
|
|
p.SetDist( -SCREEN_WIDTH );
|
|
w.ClipInPlace( p );
|
|
|
|
p.Normal().Set( 0.0f, 1.0f, 0.0f );
|
|
p.SetDist( 0.0f );
|
|
w.ClipInPlace( p );
|
|
|
|
p.Normal().Set( 0.0f, -1.0f, 0.0f );
|
|
p.SetDist( -SCREEN_HEIGHT );
|
|
w.ClipInPlace( p );
|
|
|
|
if( w.GetNumPoints() < 3 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int numIndexes = 0;
|
|
triIndex_t tempIndexes[( MAX_POINTS_ON_WINDING - 2 ) * 3];
|
|
for( int j = 2; j < w.GetNumPoints(); j++ )
|
|
{
|
|
tempIndexes[numIndexes++] = 0;
|
|
tempIndexes[numIndexes++] = j - 1;
|
|
tempIndexes[numIndexes++] = j;
|
|
}
|
|
assert( numIndexes == ( w.GetNumPoints() - 2 ) * 3 );
|
|
|
|
idDrawVert* verts = renderSystem->AllocTris( w.GetNumPoints(), tempIndexes, numIndexes, mat );
|
|
if( verts == NULL )
|
|
{
|
|
return;
|
|
}
|
|
uint32 currentColor = renderSystem->GetColor();
|
|
|
|
for( int j = 0 ; j < w.GetNumPoints() ; j++ )
|
|
{
|
|
verts[j].xyz.x = xOffset + w[j].x * xScale;
|
|
verts[j].xyz.y = yOffset + w[j].y * yScale;
|
|
verts[j].xyz.z = w[j].z;
|
|
verts[j].SetTexCoord( w[j].s, w[j].t );
|
|
verts[j].SetColor( currentColor );
|
|
verts[j].ClearColor2();
|
|
verts[j].SetNormal( 0.0f, 0.0f, 1.0f );
|
|
verts[j].SetTangent( 1.0f, 0.0f, 0.0f );
|
|
verts[j].SetBiTangent( 0.0f, 1.0f, 0.0f );
|
|
}
|
|
}
|
|
|
|
void idDeviceContext::DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial* shader )
|
|
{
|
|
if( matIsIdentity )
|
|
{
|
|
renderSystem->DrawStretchPic( xOffset + x * xScale, yOffset + y * yScale, w * xScale, h * yScale, s1, t1, s2, t2, shader );
|
|
return;
|
|
}
|
|
|
|
idFixedWinding winding;
|
|
winding.AddPoint( idVec5( x, y, 0.0f, s1, t1 ) );
|
|
winding.AddPoint( idVec5( x + w, y, 0.0f, s2, t1 ) );
|
|
winding.AddPoint( idVec5( x + w, y + h, 0.0f, s2, t2 ) );
|
|
winding.AddPoint( idVec5( x, y + h, 0.0f, s1, t2 ) );
|
|
|
|
for( int i = 0; i < winding.GetNumPoints(); i++ )
|
|
{
|
|
winding[i].ToVec3() -= origin;
|
|
winding[i].ToVec3() *= mat;
|
|
winding[i].ToVec3() += origin;
|
|
}
|
|
|
|
DrawWinding( winding, shader );
|
|
}
|
|
|
|
|
|
void idDeviceContext::DrawMaterial( float x, float y, float w, float h, const idMaterial* mat, const idVec4& color, float scalex, float scaley )
|
|
{
|
|
|
|
renderSystem->SetColor( color );
|
|
|
|
float s0, s1, t0, t1;
|
|
//
|
|
// handle negative scales as well
|
|
if( scalex < 0 )
|
|
{
|
|
w *= -1;
|
|
scalex *= -1;
|
|
}
|
|
if( scaley < 0 )
|
|
{
|
|
h *= -1;
|
|
scaley *= -1;
|
|
}
|
|
//
|
|
if( w < 0 ) // flip about vertical
|
|
{
|
|
w = -w;
|
|
s0 = 1 * scalex;
|
|
s1 = 0;
|
|
}
|
|
else
|
|
{
|
|
s0 = 0;
|
|
s1 = 1 * scalex;
|
|
}
|
|
|
|
if( h < 0 ) // flip about horizontal
|
|
{
|
|
h = -h;
|
|
t0 = 1 * scaley;
|
|
t1 = 0;
|
|
}
|
|
else
|
|
{
|
|
t0 = 0;
|
|
t1 = 1 * scaley;
|
|
}
|
|
|
|
if( ClippedCoords( &x, &y, &w, &h, &s0, &t0, &s1, &t1 ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DrawStretchPic( x, y, w, h, s0, t0, s1, t1, mat );
|
|
}
|
|
|
|
void idDeviceContext::DrawMaterialRotated( float x, float y, float w, float h, const idMaterial* mat, const idVec4& color, float scalex, float scaley, float angle )
|
|
{
|
|
|
|
renderSystem->SetColor( color );
|
|
|
|
float s0, s1, t0, t1;
|
|
//
|
|
// handle negative scales as well
|
|
if( scalex < 0 )
|
|
{
|
|
w *= -1;
|
|
scalex *= -1;
|
|
}
|
|
if( scaley < 0 )
|
|
{
|
|
h *= -1;
|
|
scaley *= -1;
|
|
}
|
|
//
|
|
if( w < 0 ) // flip about vertical
|
|
{
|
|
w = -w;
|
|
s0 = 1 * scalex;
|
|
s1 = 0;
|
|
}
|
|
else
|
|
{
|
|
s0 = 0;
|
|
s1 = 1 * scalex;
|
|
}
|
|
|
|
if( h < 0 ) // flip about horizontal
|
|
{
|
|
h = -h;
|
|
t0 = 1 * scaley;
|
|
t1 = 0;
|
|
}
|
|
else
|
|
{
|
|
t0 = 0;
|
|
t1 = 1 * scaley;
|
|
}
|
|
|
|
if( angle == 0.0f && ClippedCoords( &x, &y, &w, &h, &s0, &t0, &s1, &t1 ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DrawStretchPicRotated( x, y, w, h, s0, t0, s1, t1, mat, angle );
|
|
}
|
|
|
|
void idDeviceContext::DrawStretchPicRotated( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial* shader, float angle )
|
|
{
|
|
|
|
idFixedWinding winding;
|
|
winding.AddPoint( idVec5( x, y, 0.0f, s1, t1 ) );
|
|
winding.AddPoint( idVec5( x + w, y, 0.0f, s2, t1 ) );
|
|
winding.AddPoint( idVec5( x + w, y + h, 0.0f, s2, t2 ) );
|
|
winding.AddPoint( idVec5( x, y + h, 0.0f, s1, t2 ) );
|
|
|
|
for( int i = 0; i < winding.GetNumPoints(); i++ )
|
|
{
|
|
winding[i].ToVec3() -= origin;
|
|
winding[i].ToVec3() *= mat;
|
|
winding[i].ToVec3() += origin;
|
|
}
|
|
|
|
//Generate a translation so we can translate to the center of the image rotate and draw
|
|
idVec3 origTrans;
|
|
origTrans.x = x + ( w / 2 );
|
|
origTrans.y = y + ( h / 2 );
|
|
origTrans.z = 0;
|
|
|
|
|
|
//Rotate the verts about the z axis before drawing them
|
|
idMat3 rotz;
|
|
rotz.Identity();
|
|
float sinAng, cosAng;
|
|
idMath::SinCos( angle, sinAng, cosAng );
|
|
rotz[0][0] = cosAng;
|
|
rotz[0][1] = sinAng;
|
|
rotz[1][0] = -sinAng;
|
|
rotz[1][1] = cosAng;
|
|
for( int i = 0; i < winding.GetNumPoints(); i++ )
|
|
{
|
|
winding[i].ToVec3() -= origTrans;
|
|
winding[i].ToVec3() *= rotz;
|
|
winding[i].ToVec3() += origTrans;
|
|
}
|
|
|
|
DrawWinding( winding, shader );
|
|
}
|
|
|
|
void idDeviceContext::DrawFilledRect( float x, float y, float w, float h, const idVec4& color )
|
|
{
|
|
|
|
if( color.w == 0.0f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
renderSystem->SetColor( color );
|
|
|
|
if( ClippedCoords( &x, &y, &w, &h, NULL, NULL, NULL, NULL ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DrawStretchPic( x, y, w, h, 0, 0, 0, 0, whiteImage );
|
|
}
|
|
|
|
|
|
void idDeviceContext::DrawRect( float x, float y, float w, float h, float size, const idVec4& color )
|
|
{
|
|
|
|
if( color.w == 0.0f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
renderSystem->SetColor( color );
|
|
|
|
if( ClippedCoords( &x, &y, &w, &h, NULL, NULL, NULL, NULL ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DrawStretchPic( x, y, size, h, 0, 0, 0, 0, whiteImage );
|
|
DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, whiteImage );
|
|
DrawStretchPic( x, y, w, size, 0, 0, 0, 0, whiteImage );
|
|
DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, whiteImage );
|
|
}
|
|
|
|
void idDeviceContext::DrawMaterialRect( float x, float y, float w, float h, float size, const idMaterial* mat, const idVec4& color )
|
|
{
|
|
|
|
if( color.w == 0.0f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
renderSystem->SetColor( color );
|
|
DrawMaterial( x, y, size, h, mat, color );
|
|
DrawMaterial( x + w - size, y, size, h, mat, color );
|
|
DrawMaterial( x, y, w, size, mat, color );
|
|
DrawMaterial( x, y + h - size, w, size, mat, color );
|
|
}
|
|
|
|
|
|
void idDeviceContext::SetCursor( int n )
|
|
{
|
|
|
|
if( n > CURSOR_ARROW && n < CURSOR_COUNT )
|
|
{
|
|
|
|
keyBindings_t binds = idKeyInput::KeyBindingsFromBinding( "_use", true );
|
|
|
|
keyNum_t keyNum = K_NONE;
|
|
if( in_useJoystick.GetBool() )
|
|
{
|
|
keyNum = idKeyInput::StringToKeyNum( binds.gamepad.c_str() );
|
|
}
|
|
|
|
if( keyNum != K_NONE )
|
|
{
|
|
|
|
if( keyNum == K_JOY1 )
|
|
{
|
|
cursor = CURSOR_HAND_JOY1;
|
|
}
|
|
else if( keyNum == K_JOY2 )
|
|
{
|
|
cursor = CURSOR_HAND_JOY2;
|
|
}
|
|
else if( keyNum == K_JOY3 )
|
|
{
|
|
cursor = CURSOR_HAND_JOY3;
|
|
}
|
|
else if( keyNum == K_JOY4 )
|
|
{
|
|
cursor = CURSOR_HAND_JOY4;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
cursor = CURSOR_HAND;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
cursor = CURSOR_ARROW;
|
|
}
|
|
}
|
|
|
|
void idDeviceContext::DrawCursor( float* x, float* y, float size )
|
|
{
|
|
if( *x < 0 )
|
|
{
|
|
*x = 0;
|
|
}
|
|
|
|
if( *x >= VIRTUAL_WIDTH )
|
|
{
|
|
*x = VIRTUAL_WIDTH;
|
|
}
|
|
|
|
if( *y < 0 )
|
|
{
|
|
*y = 0;
|
|
}
|
|
|
|
if( *y >= VIRTUAL_HEIGHT )
|
|
{
|
|
*y = VIRTUAL_HEIGHT;
|
|
}
|
|
|
|
renderSystem->SetColor( colorWhite );
|
|
DrawStretchPic( *x, *y, size, size, 0, 0, 1, 1, cursorImages[cursor] );
|
|
}
|
|
/*
|
|
=======================================================================================================================
|
|
=======================================================================================================================
|
|
*/
|
|
|
|
void idDeviceContext::PaintChar( float x, float y, const scaledGlyphInfo_t& glyphInfo )
|
|
{
|
|
y -= glyphInfo.top;
|
|
x += glyphInfo.left;
|
|
|
|
float w = glyphInfo.width;
|
|
float h = glyphInfo.height;
|
|
float s = glyphInfo.s1;
|
|
float t = glyphInfo.t1;
|
|
float s2 = glyphInfo.s2;
|
|
float t2 = glyphInfo.t2;
|
|
const idMaterial* hShader = glyphInfo.material;
|
|
|
|
if( ClippedCoords( &x, &y, &w, &h, &s, &t, &s2, &t2 ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader );
|
|
}
|
|
|
|
int idDeviceContext::DrawText( float x, float y, float scale, idVec4 color, const char* text, float adjust, int limit, int style, int cursor )
|
|
{
|
|
int len;
|
|
idVec4 newColor;
|
|
|
|
idStr drawText = text;
|
|
int charIndex = 0;
|
|
|
|
if( text && color.w != 0.0f )
|
|
{
|
|
renderSystem->SetColor( color );
|
|
memcpy( &newColor[0], &color[0], sizeof( idVec4 ) );
|
|
len = drawText.Length();
|
|
if( limit > 0 && len > limit )
|
|
{
|
|
len = limit;
|
|
}
|
|
|
|
float prevGlyphSkip = 0.0f;
|
|
|
|
while( charIndex < len )
|
|
{
|
|
uint32 textChar = drawText.UTF8Char( charIndex );
|
|
|
|
if( idStr::IsColor( drawText.c_str() + charIndex ) )
|
|
{
|
|
if( drawText[ charIndex++ ] == C_COLOR_DEFAULT )
|
|
{
|
|
newColor = color;
|
|
}
|
|
else
|
|
{
|
|
newColor = idStr::ColorForIndex( charIndex );
|
|
newColor[3] = color[3];
|
|
}
|
|
if( cursor == charIndex - 1 || cursor == charIndex )
|
|
{
|
|
float backup = 0.0f;
|
|
if( prevGlyphSkip > 0.0f )
|
|
{
|
|
backup = ( prevGlyphSkip + adjust ) / 5.0f;
|
|
}
|
|
if( cursor == charIndex - 1 )
|
|
{
|
|
backup *= 2.0f;
|
|
}
|
|
else
|
|
{
|
|
renderSystem->SetColor( newColor );
|
|
}
|
|
DrawEditCursor( x - backup, y, scale );
|
|
}
|
|
renderSystem->SetColor( newColor );
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
scaledGlyphInfo_t glyphInfo;
|
|
activeFont->GetScaledGlyph( scale, textChar, glyphInfo );
|
|
prevGlyphSkip = glyphInfo.xSkip;
|
|
|
|
PaintChar( x, y, glyphInfo );
|
|
|
|
if( cursor == charIndex - 1 )
|
|
{
|
|
DrawEditCursor( x, y, scale );
|
|
}
|
|
x += glyphInfo.xSkip + adjust;
|
|
}
|
|
}
|
|
if( cursor == len )
|
|
{
|
|
DrawEditCursor( x, y, scale );
|
|
}
|
|
}
|
|
return drawText.Length();
|
|
}
|
|
|
|
void idDeviceContext::SetSize( float width, float height )
|
|
{
|
|
xScale = VIRTUAL_WIDTH / width;
|
|
yScale = VIRTUAL_HEIGHT / height;
|
|
}
|
|
|
|
void idDeviceContext::SetOffset( float x, float y )
|
|
{
|
|
xOffset = x;
|
|
yOffset = y;
|
|
}
|
|
|
|
int idDeviceContext::CharWidth( const char c, float scale )
|
|
{
|
|
return idMath::Ftoi( activeFont->GetGlyphWidth( scale, c ) );
|
|
}
|
|
|
|
int idDeviceContext::TextWidth( const char* text, float scale, int limit )
|
|
{
|
|
if( text == NULL )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int i;
|
|
float width = 0;
|
|
if( limit > 0 )
|
|
{
|
|
for( i = 0; text[i] != '\0' && i < limit; i++ )
|
|
{
|
|
if( idStr::IsColor( text + i ) )
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
width += activeFont->GetGlyphWidth( scale, ( ( const unsigned char* )text )[i] );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( i = 0; text[i] != '\0'; i++ )
|
|
{
|
|
if( idStr::IsColor( text + i ) )
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
width += activeFont->GetGlyphWidth( scale, ( ( const unsigned char* )text )[i] );
|
|
}
|
|
}
|
|
}
|
|
return idMath::Ftoi( width );
|
|
}
|
|
|
|
int idDeviceContext::TextHeight( const char* text, float scale, int limit )
|
|
{
|
|
return idMath::Ftoi( activeFont->GetLineHeight( scale ) );
|
|
}
|
|
|
|
int idDeviceContext::MaxCharWidth( float scale )
|
|
{
|
|
return idMath::Ftoi( activeFont->GetMaxCharWidth( scale ) );
|
|
}
|
|
|
|
int idDeviceContext::MaxCharHeight( float scale )
|
|
{
|
|
return idMath::Ftoi( activeFont->GetLineHeight( scale ) );
|
|
}
|
|
|
|
const idMaterial* idDeviceContext::GetScrollBarImage( int index )
|
|
{
|
|
if( index >= SCROLLBAR_HBACK && index < SCROLLBAR_COUNT )
|
|
{
|
|
return scrollBarImages[index];
|
|
}
|
|
return scrollBarImages[SCROLLBAR_HBACK];
|
|
}
|
|
|
|
// this only supports left aligned text
|
|
idRegion* idDeviceContext::GetTextRegion( const char* text, float textScale, idRectangle rectDraw, float xStart, float yStart )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void idDeviceContext::DrawEditCursor( float x, float y, float scale )
|
|
{
|
|
if( ( int )( idLib::frameNumber >> 4 ) & 1 )
|
|
{
|
|
return;
|
|
}
|
|
char cursorChar = ( overStrikeMode ) ? '_' : '|';
|
|
scaledGlyphInfo_t glyphInfo;
|
|
activeFont->GetScaledGlyph( scale, cursorChar, glyphInfo );
|
|
PaintChar( x, y, glyphInfo );
|
|
}
|
|
|
|
int idDeviceContext::DrawText( const char* text, float textScale, int textAlign, idVec4 color, idRectangle rectDraw, bool wrap, int cursor, bool calcOnly, idList<int>* breaks, int limit )
|
|
{
|
|
int count = 0;
|
|
int charIndex = 0;
|
|
int lastBreak = 0;
|
|
float y = 0.0f;
|
|
float textWidth = 0.0f;
|
|
float textWidthAtLastBreak = 0.0f;
|
|
|
|
float charSkip = MaxCharWidth( textScale ) + 1;
|
|
float lineSkip = MaxCharHeight( textScale );
|
|
|
|
bool lineBreak = false;
|
|
bool wordBreak = false;
|
|
|
|
idStr drawText = text;
|
|
idStr textBuffer;
|
|
|
|
if( !calcOnly && !( text && *text ) )
|
|
{
|
|
if( cursor == 0 )
|
|
{
|
|
renderSystem->SetColor( color );
|
|
DrawEditCursor( rectDraw.x, lineSkip + rectDraw.y, textScale );
|
|
}
|
|
return idMath::Ftoi( rectDraw.w / charSkip );
|
|
}
|
|
|
|
y = lineSkip + rectDraw.y;
|
|
|
|
if( breaks )
|
|
{
|
|
breaks->Append( 0 );
|
|
}
|
|
|
|
while( charIndex < drawText.Length() )
|
|
{
|
|
uint32 textChar = drawText.UTF8Char( charIndex );
|
|
|
|
// See if we need to start a new line.
|
|
if( textChar == '\n' || textChar == '\r' || charIndex == drawText.Length() )
|
|
{
|
|
lineBreak = true;
|
|
if( charIndex < drawText.Length() )
|
|
{
|
|
// New line character and we still have more text to read.
|
|
char nextChar = drawText[ charIndex + 1 ];
|
|
if( ( textChar == '\n' && nextChar == '\r' ) || ( textChar == '\r' && nextChar == '\n' ) )
|
|
{
|
|
// Just absorb extra newlines.
|
|
textChar = drawText.UTF8Char( charIndex );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for escape colors if not then simply get the glyph width.
|
|
if( textChar == C_COLOR_ESCAPE && charIndex < drawText.Length() )
|
|
{
|
|
textBuffer.AppendUTF8Char( textChar );
|
|
textChar = drawText.UTF8Char( charIndex );
|
|
}
|
|
|
|
// If the character isn't a new line then add it to the text buffer.
|
|
if( textChar != '\n' && textChar != '\r' )
|
|
{
|
|
textWidth += activeFont->GetGlyphWidth( textScale, textChar );
|
|
textBuffer.AppendUTF8Char( textChar );
|
|
}
|
|
|
|
if( !lineBreak && ( textWidth > rectDraw.w ) )
|
|
{
|
|
// The next character will cause us to overflow, if we haven't yet found a suitable
|
|
// break spot, set it to be this character
|
|
if( textBuffer.Length() > 0 && lastBreak == 0 )
|
|
{
|
|
lastBreak = textBuffer.Length();
|
|
textWidthAtLastBreak = textWidth;
|
|
}
|
|
wordBreak = true;
|
|
}
|
|
else if( lineBreak || ( wrap && ( textChar == ' ' || textChar == '\t' ) ) )
|
|
{
|
|
// The next character is in view, so if we are a break character, store our position
|
|
lastBreak = textBuffer.Length();
|
|
textWidthAtLastBreak = textWidth;
|
|
}
|
|
|
|
// We need to go to a new line
|
|
if( lineBreak || wordBreak )
|
|
{
|
|
float x = rectDraw.x;
|
|
|
|
if( textWidthAtLastBreak > 0 )
|
|
{
|
|
textWidth = textWidthAtLastBreak;
|
|
}
|
|
|
|
// Align text if needed
|
|
if( textAlign == ALIGN_RIGHT )
|
|
{
|
|
x = rectDraw.x + rectDraw.w - textWidth;
|
|
}
|
|
else if( textAlign == ALIGN_CENTER )
|
|
{
|
|
x = rectDraw.x + ( rectDraw.w - textWidth ) / 2;
|
|
}
|
|
|
|
if( wrap || lastBreak > 0 )
|
|
{
|
|
// This is a special case to handle breaking in the middle of a word.
|
|
// if we didn't do this, the cursor would appear on the end of this line
|
|
// and the beginning of the next.
|
|
if( wordBreak && cursor >= lastBreak && lastBreak == textBuffer.Length() )
|
|
{
|
|
cursor++;
|
|
}
|
|
}
|
|
|
|
// Draw what's in the current text buffer.
|
|
if( !calcOnly )
|
|
{
|
|
if( lastBreak > 0 )
|
|
{
|
|
count += DrawText( x, y, textScale, color, textBuffer.Left( lastBreak ).c_str(), 0, 0, 0, cursor );
|
|
textBuffer = textBuffer.Right( textBuffer.Length() - lastBreak );
|
|
}
|
|
else
|
|
{
|
|
count += DrawText( x, y, textScale, color, textBuffer.c_str(), 0, 0, 0, cursor );
|
|
textBuffer.Clear();
|
|
}
|
|
}
|
|
|
|
if( cursor < lastBreak )
|
|
{
|
|
cursor = -1;
|
|
}
|
|
else if( cursor >= 0 )
|
|
{
|
|
cursor -= ( lastBreak + 1 );
|
|
}
|
|
|
|
// If wrap is disabled return at this point.
|
|
if( !wrap )
|
|
{
|
|
return lastBreak;
|
|
}
|
|
|
|
// If we've hit the allowed character limit then break.
|
|
if( limit && count > limit )
|
|
{
|
|
break;
|
|
}
|
|
|
|
y += lineSkip + 5;
|
|
|
|
if( !calcOnly && y > rectDraw.Bottom() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If breaks were requested then make a note of this one.
|
|
if( breaks )
|
|
{
|
|
breaks->Append( drawText.Length() - charIndex );
|
|
}
|
|
|
|
// Reset necessary parms for next line.
|
|
lastBreak = 0;
|
|
textWidth = 0;
|
|
textWidthAtLastBreak = 0;
|
|
lineBreak = false;
|
|
wordBreak = false;
|
|
|
|
// Reassess the remaining width
|
|
for( int i = 0; i < textBuffer.Length(); )
|
|
{
|
|
if( textChar != C_COLOR_ESCAPE )
|
|
{
|
|
textWidth += activeFont->GetGlyphWidth( textScale, textBuffer.UTF8Char( i ) );
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return idMath::Ftoi( rectDraw.w / charSkip );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idRectangle::String
|
|
=============
|
|
*/
|
|
char* idRectangle::String() const
|
|
{
|
|
static int index = 0;
|
|
static char str[ 8 ][ 48 ];
|
|
char* s;
|
|
|
|
// use an array so that multiple toString's won't collide
|
|
s = str[ index ];
|
|
index = ( index + 1 ) & 7;
|
|
|
|
sprintf( s, "%.2f %.2f %.2f %.2f", x, y, w, h );
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
/*
|
|
================================================================================================
|
|
|
|
OPTIMIZED VERSIONS
|
|
|
|
================================================================================================
|
|
*/
|
|
|
|
// this is only called for the cursor and debug strings, and it should
|
|
// scope properly with push/pop clipRect
|
|
void idDeviceContextOptimized::EnableClipping( bool b )
|
|
{
|
|
if( b == enableClipping )
|
|
{
|
|
return;
|
|
}
|
|
enableClipping = b;
|
|
if( !enableClipping )
|
|
{
|
|
PopClipRect();
|
|
}
|
|
else
|
|
{
|
|
// the actual value of the rect is irrelvent
|
|
PushClipRect( idRectangle( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ) );
|
|
|
|
// allow drawing beyond the normal bounds for debug text
|
|
// this also allows the cursor to draw outside, so we might want
|
|
// to make this exactly the screen bounds, since we aren't likely
|
|
// to ever turn on the gui debug text again...
|
|
clipX1 = -SCREEN_WIDTH;
|
|
clipX2 = SCREEN_WIDTH * 2;
|
|
clipY1 = -SCREEN_HEIGHT;
|
|
clipY2 = SCREEN_HEIGHT * 2;
|
|
}
|
|
};
|
|
|
|
|
|
void idDeviceContextOptimized::PopClipRect()
|
|
{
|
|
if( clipRects.Num() )
|
|
{
|
|
clipRects.SetNum( clipRects.Num() - 1 ); // don't resize the list, just change num
|
|
}
|
|
if( clipRects.Num() > 0 )
|
|
{
|
|
const idRectangle& clipRect = clipRects[ clipRects.Num() - 1 ];
|
|
clipX1 = clipRect.x;
|
|
clipY1 = clipRect.y;
|
|
clipX2 = clipRect.x + clipRect.w;
|
|
clipY2 = clipRect.y + clipRect.h;
|
|
}
|
|
else
|
|
{
|
|
clipX1 = 0;
|
|
clipY1 = 0;
|
|
clipX2 = SCREEN_WIDTH;
|
|
clipY2 = SCREEN_HEIGHT;
|
|
}
|
|
}
|
|
|
|
static const idRectangle baseScreenRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT );
|
|
|
|
void idDeviceContextOptimized::PushClipRect( idRectangle r )
|
|
{
|
|
const idRectangle& prev = ( clipRects.Num() == 0 ) ? baseScreenRect : clipRects[clipRects.Num() - 1];
|
|
|
|
// instead of storing the rect, store the intersection of the rect
|
|
// with the previous rect, so ClippedCoords() only has to test against one rect
|
|
idRectangle intersection = prev;
|
|
intersection.ClipAgainst( r, false );
|
|
clipRects.Append( intersection );
|
|
|
|
const idRectangle& clipRect = clipRects[ clipRects.Num() - 1 ];
|
|
clipX1 = clipRect.x;
|
|
clipY1 = clipRect.y;
|
|
clipX2 = clipRect.x + clipRect.w;
|
|
clipY2 = clipRect.y + clipRect.h;
|
|
}
|
|
|
|
bool idDeviceContextOptimized::ClippedCoords( float* x, float* y, float* w, float* h, float* s1, float* t1, float* s2, float* t2 )
|
|
{
|
|
const float ox = *x;
|
|
const float oy = *y;
|
|
const float ow = *w;
|
|
const float oh = *h;
|
|
|
|
// presume visible first
|
|
if( ox >= clipX1 && oy >= clipY1 && ox + ow <= clipX2 && oy + oh <= clipY2 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// do clipping
|
|
if( ox < clipX1 )
|
|
{
|
|
*w -= clipX1 - ox;
|
|
*x = clipX1;
|
|
}
|
|
else if( ox > clipX2 )
|
|
{
|
|
return true;
|
|
}
|
|
if( oy < clipY1 )
|
|
{
|
|
*h -= clipY1 - oy;
|
|
*y = clipY1;
|
|
}
|
|
else if( oy > clipY2 )
|
|
{
|
|
return true;
|
|
}
|
|
if( *x + *w > clipX2 )
|
|
{
|
|
*w = clipX2 - *x;
|
|
}
|
|
if( *y + *h > clipY2 )
|
|
{
|
|
*h = clipY2 - *y;
|
|
}
|
|
|
|
if( *w <= 0 || *h <= 0 )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// filled rects won't pass in texcoords
|
|
if( s1 )
|
|
{
|
|
float ns1, ns2, nt1, nt2;
|
|
// upper left
|
|
float u = ( *x - ox ) / ow;
|
|
ns1 = *s1 * ( 1.0f - u ) + *s2 * ( u );
|
|
|
|
// upper right
|
|
u = ( *x + *w - ox ) / ow;
|
|
ns2 = *s1 * ( 1.0f - u ) + *s2 * ( u );
|
|
|
|
// lower left
|
|
u = ( *y - oy ) / oh;
|
|
nt1 = *t1 * ( 1.0f - u ) + *t2 * ( u );
|
|
|
|
// lower right
|
|
u = ( *y + *h - oy ) / oh;
|
|
nt2 = *t1 * ( 1.0f - u ) + *t2 * ( u );
|
|
|
|
// set values
|
|
*s1 = ns1;
|
|
*s2 = ns2;
|
|
*t1 = nt1;
|
|
*t2 = nt2;
|
|
}
|
|
|
|
// still needs to be drawn
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idDeviceContextOptimized::DrawText
|
|
=============
|
|
*/
|
|
static triIndex_t quadPicIndexes[6] = { 3, 0, 2, 2, 0, 1 };
|
|
int idDeviceContextOptimized::DrawText( float x, float y, float scale, idVec4 color, const char* text, float adjust, int limit, int style, int cursor )
|
|
{
|
|
if( !matIsIdentity || cursor != -1 )
|
|
{
|
|
// fallback to old code
|
|
return idDeviceContext::DrawText( x, y, scale, color, text, adjust, limit, style, cursor );
|
|
}
|
|
|
|
idStr drawText = text;
|
|
|
|
if( drawText.Length() == 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
if( color.w == 0.0f )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const uint32 currentColor = PackColor( color );
|
|
uint32 currentColorNativeByteOrder = LittleLong( currentColor );
|
|
|
|
int len = drawText.Length();
|
|
if( limit > 0 && len > limit )
|
|
{
|
|
len = limit;
|
|
}
|
|
|
|
int charIndex = 0;
|
|
while( charIndex < drawText.Length() )
|
|
{
|
|
uint32 textChar = drawText.UTF8Char( charIndex );
|
|
if( textChar == C_COLOR_ESCAPE )
|
|
{
|
|
// I'm not sure if inline text color codes are used anywhere in the game,
|
|
// they may only be needed for multi-color user names
|
|
idVec4 newColor;
|
|
uint32 colorIndex = drawText.UTF8Char( charIndex );
|
|
if( colorIndex == C_COLOR_DEFAULT )
|
|
{
|
|
newColor = color;
|
|
}
|
|
else
|
|
{
|
|
newColor = idStr::ColorForIndex( colorIndex );
|
|
newColor[3] = color[3];
|
|
}
|
|
renderSystem->SetColor( newColor );
|
|
currentColorNativeByteOrder = LittleLong( PackColor( newColor ) );
|
|
continue;
|
|
}
|
|
|
|
scaledGlyphInfo_t glyphInfo;
|
|
activeFont->GetScaledGlyph( scale, textChar, glyphInfo );
|
|
|
|
// PaintChar( x, y, glyphInfo );
|
|
float drawY = y - glyphInfo.top;
|
|
float drawX = x + glyphInfo.left;
|
|
float w = glyphInfo.width;
|
|
float h = glyphInfo.height;
|
|
float s = glyphInfo.s1;
|
|
float t = glyphInfo.t1;
|
|
float s2 = glyphInfo.s2;
|
|
float t2 = glyphInfo.t2;
|
|
|
|
if( !ClippedCoords( &drawX, &drawY, &w, &h, &s, &t, &s2, &t2 ) )
|
|
{
|
|
float x1 = xOffset + drawX * xScale;
|
|
float x2 = xOffset + ( drawX + w ) * xScale;
|
|
float y1 = yOffset + drawY * yScale;
|
|
float y2 = yOffset + ( drawY + h ) * yScale;
|
|
idDrawVert* verts = tr_guiModel->AllocTris( 4, quadPicIndexes, 6, glyphInfo.material, 0, STEREO_DEPTH_TYPE_NONE );
|
|
if( verts != NULL )
|
|
{
|
|
verts[0].xyz[0] = x1;
|
|
verts[0].xyz[1] = y1;
|
|
verts[0].xyz[2] = 0.0f;
|
|
verts[0].SetTexCoord( s, t );
|
|
verts[0].SetNativeOrderColor( currentColorNativeByteOrder );
|
|
verts[0].ClearColor2();
|
|
|
|
verts[1].xyz[0] = x2;
|
|
verts[1].xyz[1] = y1;
|
|
verts[1].xyz[2] = 0.0f;
|
|
verts[1].SetTexCoord( s2, t );
|
|
verts[1].SetNativeOrderColor( currentColorNativeByteOrder );
|
|
verts[1].ClearColor2();
|
|
|
|
verts[2].xyz[0] = x2;
|
|
verts[2].xyz[1] = y2;
|
|
verts[2].xyz[2] = 0.0f;
|
|
verts[2].SetTexCoord( s2, t2 );
|
|
verts[2].SetNativeOrderColor( currentColorNativeByteOrder );
|
|
verts[2].ClearColor2();
|
|
|
|
verts[3].xyz[0] = x1;
|
|
verts[3].xyz[1] = y2;
|
|
verts[3].xyz[2] = 0.0f;
|
|
verts[3].SetTexCoord( s, t2 );
|
|
verts[3].SetNativeOrderColor( currentColorNativeByteOrder );
|
|
verts[3].ClearColor2();
|
|
}
|
|
}
|
|
|
|
x += glyphInfo.xSkip + adjust;
|
|
}
|
|
return drawText.Length();
|
|
}
|