Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added viewmodel FOV cvar, added viewmodel xyz offset cvar #107

Merged
merged 1 commit into from
Apr 20, 2021

Conversation

chinese-soup
Copy link
Contributor

@chinese-soup chinese-soup commented Apr 19, 2021

Hello. This closes #26.

Abstract

@execut4ble says:

Something like CSGO's viewmodel_fov so you can set how much your weapon sticks out without changing the actual field of view.

AFAIK CSGO's viewmodel_fov doesn't actually change the viewmodel_FOV at all, it's merely (I THINK) changing the origin from which the "camera" is looking at the gun (or it's actually just keeping the origin the same, but changing how it's "looked at" some other way, but you get the point).
This is different to what other source games e.g. Portal/HL2/HL2EP1 do, where viewmodel_fov actually changes the FOV for the viewmodel "camera".

I've implemented cvars for both cases (although the CS:GO offset thing isn't exactly like in CS:GO, obv, you'll see what I mean). See the reference screenshots & videos at the end of this post.

Just for reference here's what the Portal's viewmodel_fov does and what viewmodel_fov IMHO should be and therefore what viewmodel_fov in my PR means:
image

Cvars

Both the FOV and offset cvars start with cl_viewmodel for constitency and easy access.

Viewmodel FOV

  • cl_viewmodel_fov 0 or cl_viewmodel_fov 1-179 - 0 means disabled, 1-179 means the FOV for the gun.
  • Important: The FOV value is equivalent to what the gun would look like if you were to just use default_fov. (As opposed to for example HL2 where viewmodel_fov defaults to 54 and FOV itself defaults to 75/90)

Examples might be easier to understand:

  • default_fov 120 & cl_viewmodel_fov 0 = this means gun will look like it does on default_fov 120, since cl_viewmodelfov is disabled.
  • default_fov 120 & cl_viewmodel_fov 120 = this means gun will look like it does on default_fov 120, since cl_viewmodelfov is the same value as default_fov -- 120.
  • default_fov 120 & cl_viewmodel_fov 140 = this means gun will look like it would if default_fov was 140, but the rest of the view will still be defaut_fov 120.

Therefore if cl_viewmodel_fov is set to 0, the viewmodel FOV will be the same as default game, aka it will be set by default_fov like the rest of the view.

Viewmodel offset

For the offset variables I've opted for _right, _forward and _up instead of _y _x _z for the user experience, because technically the _right variable moves the "camera's" "origin" to the left, but the gun appears more to the right, therefore I've opted for these names.

Be aware that these values can be negative. Values that make sense are more or less -100 to 100, they aren't capped, but you usually can't see the gun anymore above 100 or below -100 on basically any FOV.

  • cl_viewmodel_ofs_right - 0 means no offset, offsets the origin of the camera to the left, therefore moving the gun to the right
  • cl_viewmodel_ofs_forward - 0 means no offset, offsets the origin of the camera to the back, therefore moving the gun forward ("in front of you")
  • cl_viewmodel_ofs_up - 0 means no offset, offsets the origin of the camera to the bottom, therefore moving the gun up ("above you")

Implementation: offsets

These are pretty self-explanatory, they offset the origin from which the "camera" is looking at the viewmodel, this is NOT the same thing as the player's camera. If cl_righthand is 1 (aka gordon becomes lefthanded) the "_ofs_right" offset is multiplied by -1 to get the correct values for the "other side".

Implementation: viewmodel FOV

Uh... where do I start...

StudioModelRenderer.cpp (CStudioModelRenderer)
As the name suggests this classes takes care of parsing studio .mdl models, it sets up bones, adds proper lighting, texture materials, sets up the model body parts and renders them. The code however doesn't tell you much as this file in the client dll module is mostly just using the IEngineStudio interface API and the engine takes care of the rest, if I skip the parts that are not important to my implementation:

Steps:
<1> --- For us non-important steps like: load model, setup bones, setup lighting, load up textures etc. ---
<2> SetupRenderer - setups the renderer (and sets glStuff to "model rendering" "mode")
<3> Iterates over the bodyparts of the model

    • a) StudioSetupModel (get mstudiomodel_t & mstudiobodyparts_t)
    • b) Sets GL_RenderMode (this is just some texture/face rendermode, not what the name suggests)
    • c) DrawsPoints using OpenGL - this is the part that actually renders it using OGL
    • d) Draws shadows

<4> Restores the renderer (and therefore sets glStuff back to "non-model rendering" "mode")

Like I've said, this whole process uses the IEngineStudio for all these steps so it's not transparent, what however, is transparent is the modelviewer Valve(?) made and the file studio_render.cpp that comes with it. Every model viewer (Jed's MV, HLMV-Qt, HLAM) uses the same base from the modelviewer here: https://github.com/ValveSoftware/halflife/tree/master/utils/mdlviewer, although personally I've been using the code of this one as a reference: https://github.com/MoeMod/HLMV-Qt (they all the same anyway, except modelviewer has like no features at all).

The code isn't 1:1 to what the engine probably does when the client dll calls IEngineStudio but you can get a pretty good idea of what is mostly happening in these IEngineStudio calls. Therefore if you look at this piece of code in studio_render.cpp:DrawModel():

	for (i=0 ; i < m_pstudiohdr->numbodyparts ; i++) 
	{
		SetupModel( i );
		if (g_viewerSettings.transparency > 0.0f)
			DrawPoints( );
	}

You can see that it iterates the bodyparts and setups the model, which in our case is what steps 2) and 3) a) are, see here:

		for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++)
		{
			IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel );

You can see that it also calls a DrawPoints() function that we also have in StudioModelRenderer.cpp as an API call:

		for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++)
		{
			IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); // step 3) a)
			// [...] omitted for brevity [...]
			IEngineStudio.GL_SetRenderMode( rendermode ); // step 3) b)
			IEngineStudio.StudioDrawPoints(); // <----------------- HERE step 3) c)
			IEngineStudio.GL_StudioDrawShadow(); // step 3) d)
		}

The DrawPoints function is the one that does the final render and therefore does OpenGL calls e.g.:
studio_render.cpp

                glBindTexture( GL_TEXTURE_2D, pskinref[pmesh->skinref] + 3);
                glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                // [...] omitted for brevity
                glDisable (GL_ALPHA_TEST);
                glEnable (GL_BLEND);
                // [...] omitted for brevity
                while ((i = *(ptricmds++)))
                {
                        if (i < 0)
                        {
                                tri_type = GL_TRIANGLE_FAN;
                                i = -i;
                        }
                        else
                        {
                                tri_type = GL_TRIANGLE_STRIP;
                        }

                        glBegin (tri_type);
                        for( ; i > 0; i--, ptricmds += 4)
                        {
                                // [...] omitted for brevity
                                glTexCoord2f (coords2d[0], coords2d[1]);
                                lv = g_pvlightvalues[ptricmds[1]];
                                glColor4f (lv[0], lv[1], lv[2], g_viewerSettings.transparency);
                                 
                                av = g_pxformverts[ptricmds[0]];
                                glVertex3f (av[0], av[1], av[2]);
                        }
                        glEnd ( );
                }

Therefore this is the exact spot where we should do our OpenGL calls - before the DrawPoints function is called and renders the actual model:

			IEngineStudio.GL_SetRenderMode( rendermode );
			// Warning: Order is IMPORANT here. I repeat, this has to be HERE.
			if (m_pCurrentEntity == gEngfuncs.GetViewModel() && m_pCvarViewmodelFov->value != 0.0f)
			{
				SetViewmodelFovProjection();
			}
			IEngineStudio.StudioDrawPoints();

The Renderer is then restored using an API call IEngineStudio.RestoreRenderer() hence why the next render calls (the ones that AREN'T for a viewmodel and therefore won't call our custom OpenGL stuff function) won't have our trickery anymore! \o/

The function SetViewmodelFovProjection() then calculates the FOV based on the screen resolution (window size) just like default_fov would and sets the custom projection so that the DrawPoints function OpenGL stuff usees that projection instead of the one it usually gets from the engine.

When cl_viewmodel_fov is set to 0, SetViewmodelFovProjection isn't called at all and FOV is the same as default game (aka weapon has the same FOV as the whole game set by default_fov)

Hopefully this all makes sense, it's almost 4 am and I'm kinda tired. :| Sorry.

Reference screenshots and videos

  • Video showcasing the cl_viewmodel_fov variable: https://www.youtube.com/watch?v=SujrFh9pnws (sorry for abrupt endng, i had to cut it off because of me mumbling about cl_viewmodel_ofs for too long)

  • Screenshots, these are self-explanatory, you can see the values used in each screenshot in the console window in the top left corner. 🙂
    c1a00026 bmp

c1a00027 bmp

default_fov=120 & cl_viewmodel_fov=0 ↓ ...
c1a00028 bmp
... is the same as default_fov=120 & cl_viewmodel_fov=120 ↓
c1a00030 bmp

c1a00031 bmp

Very immersive:
c1a00036

cl_righthand showcase:
c1a00034 bmp
c1a00033 bmp

TODO

  • * Fix Windows header problem -- fixed, thanks @tmp64

@chinese-soup chinese-soup force-pushed the viewmodel_fov branch 3 times, most recently from e67d5e5 to 2ecb9cf Compare April 19, 2021 02:45
Copy link
Owner

@YaLTeR YaLTeR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great!

cl_dll/StudioModelRenderer.cpp Outdated Show resolved Hide resolved
cl_dll/StudioModelRenderer.h Outdated Show resolved Hide resolved
cl_dll/StudioModelRenderer.h Outdated Show resolved Hide resolved
@YaLTeR YaLTeR merged commit 4254066 into YaLTeR:master Apr 20, 2021
@YaLTeR
Copy link
Owner

YaLTeR commented Apr 20, 2021

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a command to adjust viewmodel FOV
2 participants