openmohaa/code/cgame/cg_view.c
smallmodel ef2c5d2df6
Some checks failed
CodeQL / Analyze (push) Waiting to run
Build branch / build-all (push) Failing after 16s
Check and verify that selected player models are valid
If the player model or the player german model is not valid, they will be reset to the default value. If the server doesn't have one or more of these models, force model feature will not work
2025-01-22 20:52:05 +01:00

1015 lines
32 KiB
C

/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA 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 2 of the License,
or (at your option) any later version.
OpenMoHAA 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "cg_local.h"
#include "cg_parsemsg.h"
//============================================================================
/*
=================
CG_CalcVrect
Sets the coordinates of the rendered window
=================
*/
static void CG_CalcVrect(void)
{
int size;
// the intermission should allways be full screen
if (cg.snap->ps.pm_flags & PMF_INTERMISSION) {
size = 100;
} else {
// bound normal viewsize
if (cg_viewsize->integer < 30) {
cgi.Cvar_Set("viewsize", "30");
size = 30;
} else if (cg_viewsize->integer > 100) {
cgi.Cvar_Set("viewsize", "100");
size = 100;
} else {
size = cg_viewsize->integer;
}
}
cg.refdef.width = cgs.glconfig.vidWidth * size / 100;
cg.refdef.width &= ~1;
cg.refdef.height = cgs.glconfig.vidHeight * size / 100;
cg.refdef.height &= ~1;
cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width) / 2;
cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height) / 2;
}
//==============================================================================
/*
===============
CG_OffsetThirdPersonView
===============
*/
#define CAMERA_MINIMUM_DISTANCE 40
static void CG_OffsetThirdPersonView(void)
{
vec3_t forward;
vec3_t original_camera_position;
vec3_t new_vieworg;
trace_t trace;
vec3_t min, max;
float *look_offset;
float *target_angles;
float *target_position;
vec3_t delta;
vec3_t original_angles;
qboolean lookactive, resetview;
static vec3_t saved_look_offset;
vec3_t camera_offset;
target_angles = cg.refdefViewAngles;
target_position = cg.refdef.vieworg;
// see if angles are absolute
if (cg.predicted_player_state.camera_flags & CF_CAMERA_ANGLES_ABSOLUTE) {
VectorClear(target_angles);
}
// see if we need to ignore yaw
if (cg.predicted_player_state.camera_flags & CF_CAMERA_ANGLES_IGNORE_YAW) {
target_angles[YAW] = 0;
}
// see if we need to ignore pitch
if (cg.predicted_player_state.camera_flags & CF_CAMERA_ANGLES_IGNORE_PITCH) {
target_angles[PITCH] = 0;
}
// offset the current angles by the camera offset
VectorSubtract(target_angles, cg.predicted_player_state.camera_offset, target_angles);
// Get the position of the camera after any needed rotation
look_offset = cgi.get_camera_offset(&lookactive, &resetview);
if ((!resetview) && ((cg.predicted_player_state.camera_flags & CF_CAMERA_ANGLES_ALLOWOFFSET) || (lookactive))) {
VectorSubtract(look_offset, saved_look_offset, camera_offset);
VectorAdd(target_angles, camera_offset, target_angles);
if (target_angles[PITCH] > 90) {
target_angles[PITCH] = 90;
} else if (target_angles[PITCH] < -90) {
target_angles[PITCH] = -90;
}
} else {
VectorCopy(look_offset, saved_look_offset);
}
target_angles[YAW] = AngleNormalize360(target_angles[YAW]);
target_angles[PITCH] = AngleNormalize180(target_angles[PITCH]);
// Move reference point up
target_position[2] += cg_cameraheight->value;
VectorCopy(target_position, original_camera_position);
// Move camera back from reference point
AngleVectors(target_angles, forward, NULL, NULL);
VectorMA(target_position, -cg_cameradist->value, forward, new_vieworg);
new_vieworg[2] += cg_cameraverticaldisplacement->value;
// Create a bounding box for our camera
min[0] = -5;
min[1] = -5;
min[2] = -5;
max[0] = 5;
max[1] = 5;
max[2] = 5;
// Make sure camera does not collide with anything
CG_Trace(&trace, cg.playerHeadPos, min, max, new_vieworg, 0, MASK_CAMERASOLID, qfalse, qtrue, "ThirdPersonTrace 1");
VectorCopy(trace.endpos, target_position);
// calculate distance from end position to head position
VectorSubtract(target_position, cg.playerHeadPos, delta);
// kill any negative z difference in delta
if (delta[2] < CAMERA_MINIMUM_DISTANCE) {
delta[2] = 0;
}
if (VectorLength(delta) < CAMERA_MINIMUM_DISTANCE) {
VectorNormalize(delta);
/*
// see if we are going straight up
if ( ( delta[ 2 ] > 0.75 ) && ( height > 0.85f * cg.predicted_player_state.viewheight ) )
{
// we just need to lower our start position slightly, since we are on top of the player
new_vieworg[ 2 ] -= 16;
CG_Trace(&trace, cg.playerHeadPos, min, max, new_vieworg, 0, MASK_CAMERASOLID, qfalse, true, "ThirdPersonTrace 2" );
VectorCopy(trace.endpos, target_position);
}
else
*/
{
// we are probably up against the wall so we want the camera to pitch up on top of the player
// save off the original angles
VectorCopy(target_angles, original_angles);
// start cranking up the target angles, pitch until we are the correct distance away from the player
while (target_angles[PITCH] < 90) {
target_angles[PITCH] += 2;
AngleVectors(target_angles, forward, NULL, NULL);
VectorMA(original_camera_position, -cg_cameradist->value, forward, new_vieworg);
new_vieworg[2] += cg_cameraverticaldisplacement->value;
CG_Trace(
&trace,
cg.playerHeadPos,
min,
max,
new_vieworg,
0,
MASK_CAMERASOLID,
qfalse,
qtrue,
"ThirdPersonTrace 3"
);
VectorCopy(trace.endpos, target_position);
// calculate distance from end position to head position
VectorSubtract(target_position, cg.playerHeadPos, delta);
// kill any negative z difference in delta
if (delta[2] < 0) {
delta[2] = 0;
}
if (VectorLength(delta) >= CAMERA_MINIMUM_DISTANCE) {
target_angles[PITCH] = (0.25f * target_angles[PITCH]) + (0.75f * original_angles[PITCH]);
// set the pitch to be that of the angle we are currently looking
//target_angles[ PITCH ] = original_angles[ PITCH ];
break;
}
}
if (target_angles[PITCH] > 90) {
// if we failed, go with the original angles
target_angles[PITCH] = original_angles[PITCH];
}
}
}
}
/*
===============
CG_OffsetFirstPersonView
===============
*/
void CG_OffsetFirstPersonView(refEntity_t *pREnt, qboolean bUseWorldPosition)
{
float *origin;
centity_t *pCent;
dtiki_t *tiki;
int iTag;
int i;
int iMask;
vec3_t vDelta;
float mat[3][3];
vec3_t vOldOrigin;
vec3_t vStart, vEnd, vMins, vMaxs;
vec3_t vVelocity;
trace_t trace;
VectorSet(vMins, -6, -6, -6);
VectorSet(vMaxs, 6, 6, 6);
//
//
//
//
origin = cg.refdef.vieworg;
pCent = &cg_entities[cg.predicted_player_state.clientNum];
tiki = cgi.R_Model_GetHandle(cgs.model_draw[pCent->currentState.modelindex]);
iTag = cgi.Tag_NumForName(tiki, "eyes bone");
if (iTag != -1) {
if (bUseWorldPosition) {
orientation_t oHead;
float mat3[3][3];
vec3_t vHeadAng, vDelta;
VectorCopy(pCent->lerpOrigin, origin);
AxisCopy(pREnt->axis, mat);
oHead = cgi.TIKI_Orientation(pREnt, iTag);
for (i = 0; i < 3; i++) {
VectorMA(origin, oHead.origin[i], mat[i], origin);
}
R_ConcatRotations(oHead.axis, mat, mat3);
MatrixToEulerAngles(mat3, vHeadAng);
AnglesSubtract(vHeadAng, cg.refdefViewAngles, vDelta);
VectorMA(cg.refdefViewAngles, cg.fEyeOffsetFrac, vDelta, cg.refdefViewAngles);
VectorCopy(vHeadAng, cg.refdefViewAngles);
} else {
orientation_t oHead;
vec3_t vHeadAng;
VectorCopy(pCent->lerpOrigin, origin);
AxisCopy(pREnt->axis, mat);
MatrixToEulerAngles(mat, vHeadAng);
oHead = cgi.TIKI_Orientation(pREnt, iTag);
for (i = 0; i < 3; i++) {
VectorMA(origin, oHead.origin[i], mat[i], origin);
}
cg.refdefViewAngles[2] += cg.predicted_player_state.fLeanAngle * 0.3;
}
} else {
cgi.DPrintf("CG_OffsetFirstPersonView warning: Couldn't find 'eyes bone' for player\n");
}
VectorCopy(origin, vOldOrigin);
if (!cg.predicted_player_state.walking || (!(cg.predicted_player_state.pm_flags & PMF_FROZEN) && !(cg.predicted_player_state.pm_flags & PMF_NO_MOVE))) {
VectorCopy(cg.predicted_player_state.velocity, vVelocity);
} else {
//
// Added in OPM
// When frozen, there must be no movement at all
VectorClear(vVelocity);
}
if (bUseWorldPosition) {
iMask = MASK_VIEWSOLID;
} else {
float fTargHeight;
float fHeightDelta, fHeightChange;
float fPhase, fVel;
vec3_t vDelta;
vec3_t vPivotPoint;
vec3_t vForward, vLeft;
origin[0] = cg.predicted_player_state.origin[0];
origin[1] = cg.predicted_player_state.origin[1];
fTargHeight = cg.predicted_player_state.origin[2] + cg.predicted_player_state.viewheight;
fHeightDelta = fTargHeight - cg.fCurrentViewHeight;
if (fabs(fHeightDelta) < 0.1 || !cg.fCurrentViewHeight) {
cg.fCurrentViewHeight = fTargHeight;
} else {
if (fHeightDelta > 32.f) {
fHeightDelta = 32.f;
cg.fCurrentViewHeight = fTargHeight - 32.0;
} else if (fHeightDelta < -32.f) {
fHeightDelta = -32.f;
cg.fCurrentViewHeight = fTargHeight + 32.0;
}
fHeightChange = cg.frametime / 1000.0 * fHeightDelta * 12.5;
if (!cg.predicted_player_state.walking) {
fHeightChange += fHeightChange;
}
if (fabs(fHeightDelta) < fabs(fHeightChange)) {
fHeightChange = fHeightDelta;
}
cg.fCurrentViewHeight += fHeightChange;
}
origin[2] = cg.fCurrentViewHeight;
vPivotPoint[0] = cg.refdefViewAngles[0];
vPivotPoint[1] = cg.refdefViewAngles[1];
vPivotPoint[2] = 0.0;
AngleVectorsLeft(vPivotPoint, vForward, vLeft, NULL);
VectorCopy(origin, vStart);
if (cg.predicted_player_state.pm_type != PM_CLIMBWALL) {
if (cg.refdefViewAngles[0] > 0.0) {
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.4;
} else {
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.2;
}
} else {
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.15;
}
VectorSubtract(origin, vStart, vDelta);
RotatePointAroundVector(vEnd, vLeft, vDelta, cg.refdefViewAngles[0] * 0.4);
VectorAdd(vStart, vEnd, origin);
if (cg.predicted_player_state.fLeanAngle) {
VectorCopy(origin, vStart);
vStart[2] -= 28.7f;
VectorSubtract(origin, vStart, vDelta);
RotatePointAroundVector(vEnd, vForward, vDelta, cg.predicted_player_state.fLeanAngle);
VectorAdd(vStart, vEnd, origin);
}
if (cg.predicted_player_state.walking) {
fVel = VectorLength(vVelocity);
fPhase = fVel * 0.0015 + 0.9;
cg.fCurrentViewBobPhase += (cg.frametime / 1000.0 + cg.frametime / 1000.0) * M_PI * fPhase;
if (cg.fCurrentViewBobAmp) {
cg.fCurrentViewBobAmp = fVel;
} else {
cg.fCurrentViewBobAmp = fVel * 0.5;
}
if (cg.predicted_player_state.fLeanAngle != 0.0) {
cg.fCurrentViewBobAmp *= 0.75;
}
cg.fCurrentViewBobAmp *= (1.0 - fabs(cg.refdefViewAngles[0]) * (1.0 / 90.0) * 0.5) * 0.5;
} else if (cg.fCurrentViewBobAmp > 0.0) {
cg.fCurrentViewBobAmp -=
(cg.frametime / 1000.0 * cg.fCurrentViewBobAmp) + (cg.frametime / 1000.0 * cg.fCurrentViewBobAmp);
if (cg.fCurrentViewBobAmp < 0.1) {
cg.fCurrentViewBobAmp = 0.0;
}
}
if (cg.fCurrentViewBobAmp > 0.0) {
fPhase = sin(cg.fCurrentViewBobPhase) * cg.fCurrentViewBobAmp * 0.03;
if (fPhase > 16.0) {
fPhase = 16.0;
} else if (fPhase < -16.0) {
fPhase = -16.0;
}
VectorMA(origin, fPhase, vLeft, origin);
fPhase = sin(cg.fCurrentViewBobPhase - 0.94);
fPhase = (fabs(fPhase) - 0.5) * cg.fCurrentViewBobAmp * 0.06;
if (fPhase > 16.0) {
fPhase = 16.0;
} else if (fPhase < -16.0) {
fPhase = -16.0;
}
origin[2] += fPhase;
}
iMask = MASK_PLAYERSOLID;
}
vStart[0] = cg.predicted_player_state.origin[0];
vStart[1] = cg.predicted_player_state.origin[1];
vStart[2] = cg.predicted_player_state.origin[2] + cg.predicted_player_state.viewheight;
vEnd[0] = cg.predicted_player_state.origin[0];
vEnd[1] = cg.predicted_player_state.origin[1];
vEnd[2] = origin[2];
CG_Trace(
&trace, vStart, vMins, vMaxs, vEnd, cg.snap->ps.clientNum, iMask, qfalse, qtrue, "FirstPerson Height Check"
);
VectorCopy(trace.endpos, vStart);
vEnd[0] = origin[0];
vEnd[1] = origin[1];
vEnd[2] = trace.endpos[2];
CG_Trace(&trace, vStart, vMins, vMaxs, vEnd, cg.snap->ps.clientNum, iMask, 0, 1, "FirstPerson Lateral Check");
VectorCopy(trace.endpos, origin);
VectorSubtract(origin, vOldOrigin, vDelta);
VectorAdd(pREnt->origin, vDelta, pREnt->origin);
if (!bUseWorldPosition) {
VectorCopy(cg.refdefViewAngles, vDelta);
vDelta[0] *= 0.5;
vDelta[2] *= 0.75;
AngleVectorsLeft(vDelta, mat[0], mat[1], mat[2]);
CG_CalcViewModelMovement(
cg.fCurrentViewBobPhase, cg.fCurrentViewBobAmp, vVelocity, vDelta
);
VectorMA(pREnt->origin, vDelta[0], mat[0], pREnt->origin);
VectorMA(pREnt->origin, vDelta[1], mat[1], pREnt->origin);
VectorMA(pREnt->origin, vDelta[2], mat[2], pREnt->origin);
}
VectorCopy(origin, cg.playerHeadPos);
}
/*
====================
CG_CalcFov
Fixed fov at intermissions, otherwise account for fov variable and zooms.
====================
*/
#define WAVE_AMPLITUDE 1
#define WAVE_FREQUENCY 0.4
static int CG_CalcFov(void)
{
float x;
float phase;
float v;
int contents;
float fov_x, fov_y;
int inwater;
float fov_ratio;
fov_ratio = (float)cg.refdef.width / (float)cg.refdef.height * (3.0 / 4.0);
if (fov_ratio == 1) {
fov_x = cg.camera_fov;
} else {
fov_x = RAD2DEG(atan(tan(DEG2RAD(cg.camera_fov / 2.0)) * fov_ratio)) * 2.0;
}
x = cg.refdef.width / tan(fov_x / 360 * M_PI);
fov_y = atan2(cg.refdef.height, x);
fov_y = fov_y * 360 / M_PI;
// warp if underwater
contents = CG_PointContents(cg.refdef.vieworg, -1);
if (contents & (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA)) {
phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
v = WAVE_AMPLITUDE * sin(phase);
fov_x += v;
fov_y -= v;
inwater = qtrue;
} else {
inwater = qfalse;
}
// set it
cg.refdef.fov_x = fov_x;
cg.refdef.fov_y = fov_y;
cg.fRefFovXCos = cos(fov_x / 114.0f);
cg.fRefFovXSin = sin(fov_x / 114.0f);
cg.fRefFovYCos = cos(fov_y / 114.0f);
cg.fRefFovYSin = sin(fov_y / 114.0f);
cg.zoomSensitivity = cg.refdef.fov_y / 75.0;
return inwater;
}
/*
===============
CG_SetupFog
Prepares fog values for rendering
===============
*/
void CG_SetupFog() {
cg.refdef.farplane_distance = cg.farplane_distance;
cg.refdef.farplane_bias = cg.farplane_bias;
cg.refdef.farplane_color[0] = cg.farplane_color[0];
cg.refdef.farplane_color[1] = cg.farplane_color[1];
cg.refdef.farplane_color[2] = cg.farplane_color[2];
cg.refdef.farplane_cull = cg.farplane_cull;
cg.refdef.skybox_farplane = cg.skyboxFarplane;
cg.refdef.renderTerrain = cg.renderTerrain;
cg.refdef.farclipOverride = cg.farclipOverride;
cg.refdef.farplaneColorOverride[0] = cg.farplaneColorOverride[0];
cg.refdef.farplaneColorOverride[1] = cg.farplaneColorOverride[1];
cg.refdef.farplaneColorOverride[2] = cg.farplaneColorOverride[2];
}
/*
===============
CG_SetupPortalSky
Sets portalsky values for rendering
===============
*/
void CG_SetupPortalSky() {
cg.refdef.sky_alpha = cg.sky_alpha;
cg.refdef.sky_portal = cg.sky_portal;
VectorCopy(cg.sky_axis[0], cg.refdef.sky_axis[0]);
VectorCopy(cg.sky_axis[1], cg.refdef.sky_axis[1]);
VectorCopy(cg.sky_axis[2], cg.refdef.sky_axis[2]);
VectorMA(cg.sky_origin, cg.skyboxSpeed, cg.refdef.vieworg, cg.refdef.sky_origin);
}
/*
===============
CG_CalcViewValues
Sets cg.refdef view values
===============
*/
static int CG_CalcViewValues(void)
{
playerState_t *ps;
float SoundAngles[3];
memset(&cg.refdef, 0, sizeof(cg.refdef));
// calculate size of 3D view
CG_CalcVrect();
CG_SetupFog();
ps = &cg.predicted_player_state;
VectorCopy(ps->origin, cg.refdef.vieworg);
VectorCopy(ps->viewangles, cg.refdefViewAngles);
if (cg.snap->ps.stats[STAT_HEALTH] > 0) {
VectorSubtract(cg.refdefViewAngles, cg.predicted_player_state.damage_angles, cg.refdefViewAngles);
cg.refdefViewAngles[0] += cg.viewkick[0];
cg.refdefViewAngles[1] += cg.viewkick[1];
if (cg.viewkick[0] || cg.viewkick[1]) {
int i;
float fDecay;
for (i = 0; i < 2; i++) {
fDecay = cg.viewkick[i] * cg.viewkickRecenter;
if (fDecay > cg.viewkickMaxDecay) {
fDecay = cg.viewkickMaxDecay;
} else if (fDecay < -cg.viewkickMaxDecay) {
fDecay = -cg.viewkickMaxDecay;
}
if (fabs(fDecay) < cg.viewkickMinDecay) {
if (fDecay > 0.0) {
fDecay = cg.viewkickMinDecay;
} else {
fDecay = -cg.viewkickMinDecay;
}
}
if (cg.viewkick[i] > 0.0) {
cg.viewkick[i] -= fDecay * (float)cg.frametime / 1000.0;
if (cg.viewkick[i] < 0.0) {
cg.viewkick[i] = 0.0;
}
} else {
cg.viewkick[i] -= fDecay * (float)cg.frametime / 1000.0;
if (cg.viewkick[i] > 0.0) {
cg.viewkick[i] = 0.0;
}
}
}
}
}
// FIXME: fffx screen shake on win32 builds?
// add error decay
if (cg_errorDecay->value > 0) {
int t;
float f;
t = cg.time - cg.predictedErrorTime;
f = (cg_errorDecay->value - t) / cg_errorDecay->value;
if (f > 0 && f < 1) {
VectorMA(cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg);
} else {
cg.predictedErrorTime = 0;
}
}
// calculate position of player's head
cg.refdef.vieworg[2] += cg.predicted_player_state.viewheight;
// save off the position of the player's head
VectorCopy(cg.refdef.vieworg, cg.playerHeadPos);
// Set the aural position of the player
VectorCopy(cg.playerHeadPos, cg.SoundOrg);
// Set the aural axis of the player
VectorCopy(cg.refdefViewAngles, SoundAngles);
// yaw is purposely inverted because of the miles sound system
// Commented out in OPM
// Useless as SDL audio/AL is used
//SoundAngles[YAW] = -SoundAngles[YAW];
AnglesToAxis(SoundAngles, cg.SoundAxis);
// decide on third person view
cg.renderingThirdPerson = cg_3rd_person->integer;
if (cg.renderingThirdPerson) {
// back away from character
CG_OffsetThirdPersonView();
}
// if we are in a camera view, we take our audio cues directly from the camera
if (ps->pm_flags & PMF_CAMERA_VIEW) {
// Set the aural position to that of the camera
VectorCopy(cg.camera_origin, cg.refdef.vieworg);
// Set the aural axis to the camera's angles
VectorCopy(cg.camera_angles, cg.refdefViewAngles);
if (cg_protocol >= PROTOCOL_MOHTA_MIN && (ps->pm_flags & PMF_DAMAGE_ANGLES)) {
// Handle camera shake
VectorSubtract(cg.refdefViewAngles, cg.predicted_player_state.damage_angles, cg.refdefViewAngles);
}
if (ps->camera_posofs[0] || ps->camera_posofs[1] || ps->camera_posofs[2]) {
vec3_t vAxis[3], vOrg;
AnglesToAxis(cg.refdefViewAngles, vAxis);
MatrixTransformVector(ps->camera_posofs, vAxis, vOrg);
VectorAdd(cg.refdef.vieworg, vOrg, cg.refdef.vieworg);
}
// copy view values
VectorCopy(cg.refdef.vieworg, cg.currentViewPos);
VectorCopy(cg.refdefViewAngles, cg.currentViewAngles);
// since 2.0: also copy location data for sound
VectorCopy(cg.refdef.vieworg, cg.SoundOrg);
AnglesToAxis(cg.refdefViewAngles, cg.SoundAxis);
}
// position eye reletive to origin
AnglesToAxis(cg.refdefViewAngles, cg.refdef.viewaxis);
if (cg.hyperspace) {
cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE;
}
// field of view
return CG_CalcFov();
}
void CG_EyePosition(vec3_t *o_vPos)
{
(*o_vPos)[0] = cg.playerHeadPos[0];
(*o_vPos)[1] = cg.playerHeadPos[1];
(*o_vPos)[2] = cg.playerHeadPos[2];
}
void CG_EyeOffset(vec3_t *o_vOfs)
{
(*o_vOfs)[0] = cg.playerHeadPos[0] - cg.predicted_player_state.origin[0];
(*o_vOfs)[1] = cg.playerHeadPos[1] - cg.predicted_player_state.origin[1];
(*o_vOfs)[2] = cg.playerHeadPos[2] - cg.predicted_player_state.origin[2];
}
void CG_EyeAngles(vec3_t *o_vAngles)
{
(*o_vAngles)[0] = cg.refdefViewAngles[0];
(*o_vAngles)[1] = cg.refdefViewAngles[1];
(*o_vAngles)[2] = cg.refdefViewAngles[2];
}
float CG_SensitivityScale()
{
return cg.zoomSensitivity;
}
void CG_AddLightShow()
{
int i;
float fSlopeY, fSlopeZ;
float x, y, z;
vec3_t vOrg;
float r, g, b;
float fMax;
fSlopeY = tan(cg.refdef.fov_x * 0.5);
fSlopeZ = tan(cg.refdef.fov_y * 0.5);
for (i = 0; i < cg_acidtrip->integer; i++) {
x = pow(random(), 1.0 / 3.0) * 2048.0;
y = crandom() * x * fSlopeY;
z = crandom() * x * fSlopeZ;
VectorCopy(cg.refdef.vieworg, vOrg);
VectorMA(vOrg, x, cg.refdef.viewaxis[0], vOrg);
VectorMA(vOrg, y, cg.refdef.viewaxis[1], vOrg);
VectorMA(vOrg, z, cg.refdef.viewaxis[2], vOrg);
r = random();
g = random();
b = random();
fMax = Q_max(r, Q_max(g, b));
r /= fMax;
g /= fMax;
b /= fMax;
cgi.R_AddLightToScene(vOrg, (rand() & 0x1FF) + 0x80, r, g, b, 0);
}
}
qboolean CG_FrustumCullSphere(const vec3_t vPos, float fRadius) {
vec3_t delta;
float fDotFwd, fDotSide, fDotUp;
VectorSubtract(vPos, cg.refdef.vieworg, delta);
fDotFwd = DotProduct(delta, cg.refdef.viewaxis[0]);
if (-fRadius >= fDotFwd) {
return qtrue;
}
if (cg.refdef.farplane_distance && cg.refdef.farplane_distance + fRadius <= fDotFwd) {
return qtrue;
}
fDotSide = DotProduct(delta, cg.refdef.viewaxis[1]);
if (fDotSide < 1.f) {
fDotSide = -fDotSide;
}
if (fDotSide * cg.fRefFovXCos - fDotFwd * cg.fRefFovXSin >= fRadius) {
return qtrue;
}
fDotUp = DotProduct(delta, cg.refdef.viewaxis[2]);
if (fDotUp < 0.f) {
fDotUp = -fDotUp;
}
if (fDotUp * cg.fRefFovYCos - fDotFwd * cg.fRefFovYSin >= fRadius) {
return qtrue;
}
return qfalse;
}
//=========================================================================
/*
=================
CG_DrawActiveFrame
Generates and draws a game scene and status information at the given time.
=================
*/
void CG_DrawActiveFrame(int serverTime, int frameTime, stereoFrame_t stereoView, qboolean demoPlayback)
{
cg.time = serverTime;
cg.frametime = frameTime;
cg.demoPlayback = demoPlayback;
// any looped sounds will be respecified as entities
// are added to the render list
cgi.S_ClearLoopingSounds();
// clear all the render lists
cgi.R_ClearScene();
// set up cg.snap and possibly cg.nextSnap
CG_ProcessSnapshots();
// if we haven't received any snapshots yet, all
// we can draw is the information screen
if (!cg.snap || (cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE)) {
return;
}
// this counter will be bumped for every valid scene we generate
cg.clientFrame++;
// set cg.frameInterpolation
if (cg.nextSnap && r_lerpmodels->integer) {
int delta;
delta = (cg.nextSnap->serverTime - cg.snap->serverTime);
if (delta == 0) {
cg.frameInterpolation = 0;
} else {
cg.frameInterpolation = (float)(cg.time - cg.snap->serverTime) / delta;
}
} else {
cg.frameInterpolation = 0; // actually, it should never be used, because
// no entities should be marked as interpolating
}
//
// Added in OPM
// Clamp the fov to avoid artifacts
if (cg_fov->value < 65) {
cgi.Cvar_Set("cg_fov", "65");
} else if (cg_fov->value > 120) {
cgi.Cvar_Set("cg_fov", "120");
}
// update cg.predicted_player_state
CG_PredictPlayerState();
// build cg.refdef
CG_CalcViewValues();
// display the intermission
if (cg.snap->ps.pm_flags & PMF_INTERMISSION) {
if (cgs.gametype != GT_SINGLE_PLAYER) {
CG_ScoresDown_f();
} else if (cg.bIntermissionDisplay) {
if (cg.nextSnap) {
if (cg_protocol >= PROTOCOL_MOHTA_MIN) {
cvar_t* pMission = cgi.Cvar_Get("g_mission", "", CVAR_ARCHIVE);
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
switch (pMission->integer)
{
default:
case 0:
cgi.UI_ShowMenu("mission_success_1", 0);
cgi.Cvar_Set("g_t2l1", "1");
break;
case 2:
cgi.UI_ShowMenu("mission_success_2", 0);
cgi.Cvar_Set("g_t3l1", "1");
break;
case 3:
cgi.UI_ShowMenu("mission_success_3", 0);
break;
}
} else {
switch (pMission->integer)
{
default:
case 0:
cgi.UI_ShowMenu("mission_failed_1", 0);
break;
case 2:
cgi.UI_ShowMenu("mission_failed_2", 0);
break;
case 3:
cgi.UI_ShowMenu("mission_failed_3", 0);
break;
}
}
} else {
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
cgi.UI_ShowMenu("StatsScreen_Success", qfalse);
} else {
cgi.UI_ShowMenu("StatsScreen_Failed", qfalse);
}
}
}
} else {
cgi.SendClientCommand("stats");
}
cg.bIntermissionDisplay = qtrue;
} else if (cg.bIntermissionDisplay) {
if (cgs.gametype != GT_SINGLE_PLAYER) {
CG_ScoresUp_f();
} else {
if (cg_protocol >= PROTOCOL_MOHTA_MIN) {
cvar_t* pMission = cgi.Cvar_Get("g_mission", "", CVAR_ARCHIVE);
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
switch (pMission->integer)
{
default:
case 0:
cgi.UI_HideMenu("mission_success_1", qtrue);
break;
case 2:
cgi.UI_HideMenu("mission_success_2", qtrue);
break;
case 3:
cgi.UI_HideMenu("mission_success_3", qtrue);
break;
}
} else {
switch (pMission->integer)
{
default:
case 0:
cgi.UI_HideMenu("mission_failed_1", qtrue);
break;
case 2:
cgi.UI_HideMenu("mission_failed_2", qtrue);
break;
case 3:
cgi.UI_HideMenu("mission_failed_3", qtrue);
break;
}
}
} else {
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
cgi.UI_HideMenu("StatsScreen_Success", qtrue);
} else {
cgi.UI_HideMenu("StatsScreen_Failed", qtrue);
}
}
}
cg.bIntermissionDisplay = qfalse;
}
// Added in OPM
CG_ProcessPlayerModel();
// build the render lists
if (!cg.hyperspace) {
CG_AddPacketEntities(); // after calcViewValues, so predicted player state is correct
CG_AddMarks();
}
// finish up the rest of the refdef
CG_SetupPortalSky();
cg.refdef.time = cg.time;
memcpy(cg.refdef.areamask, cg.snap->areamask, sizeof(cg.refdef.areamask));
// update audio positions
cgi.S_Respatialize(cg.snap->ps.clientNum, cg.SoundOrg, cg.SoundAxis);
// make sure the lagometerSample and frame timing isn't done twice when in stereo
if (stereoView != STEREO_RIGHT) {
CG_AddLagometerFrameInfo();
}
CG_UpdateTestEmitter();
CG_AddPendingEffects();
if (!cg_hidetempmodels->integer) {
CG_AddTempModels();
}
if (vss_draw->integer) {
CG_AddVSSSources();
}
CG_AddBulletTracers();
CG_AddBulletImpacts();
CG_AddBeams();
if (cg_acidtrip->integer) {
// lol disco
CG_AddLightShow();
}
// actually issue the rendering calls
CG_DrawActive(stereoView);
if (cg_stats->integer) {
cgi.Printf("cg.clientFrame:%i\n", cg.clientFrame);
}
}