From: <kso...@tw...> - 2004-10-06 21:40:28
|
Hi. A somewhat long and techical post follows - proceed with caution. :) Karsten Siegmund asked me about how the ship wall bounces work in XPilot NG. We've been trying to implement improved bounce code because in NG and high fps (50 or so) the walls have new features compared to old xpilot. For example there is something the players call "wall glue" where your ship loses all speed next to a wall. I'll try to decribe here some new wall bounce code I implemented. The original xpilot and xpilot ng bounce code basically worked the same way; the differences in behaviuor come from the higher fps and possibly from the fact that in ng ship edges, not only vertices are checked for collisions; maybe also because currently there's no sparks caused by wall bounces that could push your ship away from the wall. The idea of the old bounce code is that when the ship hits a wall (line) it bounces out at the same angle but in the opposite direction and the velocity is multiplied by the playerWallBrakeFactor option. The code that implements this in old xpilot ng is basically this: double x, y, l2, c, s, fx, fy; /* Here we have variables related to the wall line the ship has hit. Variables x and y are components of a vector from the start point of the line to the end point. One can also think of a right triangle with sides |x| and |y| and hypotenuse l, where l2 is the length of the hypotenuse squared. One can define a "wall (line) angle" alpha which is the angle the vector [x; y] makes with the x-axis, it can be in the range 0 <= alpha < 360 degrees (2*pi radians). The 'c' is the cosine and the 's' are the sine of 2*alpha. (This is easy to see because cos(alpha) = x/l and sin(alpha) = y/l, (x*x - y*y) / l2 = (cos(alpha))^2 - (sin(alpha))^2 = cos(2*alpha) 2*x*y / l2 = 2*cos(alpha)*sin(alpha) = sin(2*alpha) */ x = linet[line].delta.cx; y = linet[line].delta.cy; l2 = (x*x + y*y); c = (x*x - y*y) / l2; s = 2*x*y / l2; ... /* calculate new move delta, what is left to move after bounce */ fx = move->delta.cx * c + move->delta.cy * s; fy = move->delta.cx * s - move->delta.cy * c; move->delta.cx = fx * options.playerWallBrakeFactor; move->delta.cy = fy * options.playerWallBrakeFactor; /* calculate new velocity after bounce */ fx = pl->vel.x * c + pl->vel.y * s; fy = pl->vel.x * s - pl->vel.y * c; pl->vel.x = fx * options.playerWallBrakeFactor; pl->vel.y = fy * options.playerWallBrakeFactor; Ok, so the new move delta and velocity after bounce is calculated using a matrix multiplication. One can write A = [c s; s -c], a 2x2 matrix and v = [pl->vel.x; pl->vel.y], a 2x1 matrix. The new velocity after bounce is options.playerWallBrakeFactor*(A*v). So one wonders why does this work? The way I imagined the situation is that one could rotate the line and the velocity vector so that alpha would be 0, then it would be easier to see what is going on. To rotate a 2D vector the angle theta (counterclockwise) one can use the 2x2 matrix [cos(theta) -sin(theta); sin(theta) cos(theta)]. However in this case we make the rotation clockwise so we use the angle -alpha. Using -alpha for theta we get the matrix B = [cos(alpha) sin(alpha); -sin(alpha) cos(alpha)]. Now the hypotenuse l is "on top of" the x-axis, the wall normal is in the y-axis' direction. In the bounce we now change the velocity component which is perpendicular to the wall to into the opposite direction. If v was the initial velocity one can write v1 (2x1 matrix) = B * v. To change the direction of the velocity component perpendicular to the wall we multiply v1 by the matrix C = [1 0; 0 -1], we get v2 = C * B * v. Now we can rotate everything back to the correct angle alpha using the inverse matrix of B, a counterclockwise rotation by angle alpha: inv(B) = [cos(alpha) -sin(alpha); sin(alpha) cos(alpha)]; v3 = inv(B) * C * B * v. Vector v3 needs to be multiplied by the playerWallBrakeFactor to get the end result. Now one can check that inv(B) * C * B is the matrix A. To implement new bounce types, one can use the same sort of ideas described above. My first attempt was one where there was used separate multipliers for the parallel and the perpendicular components, so basically you would have an own playerWallBrakeFactor for both components. These 3 different patches implement this: http://www.hut.fi/~ksoderbl/xpilot/patches/separatemultipliers.diff http://www.hut.fi/~ksoderbl/xpilot/patches/separatemultipliers2.diff http://www.hut.fi/~ksoderbl/xpilot/patches/separatemultipliers3.diff As one can see, using separate multipliers can also be implemented with a 2x2 matrix multiplication. The separate multipliers implementation wasn't good enough (in my opinion it was quite ok in practice, one would make it so that the multiplier used for the parallell component would be bigger than the one used for the perpendicular one, then the wall glue would partially go away). Erik Andersson (Mara/Virus) suggested this idea: "Vtangent2 = (1-Vnormal1/Vtotal1*wallfriction)*Vtangent1" The idea of this is that the new parallel speed will depend on how hard the wall hit is; a light touch slows us down only a little. Uoti Urpala (U/Uau/MWMW...MWMWMW) suggested this: "change the parallel one by MIN(C1*perpendicular_change, C2*parallel_speed) if you assume the wall has a coefficient of friction C1" Ok, this is a bit confusing maybe, but I implemented it anyway. Mara's suggestion is what is used in ng by default at the moment (version 4.6.6). Uau's suggestion can be used by changing the maraWallBounce option to false (note that probably in the future this option will go away or will be replaced by playerWallBounceType option or so). The code that implement this is in function Bounce_player() in walls.c, check e.g. http://cvs.sourceforge.net/viewcvs.py/xpilot/xpilot/src/server/walls.c?rev=1.199&view=markup kps |