Page 1 of 1

Don't use Q_rint in MSG_WriteAngles...

Posted: Sun Apr 10, 2011 5:00 pm
by mh
...or other +/- 0.5f tricks.

There is a bug with some maps (outlined here: http://forums.inside3d.com/viewtopic.php?t=2376&start=70) where entities do have angles set, but the standard precision loss in stock ID code doesn't cause them to have any effect. Switching to Q_rint-like behaviour causes things to go mildly berserk.

This also affects any protocol change where angles have more precision that 1 byte.

Fully reproducable in hrim_sp1: the part where you push a button and a platform rises out of lava.

Posted: Sun Apr 10, 2011 5:14 pm
by goldenboy
In other words it was always broken but it was never noticed owing to loss of precision when transmitting angles in stock protocol 15.
For my part, I need the smoothness and I'll say "to hell with the old stuff".

Throwing away real improvements in order to support old stuff is wrong.

That's my opinion.

Posted: Sun Apr 10, 2011 5:17 pm
by mh
Compromise/hacky/quick-n-dirty solution involves popping this at the top of MSG_WriteAngles:

Code: Select all

	// compensate for Q_rint and higher precision angles causing incorrect angles at this range
	// by setting anything from -1..1 to 0 in order to match the loss with stock ID Quake
	if (f > 1)
		;
	else if (f < -1)
		;
	else f = 0;

Posted: Sun Apr 10, 2011 7:38 pm
by Spike
how does that fix anything?
if the server is generating the wrong bbox then its collisions will be broken anyway.
if the model really is rotated with an origin, its clientside representation will differ from the server's 1-rotated bbox whether its sent as 0 or 1.0/255.

Posted: Sun Apr 10, 2011 7:56 pm
by mh
It's not bbox-related.

OK, specific case where this occurs. hrim_sp1 has a brush model that has angles of (0, -1, 0).

Protocol 15's default handling of angles (((int)f*256/360) & 255) makes this send to the client as (0, 0, 0).

Using Q_rint makes it send to the client as (0, ~(-1), 0).

In the former case the bmodel is drawn in the correct location.

In the latter case it's not. It's not just out of position by a small amount, it's wildly out of position.

Compare:

Image

The bbox on the server stays correct, the bmodel is drawn in the incorrect position on the client, you jump on the platform but only a small part of it will actually catch you - the rest of the time you're going into lava.

So what the hacky/quick-n-dirty thing does is kinda reproduce the behaviour of standard protocol 15 for really small angles by forcing anything in the range -1..1 to 0. Standard protocol 15 already does this anyway so it's nothing new.

Posted: Sun Apr 10, 2011 10:56 pm
by mh
This lot seems to fix things up in a more robust and general manner; some brief testing done and haven't noticed anything untoward yet. Gonna throw it at the RMQ people and see if they can find anywhere it breaks.

Code: Select all

void MSG_WriteByteAngle (sizebuf_t *sb, float f)
{
	byte bang = 0;

	// -1 and -2 are legal values with special meaning so handle them
	// the same way as ID Quake did; other values can use rounding
	if (f == -1 || f == -2)
		bang = (int) (f * 256.0 / 360.0) & 255;
	else bang = Q_rint (f * 256.0 / 360.0) & 255;

	MSG_WriteByte (sb, bang);
}


void MSG_WriteShortAngle (sizebuf_t *sb, float f)
{
	int sang = 0;

	// -1 and -2 are legal values with special meaning so handle them
	// the same way as ID Quake did; other values can use rounding
	if (f == -1 || f == -2)
	{
		// encode to byte, decode back to float to get the same behaviour as ID Quake
		sang = (int) (f * 256.0 / 360.0) & 255;
		f = (float) sang * (360.0 / 256);
	}

	sang = Q_rint (f * 65536.0 / 360.0) & 65535;

	MSG_WriteShort (sb, sang);
}


void MSG_WriteFloatAngle (sizebuf_t *sb, float f)
{
	// -1 and -2 are legal values with special meaning so handle them
	// the same way as ID Quake did; other values can use rounding
	if (f == -1 || f == -2)
	{
		// encode to byte, decode back to float to get the same behaviour as ID Quake
		int fang = (int) (f * 256.0 / 360.0) & 255;
		f = (float) fang * (360.0 / 256);
	}

	MSG_WriteFloat (sb, f);
}
...and just call the appropriate writing function from your main MSG_WriteAngle.

Posted: Mon Apr 11, 2011 8:56 pm
by r00k
Shouldn't there be corresponding READ functions?

Posted: Mon Apr 11, 2011 9:02 pm
by mh
No need because once an angle is encoded into a byte, that byte is only ever going to give the same result back when decoded. It's the byte that it gets encoded into that matters.

Posted: Mon Apr 11, 2011 9:15 pm
by r00k
_small tangent_

I noticed ProQuake (for lack of another comparison) uses shorts for angles (client --> server) BUT doesnt send PRECISEangles to the client from the server in SV_WriteEntitiesToClient.

So im a bit confused, (easily done), the client moves the mouse, aims at angle 34.54 (truncate/rounds/sends) to the server 34degrees the server runs through its frame sends back to ALL client's 34*(360/256) = 47.8125 ???

Posted: Mon Apr 11, 2011 9:53 pm
by Tomaz
r00k wrote:_small tangent_

I noticed ProQuake (for lack of another comparison) uses shorts for angles (client --> server) BUT doesnt send PRECISEangles to the client from the server in SV_WriteEntitiesToClient.

So im a bit confused, (easily done), the client moves the mouse, aims at angle 34.54 (truncate/rounds/sends) to the server 34degrees the server runs through its frame sends back to ALL client's 34*(360/256) = 47.8125 ???
Correct me if I'm wrong

Server sends:
34.54 * ( 256 / 360 ) which is roughly 24.56 which it rounds to 25
Client reads:
25 and does 25 * ( 360 * 256 ) which gives 35.15

So from 34.54 we got 35.15, close enough when dealing with just byte angles.

This is due to the fact that when using byte angles you get 360 / 255 precision which is roughly 1.4 degrees, compared to shorts which gives 360 / 65535 precision which is roughly 0.005 degrees.

Or did you mean that ProQuake uses shorts in one direction and bytes in the other? I wasn't aware that the client ever sends any angles to the server, at all. Isn't it just sending input events?

Posted: Mon Apr 11, 2011 10:01 pm
by r00k
Or did you mean that ProQuake uses shorts in one direction and bytes in the other? I wasn't aware that the client ever sends any angles to the server, at all. Isn't it just sending input events?
I was thinking the PQclient sends hires angles
cl_input.c

Code: Select all

//
// send the movement message
//
    MSG_WriteByte (buf, clc_move);

	MSG_WriteFloat (buf, cl.mtime[0]);
	
	if (!cls.demoplayback && (cls.netcon->mod == MOD_PROQUAKE)) // JPG - precise aim for ProQuake!
	{
		for (i=0 ; i<3 ; i++)
			MSG_WriteAngle16(buf, cl.viewangles[i]);			
	}
	else
	{
		for (i=0 ; i<3 ; i++)
			MSG_WriteAngle (buf, cl.viewangles[i]);
	}
server Reads hires angle from the client,
sv_user.c

Code: Select all

void SV_ReadClientMove (usercmd_t *move)
{
	int		i, bits;
	vec3_t	angle;

// read ping time
	host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] = sv.time - MSG_ReadFloat ();
	host_client->num_pings++;

// read current angles
	if (host_client->netconnection->mod == MOD_PROQUAKE)
	{
		for (i=0 ; i<3 ; i++)
			angle[i] = MSG_ReadAngle16 ();
	}
	else
	{
		for (i=0 ; i<3 ; i++)
			angle[i] = MSG_ReadAngle ();
	}
but when angles are sent to all entities about all entities, the hires angles (for model orientation), was being overwritten.

sv_main.c

Code: Select all

		if (bits & U_ORIGIN1)
			MSG_WriteCoord (msg, ent->v.origin[0]);		
		if (bits & U_ANGLE1)
			MSG_WriteAngle(msg, ent->v.angles[0]);
		if (bits & U_ORIGIN2)
			MSG_WriteCoord (msg, ent->v.origin[1]);
		if (bits & U_ANGLE2)
			MSG_WriteAngle(msg, ent->v.angles[1]);
		if (bits & U_ORIGIN3)
			MSG_WriteCoord (msg, ent->v.origin[2]);
		if (bits & U_ANGLE3)
			MSG_WriteAngle(msg, ent->v.angles[2]);

but cl.viewangles and ent->v.angles must be loosly related...?

not to mention svc_setangle

cl_parse

Code: Select all

case svc_setangle:
				for (i=0 ; i<3 ; i++)
				{
					cl.viewangles[i] = MSG_ReadAngle ();
				}
sv_main:

Code: Select all

	if (ent->v.fixangle)
	{
		MSG_WriteByte (msg, svc_setangle);
		
		for (i=0 ; i<3 ; i++)
			MSG_WriteAngle (msg, ent->v.angles[i]);

		ent->v.fixangle = 0;
	}
again not using hires angles :|

Posted: Mon Apr 11, 2011 10:21 pm
by mh
All clients sent angles to the server as part of their input.

But.

Yes, the send back from the server does overwrite, but the client does nothing with this data. At least for the local client; it does use the data for other clients to orient their player.mdl correctly; for the local client it does absolutely nothing. The angles go into the entity_t struct for the local client for sure, but the view position on the local client is actually controlled by cl.viewent which is updated separately in your view.c

Then the next frame the local client just sends it's angles again, which are accumulated from input into a usercmd_t; doesn't even go near an entity_t (at least for this part of it; r_refdef.viewangles also comes in but let's not complicate things; Quake has too many variables scattered around the place all containing the same data and it makes my head hurt sometimes).

So really you've got two entity_t structs on each client representing the local client; cl_entities[cl.viewentity] is one of the regular entity_t structs that get overwritten by the server, and all other clients live there too, cl.viewent is a separate "special" entity_t that manages the state of you as the local client.

Clear as mud?

Posted: Mon Apr 11, 2011 10:28 pm
by r00k
Ok that's why sometimes players look like they are aiming at one point but the rockets comes out from the corner of their "bounding box"...

as long as my POV isnt getting reAdjusted. That would make for jittery aim.

Posted: Mon Apr 11, 2011 10:29 pm
by mh
...or it might be just MDL animations being crap... :lol: