openmohaa/code/cgame/cg_view.c

1016 lines
32 KiB
C
Raw Permalink Normal View History

2016-03-27 11:49:47 +02:00
/*
===========================================================================
2023-04-30 00:02:16 +02:00
Copyright (C) 2023 the OpenMoHAA team
2016-03-27 11:49:47 +02:00
2023-04-30 00:02:16 +02:00
This file is part of OpenMoHAA source code.
2016-03-27 11:49:47 +02:00
2023-04-30 00:02:16 +02:00
OpenMoHAA source code is free software; you can redistribute it
2016-03-27 11:49:47 +02:00
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.
2023-04-30 00:02:16 +02:00
OpenMoHAA source code is distributed in the hope that it will be
2016-03-27 11:49:47 +02:00
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
2023-04-30 00:02:16 +02:00
along with OpenMoHAA source code; if not, write to the Free Software
2016-03-27 11:49:47 +02:00
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
2023-04-30 00:02:16 +02:00
#include "cg_local.h"
#include "cg_parsemsg.h"
2016-03-27 11:49:47 +02:00
//============================================================================
/*
=================
CG_CalcVrect
Sets the coordinates of the rendered window
=================
*/
2023-07-05 21:24:23 +02:00
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;
2016-03-27 11:49:47 +02:00
}
//==============================================================================
/*
===============
CG_OffsetThirdPersonView
===============
*/
2023-04-30 00:02:16 +02:00
#define CAMERA_MINIMUM_DISTANCE 40
2023-07-05 21:24:23 +02:00
static void CG_OffsetThirdPersonView(void)
2023-04-30 00:02:16 +02:00
{
2023-07-05 21:24:23 +02:00
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);
/*
2023-04-30 00:02:16 +02:00
// 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
*/
2023-07-05 21:24:23 +02:00
{
// 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;
}
2023-04-30 00:02:16 +02:00
}
2023-07-05 21:24:23 +02:00
if (target_angles[PITCH] > 90) {
// if we failed, go with the original angles
target_angles[PITCH] = original_angles[PITCH];
2023-04-30 00:02:16 +02:00
}
2023-07-05 21:24:23 +02:00
}
}
}
2016-03-27 11:49:47 +02:00
/*
===============
CG_OffsetFirstPersonView
===============
*/
2023-07-05 21:24:23 +02:00
void CG_OffsetFirstPersonView(refEntity_t *pREnt, qboolean bUseWorldPosition)
{
2023-07-05 21:24:23 +02:00
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;
2023-07-05 21:24:23 +02:00
trace_t trace;
2016-03-27 11:49:47 +02:00
VectorSet(vMins, -6, -6, -6);
VectorSet(vMaxs, 6, 6, 6);
2023-07-05 21:24:23 +02:00
//
//
//
//
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");
2023-07-05 21:24:23 +02:00
if (iTag != -1) {
if (bUseWorldPosition) {
orientation_t oHead;
2023-07-05 21:24:23 +02:00
float mat3[3][3];
vec3_t vHeadAng, vDelta;
VectorCopy(pCent->lerpOrigin, origin);
AxisCopy(pREnt->axis, mat);
oHead = cgi.TIKI_Orientation(pREnt, iTag);
2023-07-05 21:24:23 +02:00
for (i = 0; i < 3; i++) {
VectorMA(origin, oHead.origin[i], mat[i], origin);
}
2023-07-05 21:24:23 +02:00
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);
2023-07-05 21:24:23 +02:00
} else {
orientation_t oHead;
2023-07-05 21:24:23 +02:00
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;
}
2023-07-05 21:24:23 +02:00
} 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);
}
2023-07-05 21:24:23 +02:00
if (bUseWorldPosition) {
iMask = MASK_VIEWSOLID;
2023-07-05 21:24:23 +02:00
} else {
float fTargHeight;
float fHeightDelta, fHeightChange;
float fPhase, fVel;
vec3_t vDelta;
vec3_t vPivotPoint;
vec3_t vForward, vLeft;
2023-07-05 21:24:23 +02:00
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;
2023-07-05 21:24:23 +02:00
if (fabs(fHeightDelta) < 0.1 || !cg.fCurrentViewHeight) {
cg.fCurrentViewHeight = fTargHeight;
2023-07-05 21:24:23 +02:00
} else {
if (fHeightDelta > 32.f) {
2023-07-05 21:24:23 +02:00
fHeightDelta = 32.f;
cg.fCurrentViewHeight = fTargHeight - 32.0;
2023-07-05 21:24:23 +02:00
} 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;
}
2023-07-05 21:24:23 +02:00
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);
2023-07-05 21:24:23 +02:00
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;
2023-07-05 21:24:23 +02:00
} else {
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.2;
}
2023-07-05 21:24:23 +02:00
} 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);
}
2023-07-05 21:24:23 +02:00
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;
2023-07-05 21:24:23 +02:00
} 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;
2023-07-05 21:24:23 +02:00
} 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;
}
}
2023-07-05 21:24:23 +02:00
if (cg.fCurrentViewBobAmp > 0.0) {
fPhase = sin(cg.fCurrentViewBobPhase) * cg.fCurrentViewBobAmp * 0.03;
2023-07-05 21:24:23 +02:00
if (fPhase > 16.0) {
fPhase = 16.0;
2023-07-05 21:24:23 +02:00
} 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;
2023-07-05 21:24:23 +02:00
} 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;
2023-07-05 21:24:23 +02:00
vEnd[0] = cg.predicted_player_state.origin[0];
vEnd[1] = cg.predicted_player_state.origin[1];
vEnd[2] = origin[2];
2023-07-05 21:24:23 +02:00
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);
2023-07-05 21:24:23 +02:00
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);
}
2016-03-27 11:49:47 +02:00
/*
====================
CG_CalcFov
Fixed fov at intermissions, otherwise account for fov variable and zooms.
====================
*/
2023-07-05 21:24:23 +02:00
#define WAVE_AMPLITUDE 1
#define WAVE_FREQUENCY 0.4
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
static int CG_CalcFov(void)
{
2023-07-05 21:24:23 +02:00
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;
2023-07-05 21:24:23 +02:00
v = WAVE_AMPLITUDE * sin(phase);
fov_x += v;
fov_y -= v;
inwater = qtrue;
2023-07-05 21:24:23 +02:00
} else {
inwater = qfalse;
}
// set it
2023-07-05 21:24:23 +02:00
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;
}
2016-03-27 11:49:47 +02:00
/*
===============
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);
}
2016-03-27 11:49:47 +02:00
/*
===============
CG_CalcViewValues
Sets cg.refdef view values
===============
*/
static int CG_CalcViewValues(void)
{
2023-07-05 21:24:23 +02:00
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);
2023-07-05 21:24:23 +02:00
if (cg.snap->ps.stats[STAT_HEALTH] > 0) {
2023-05-06 16:15:28 +02:00
VectorSubtract(cg.refdefViewAngles, cg.predicted_player_state.damage_angles, cg.refdefViewAngles);
cg.refdefViewAngles[0] += cg.viewkick[0];
cg.refdefViewAngles[1] += cg.viewkick[1];
2023-07-05 21:24:23 +02:00
if (cg.viewkick[0] || cg.viewkick[1]) {
int i;
2023-05-06 16:15:28 +02:00
float fDecay;
2023-07-05 21:24:23 +02:00
for (i = 0; i < 2; i++) {
2023-05-06 16:15:28 +02:00
fDecay = cg.viewkick[i] * cg.viewkickRecenter;
2023-07-05 21:24:23 +02:00
if (fDecay > cg.viewkickMaxDecay) {
2023-05-06 16:15:28 +02:00
fDecay = cg.viewkickMaxDecay;
2023-07-05 21:24:23 +02:00
} else if (fDecay < -cg.viewkickMaxDecay) {
2023-05-06 16:15:28 +02:00
fDecay = -cg.viewkickMaxDecay;
}
2023-07-05 21:24:23 +02:00
if (fabs(fDecay) < cg.viewkickMinDecay) {
2023-05-06 17:50:41 +02:00
if (fDecay > 0.0) {
fDecay = cg.viewkickMinDecay;
2023-07-05 21:24:23 +02:00
} else {
2023-05-06 17:50:41 +02:00
fDecay = -cg.viewkickMinDecay;
}
2023-05-06 16:15:28 +02:00
}
2023-07-05 21:24:23 +02:00
if (cg.viewkick[i] > 0.0) {
2023-05-06 16:15:28 +02:00
cg.viewkick[i] -= fDecay * (float)cg.frametime / 1000.0;
if (cg.viewkick[i] < 0.0) {
cg.viewkick[i] = 0.0;
}
2023-07-05 21:24:23 +02:00
} else {
2023-05-06 16:15:28 +02:00
cg.viewkick[i] -= fDecay * (float)cg.frametime / 1000.0;
if (cg.viewkick[i] > 0.0) {
cg.viewkick[i] = 0.0;
}
}
}
}
}
2023-05-06 16:15:28 +02:00
// FIXME: fffx screen shake on win32 builds?
// add error decay
2023-07-05 21:24:23 +02:00
if (cg_errorDecay->value > 0) {
int t;
float f;
t = cg.time - cg.predictedErrorTime;
f = (cg_errorDecay->value - t) / cg_errorDecay->value;
2023-07-05 21:24:23 +02:00
if (f > 0 && f < 1) {
VectorMA(cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg);
2023-07-05 21:24:23 +02:00
} 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;
2023-07-05 21:24:23 +02:00
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
2023-07-05 21:24:23 +02:00
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);
}
2023-07-05 21:24:23 +02:00
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();
2016-03-27 11:49:47 +02:00
}
2023-07-05 21:24:23 +02:00
void CG_EyePosition(vec3_t *o_vPos)
2023-05-01 00:10:23 +02:00
{
2023-05-01 15:14:17 +02:00
(*o_vPos)[0] = cg.playerHeadPos[0];
(*o_vPos)[1] = cg.playerHeadPos[1];
(*o_vPos)[2] = cg.playerHeadPos[2];
2023-05-01 00:10:23 +02:00
}
2023-07-05 21:24:23 +02:00
void CG_EyeOffset(vec3_t *o_vOfs)
2023-05-01 00:10:23 +02:00
{
2023-05-01 15:14:17 +02:00
(*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];
2023-05-01 00:10:23 +02:00
}
2023-07-05 21:24:23 +02:00
void CG_EyeAngles(vec3_t *o_vAngles)
2023-05-01 00:10:23 +02:00
{
2023-05-01 15:14:17 +02:00
(*o_vAngles)[0] = cg.refdefViewAngles[0];
(*o_vAngles)[1] = cg.refdefViewAngles[1];
(*o_vAngles)[2] = cg.refdefViewAngles[2];
2023-05-01 00:10:23 +02:00
}
float CG_SensitivityScale()
{
2023-05-01 15:14:17 +02:00
return cg.zoomSensitivity;
2023-05-01 00:10:23 +02:00
}
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);
}
2023-05-01 00:10:23 +02:00
}
2023-07-15 20:18:20 +02:00
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;
}
2023-07-15 20:18:20 +02:00
return qfalse;
}
2016-03-27 11:49:47 +02:00
//=========================================================================
/*
=================
CG_DrawActiveFrame
Generates and draws a game scene and status information at the given time.
=================
*/
2023-07-05 21:24:23 +02:00
void CG_DrawActiveFrame(int serverTime, int frameTime, stereoFrame_t stereoView, qboolean demoPlayback)
{
2023-07-05 21:24:23 +02:00
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
2023-07-05 21:24:23 +02:00
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) {
2023-07-05 21:24:23 +02:00
int delta;
delta = (cg.nextSnap->serverTime - cg.snap->serverTime);
if (delta == 0) {
cg.frameInterpolation = 0;
2023-07-05 21:24:23 +02:00
} else {
cg.frameInterpolation = (float)(cg.time - cg.snap->serverTime) / delta;
}
2023-07-05 21:24:23 +02:00
} 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");
2024-12-23 22:57:26 +01:00
} else if (cg_fov->value > 120) {
cgi.Cvar_Set("cg_fov", "120");
}
// update cg.predicted_player_state
CG_PredictPlayerState();
// build cg.refdef
CG_CalcViewValues();
2023-05-01 15:14:17 +02:00
// display the intermission
2023-07-05 21:24:23 +02:00
if (cg.snap->ps.pm_flags & PMF_INTERMISSION) {
2023-05-01 15:14:17 +02:00
if (cgs.gametype != GT_SINGLE_PLAYER) {
CG_ScoresDown_f();
2023-07-05 21:24:23 +02:00
} 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;
}
}
2023-05-01 15:14:17 +02:00
} else {
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
cgi.UI_ShowMenu("StatsScreen_Success", qfalse);
} else {
cgi.UI_ShowMenu("StatsScreen_Failed", qfalse);
}
2023-05-01 15:14:17 +02:00
}
}
} else {
cgi.SendClientCommand("stats");
}
cg.bIntermissionDisplay = qtrue;
2023-07-05 21:24:23 +02:00
} else if (cg.bIntermissionDisplay) {
2023-05-01 15:14:17 +02:00
if (cgs.gametype != GT_SINGLE_PLAYER) {
CG_ScoresUp_f();
2023-07-05 21:24:23 +02:00
} 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;
}
}
2023-05-01 15:14:17 +02:00
} else {
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
cgi.UI_HideMenu("StatsScreen_Success", qtrue);
} else {
cgi.UI_HideMenu("StatsScreen_Failed", qtrue);
}
2023-05-01 15:14:17 +02:00
}
}
cg.bIntermissionDisplay = qfalse;
}
// Added in OPM
CG_ProcessPlayerModel();
// build the render lists
2023-07-05 21:24:23 +02:00
if (!cg.hyperspace) {
CG_AddPacketEntities(); // after calcViewValues, so predicted player state is correct
CG_AddMarks();
}
// finish up the rest of the refdef
CG_SetupPortalSky();
2023-04-30 00:02:16 +02:00
cg.refdef.time = cg.time;
memcpy(cg.refdef.areamask, cg.snap->areamask, sizeof(cg.refdef.areamask));
2016-03-27 11:49:47 +02:00
// update audio positions
cgi.S_Respatialize(cg.snap->ps.clientNum, cg.SoundOrg, cg.SoundAxis);
2016-03-27 11:49:47 +02:00
// make sure the lagometerSample and frame timing isn't done twice when in stereo
2023-05-01 15:14:17 +02:00
if (stereoView != STEREO_RIGHT) {
CG_AddLagometerFrameInfo();
}
2016-03-27 11:49:47 +02:00
CG_UpdateTestEmitter();
2023-05-01 15:14:17 +02:00
CG_AddPendingEffects();
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
if (!cg_hidetempmodels->integer) {
CG_AddTempModels();
2023-07-05 21:24:23 +02:00
}
2016-03-27 11:49:47 +02:00
2023-05-01 15:14:17 +02:00
if (vss_draw->integer) {
CG_AddVSSSources();
}
CG_AddBulletTracers();
CG_AddBulletImpacts();
CG_AddBeams();
2023-04-30 00:02:16 +02:00
2023-05-01 15:14:17 +02:00
if (cg_acidtrip->integer) {
// lol disco
CG_AddLightShow();
}
// actually issue the rendering calls
CG_DrawActive(stereoView);
2023-07-05 21:24:23 +02:00
if (cg_stats->integer) {
cgi.Printf("cg.clientFrame:%i\n", cg.clientFrame);
}
}