Avirox's Rotation Tutorial Adapted to NetQuake

Post tutorials on how to do certain tasks within game or engine code here.
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

There are indeed problems when blocking a rotating door in DP; in my case, blocking it can make the door rotate an extra 360 degrees (through the player), then close normally...

We'll have to wait until LH gets back I think.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

random observations:

1.> When standing against and touching the HEALTH door and shooting it, it gets stuck closed with the open sound looping, never to open again :/

2.> Standing on the door that opens upwards my feet sink into the door.

Code: Select all


	self.max_health = self.health;
	self.solid = SOLID_BSP;
	self.movetype = MOVETYPE_PUSH; // gb
	
	setorigin (self, self.origin);  // self.origin
	
	setmodel (self, self.model);
	
	self.classname = "door_rotating";
	
shouldnt there be a setsize after setmodel??

3.> the really FAST spinning block allows me to pass thru it. (collision bug?)
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

setmodel sets the size of the ent to match the model that was set (in nq, alias models get the size '-16 -16 -16 16 16 16', necessitating the need for a set size, while in qw, setmodel on alias models does not set the size due to qw's dedicated server not trying to load the model).
Note that SV_LinkEntity does not add +/- 1 for the vertical direction by default, which might affect things.
Also note that pitch on bsp models is inverted compared to alias models.
(LH knows all this so this stuff won't help with darkplaces, but might be useful to modders of other engines).
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

1.> When standing against and touching the HEALTH door and shooting it, it gets stuck closed with the open sound looping, never to open again :/
Yup. Stupid bug. The opening sound loops because in the case of a working door, it is cut off by the closing sound playing on the same channel. Since the door doesn't reach the end position, the second sound is never played and thus the first one loops endlessly.

There is something wrong with .blocked. Not sure if this is a problem in the qc or in the engine.
2.> Standing on the door that opens upwards my feet sink into the door.
Acknowledged; same happens to me :) Is this related to the fact that you also sink into trains or lifts going upwards?
3.> the really FAST spinning block allows me to pass thru it. (collision bug?)
You are probably spun around so fast that it feels like passing straight through - remember the rotating thingy doesn't update your view angle (yet) so you come out looking in the same direction as you did when you went in. Or can you pass vertically through it, ie can you jump on top and land on the floor?
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

A small optimization...

Post by taniwha »

I don't know if this is in the current patch (I haven't looked yet), but in the original post, SV_ClipMoveToEntity is calling AngleVectors twice, once to rotate the trace into the entity's frame, and again to rotate back to the world's frame. This is not necessary because the inverse of a rotation matrix is the transpose of the rotation matrix. This means that when rotating the trace back into the world's frame, simply doing three swaps instead of full frame recalculation (with attendant calls to cos and sin) is all that's needed, and thus will be faster (and possibly more accurate, yay floats).

Note: this is untested, but it should give you the idea. Replace the second call to AngleVectors (and the preceeding VectorSubtract of the angles) with a tested version of the following

Code: Select all

temp = forward[1]; forward[1] = right[0]; right[0] = temp;
temp = forward[2]; forward[2] = up[0]; up[0] = temp;
temp = right[2]; right[2] = up[1]; up[1] = temp;
forward[0], right[1] and up[2] don't change as they form the diagonal of the matrix.
Leave others their otherness.
http://quakeforge.net/
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Post by taniwha »

Ok, I've been able to test it now, and there's a very important condition for it to work as-is: right must be negated before the transpose. This also means the DotProduct loses the negation:

Code: Select all

        AngleVectors (SVvector (touched, angles), forward, right, up);
        VectorNegate (right, right);    // convert lhs to rhs

        VectorCopy (start_l, temp);
        start_l[0] = DotProduct (temp, forward);
        start_l[1] = DotProduct (temp, right);
        start_l[2] = DotProduct (temp, up);
...

Code: Select all

        // transpose the rotation matrix to get its inverse
        t = forward[1]; forward[1] = right[0]; right[0] = t;
        t = forward[2]; forward[2] = up[0]; up[0] = t;
        t = right[2]; right[2] = up[1]; up[1] = t;

        VectorCopy (trace.endpos, temp);
        trace.endpos[0] = DotProduct (temp, forward);
        trace.endpos[1] = DotProduct (temp, right);
        trace.endpos[2] = DotProduct (temp, up);
If you don't wish to negate right, then the transpose code needs to be sprinkled with minus signs.

I haven't yet got any tests for it, but my implementation in QF should support rotating trains. While copying SV_PushRotate into QF, I noticed that it was 75% the same as SV_PushMove, so I merged the code as such.

I'll prepare an edited version of RMQ's sv_phys.c as QF's code style is very different. However, the core is:

Code: Select all

        // calculate destination position
        VectorSubtract (SVvector (check, origin),
                        SVvector (pusher, origin), org);
        org2[0] = DotProduct (org, forward);
        org2[1] = -DotProduct (org, right);
        org2[2] = DotProduct (org, up);
        VectorSubtract (org2, org, move);
        VectorAdd (move, tmove, move);
Where "tmove" is the old "move" (ie, translation move) and "move" is now the move resulting from the translation and rotation.
Leave others their otherness.
http://quakeforge.net/
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

This is pretty interesting. I'll take a closer look / get one of our coders to look at it once we have some leeway.

RMQ's rotatings will need another pass, to add trains and make entities inherit the angles / rotate with the thing they're standing on. etc.

Good findings.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

May have found a bug in this code; see here: http://www.celephais.net/board/view_thr ... &start=147

Disabling the rotation stuff in world.c fixes it, but obviously that's not 100% desirable for various reasons.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

OK, the problem spot is SV_LinkEdict and the bug is on account of this block of code in Baker's:

Code: Select all

if (ent->v.solid == SOLID_BSP &&
(ent->v.angles[0] || ent->v.angles[1] || ent->v.angles[2]) && ent != sv.edicts)
{ // expand for rotation
float max, v;
int i;

max = DotProduct(ent->v.mins, ent->v.mins);
v = DotProduct(ent->v.maxs, ent->v.maxs);

if (max < v)
max = v;

max = sqrt(max);

for (i=0 ; i<3 ; i++)
{
ent->v.absmin[i] = ent->v.origin[i] - max;
ent->v.absmax[i] = ent->v.origin[i] + max;
}
}
Replacing it with the original code from ID Quake (that's #ifdef QUAKE2'ed out) will fix it. I'd be curious as to why Baker changed the original ID Quake code for this condition though.

As Spike correctly noted this code is overkill anyway as it expands the bbox by the same values in all directions.

The specific cause appears to be when a mapper has positioned an inline bmodel entity by setting it's angles instead of just moving the brush(es). It's possible that the editor used may have done it too (in other words the mapper is innocent).
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

This will give you a correctly rotated bounding box on the server, and can be used instead of the rotation expansion in ID Quake:

Code: Select all

void SV_RotateBBoxToAbsMinMax (edict_t *ent)
{
	int i, j;
	float mins[3];
	float maxs[3];
	vec3_t bbox[8];
	vec3_t fv, rv, uv;
	float angles[3];

	// compute a full bounding box
	for (i = 0; i < 8; i++)
	{
		bbox[i][0] = (i & 1) ? ent->v.mins[0] : ent->v.maxs[0];
		bbox[i][1] = (i & 2) ? ent->v.mins[1] : ent->v.maxs[1];
		bbox[i][2] = (i & 4) ? ent->v.mins[2] : ent->v.maxs[2];
	}

	// derive forward/right/up vectors from the angles
	AngleVectors (ent->v.angles, fv, rv, uv);

	// compute the rotated bbox corners
	mins[0] = mins[1] = mins[2] = 9999999;
	maxs[0] = maxs[1] = maxs[2] = -9999999;

	// and rotate the bounding box
	for (i = 0; i < 8; i++)
	{
		vec3_t tmp;

		VectorCopy (bbox[i], tmp);

		bbox[i][0] = DotProduct (fv, tmp);
		bbox[i][1] = -DotProduct (rv, tmp);
		bbox[i][2] = DotProduct (uv, tmp);

		// and convert them to mins and maxs
		for (j = 0; j < 3; j++)
		{
			if (bbox[i][j] < mins[j]) mins[j] = bbox[i][j];
			if (bbox[i][j] > maxs[j]) maxs[j] = bbox[i][j];
		}
	}

	// translate the bbox to it's final position at the entity origin
	VectorAdd (ent->v.origin, mins, ent->v.absmin);
	VectorAdd (ent->v.origin, maxs, ent->v.absmax);
}
There's another bug with the rotation code where the initial positioning of a bmodel that's been rotated in this manner is off. Hunting it down.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

More info again.

In the particular case I've found, the brush is incorrectly offset from it's bounding box in most engines. I've tested in Fitz, DP, RMQ, DirectQ, ProQuake, Qrack, TyrQuake. Here's a shot from Fitz illustrating the problem.

Image

Oddly enough ProQuake and TyrQuake are the only ones from my list that get it right. I say "oddly" because ProQuake - I assume - has server-side rotation support (unless Baker never got round to putting it in). I also say "oddly" because Fitz doesn't.

I was working on the basis that the root cause was rotation support, particularly in light of the fact that a reversion of one part of this code to the #ifdef'ed out ID original fixes another bug - the brush not coming up.

The next step I think is to start reverting portions of code back to the original and see if I can identify at which point it breaks. More later.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

OK, it's a protocol problem. Three things are actually at work here:
  • The brush model has it's angles (actually "angle", but it's the same thing) set at (0, -1, 0) in the BSP file.
  • Use of Q_rint is causing this to translate to a non-zero angle for MSG_WriteAngle; simply casting to (int) removes that part of it and makes things valid, except for:
  • If using a modified protocol with more precision in angles the bad data still gets through.
In other words it was always broken but it was never noticed owing to loss of precision when transmitting angles in stock protocol 15.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:Oddly enough ProQuake and TyrQuake are the only ones from my list that get it right. I say "oddly" because ProQuake - I assume - has server-side rotation support (unless Baker never got round to putting it in). I also say "oddly" because Fitz doesn't.

I was working on the basis that the root cause was rotation support, particularly in light of the fact that a reversion of one part of this code to the #ifdef'ed out ID original fixes another bug - the brush not coming up.

The next step I think is to start reverting portions of code back to the original and see if I can identify at which point it breaks. More later.
I've never quite trusted Q_Rint or rounding of the angles in general. I know this sounds like a funny reason, but with stock protocol 15 and rounding the angles, if I did impulse 9 and fired a rocket the angle didn't feel right nor did the rocket trail feel right.

So even though this lead to better precision, I figured that in single player the sv_aim default would negate the need for more precision and ProQuake clients talk don't send angles to ProQuake servers using bytes anyway so I avoid Q_Rint.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

the original angle sending code doesn't round, it truncates. this means that on average, the angle sent is half a graduation too far to the right, regardless of actual precision. Or something.
Correcting the rounding by rounding to the nearest value (or adding 0.5 in the direction away from 0 then truncating), after changing it to network scaling, will give better accuracy regardless of precision.

rounding isn't the problem. the problem is that once you add rotation support in the engine, the server starts using a rotated bsp submodel (remember the origin is at '0 0 0').

The issue mh is highlighing appears in two cases:
1: The engine uses a higher precision rounding/protocol and doesn't support rotation. This results in the client seeing a non-0 rotation, and the ent no longer matches how its rotated on the server (read: unrotated).
2: The engine uses regular protocols+rounding, but supports rotation. This results in the client seeing it as unrotated, but the server rotating it by exactly 1 degree.

Both cases are going to exhibit this bug, but in different directions.
The only true fix I can suggest here is to round server-side rotated-bboxes to always be truncated/rounded in the same way as the angles that will be sent to the client.
You can also, and probably should, set the angles back to '0 0 0' in the qc code after setting the movedir field. That will also fix it, but rotating objects will still have issues. Such rotating objects should not exhibit the issue in such a fatal way, as hopefully you would be using a rotation-aware qbsp with a sane and closer origin, as well as motion lessening the noticability of the issue.

An engine will still have issues with an angle field of -2, even if it doesn't attempt greater angle accuracy.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

In the specific map that brought it to my attention the brush model in question actually did have an angles of (0, -1, 0) in the BSP file. No amount of anything on the server is going to fix that, and the bug did definitively manifest as a direct result of rounding. Don't round - no bug. Round to nearest - bug.

It also manifested in an engine that does not support rotation, but that does use rounding - FitzQuake. It can be reproduced in stock ID Quake (or any other engine that does not support rotation) simply by adding rounding.

I've nothing against rounding - this is clearly a map/editor/tool bug (the worldmodel itself had angles of (0, -92, 0)!!!) rather than an engine bug. But it poses the interesting question: if it worked in ID Quake, but yet is clearly a content bug (in other words it worked by accident rather than by design), should it be "fixed"?
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Post Reply